]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Merge branch 'release/v1.2.0'
authorChocobozzz <me@florianbigard.com>
Wed, 6 Feb 2019 11:26:58 +0000 (12:26 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 6 Feb 2019 11:26:58 +0000 (12:26 +0100)
631 files changed:
.travis.yml
CHANGELOG.md
CREDITS.md
FAQ.md
README.md
client/package.json
client/src/app/+about/about-instance/about-instance.component.html
client/src/app/+about/about-instance/about-instance.component.scss
client/src/app/+about/about-instance/about-instance.component.ts
client/src/app/+about/about-instance/contact-admin-modal.component.html [new file with mode: 0644]
client/src/app/+about/about-instance/contact-admin-modal.component.scss [new file with mode: 0644]
client/src/app/+about/about-instance/contact-admin-modal.component.ts [new file with mode: 0644]
client/src/app/+about/about.module.ts
client/src/app/+accounts/account-about/account-about.component.ts
client/src/app/+accounts/account-videos/account-videos.component.ts
client/src/app/+accounts/accounts.component.ts
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/+admin/follows/followers-list/followers-list.component.ts
client/src/app/+admin/follows/following-add/following-add.component.ts
client/src/app/+admin/follows/following-list/following-list.component.ts
client/src/app/+admin/follows/shared/redundancy-checkbox.component.ts
client/src/app/+admin/jobs/jobs-list/jobs-list.component.ts
client/src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts
client/src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts
client/src/app/+admin/moderation/moderation.component.scss
client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html
client/src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts
client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.html
client/src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts
client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html
client/src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts
client/src/app/+admin/users/user-edit/user-create.component.ts
client/src/app/+admin/users/user-edit/user-update.component.ts
client/src/app/+admin/users/user-list/user-list.component.html
client/src/app/+admin/users/user-list/user-list.component.scss
client/src/app/+admin/users/user-list/user-list.component.ts
client/src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts
client/src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts
client/src/app/+my-account/my-account-history/my-account-history.component.html [new file with mode: 0644]
client/src/app/+my-account/my-account-history/my-account-history.component.scss [new file with mode: 0644]
client/src/app/+my-account/my-account-history/my-account-history.component.ts [new file with mode: 0644]
client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html [new file with mode: 0644]
client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss [new file with mode: 0644]
client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts [new file with mode: 0644]
client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html
client/src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts
client/src/app/+my-account/my-account-ownership/my-account-ownership.component.html
client/src/app/+my-account/my-account-ownership/my-account-ownership.component.ts
client/src/app/+my-account/my-account-routing.module.ts
client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts
client/src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts
client/src/app/+my-account/my-account-settings/my-account-notification-preferences/index.ts [new file with mode: 0644]
client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html [new file with mode: 0644]
client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss [new file with mode: 0644]
client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts [new file with mode: 0644]
client/src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts
client/src/app/+my-account/my-account-settings/my-account-settings.component.html
client/src/app/+my-account/my-account-settings/my-account-settings.component.ts
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts
client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.scss
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
client/src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts
client/src/app/+my-account/my-account-videos/my-account-videos.component.html
client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html
client/src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts
client/src/app/+my-account/my-account.component.html
client/src/app/+my-account/my-account.component.scss
client/src/app/+my-account/my-account.component.ts
client/src/app/+my-account/my-account.module.ts
client/src/app/+my-account/shared/actor-avatar-info.component.ts
client/src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts
client/src/app/+verify-account/verify-account-email/verify-account-email.component.html
client/src/app/+verify-account/verify-account-email/verify-account-email.component.ts
client/src/app/+video-channels/video-channel-about/video-channel-about.component.ts
client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts
client/src/app/+video-channels/video-channels-routing.module.ts
client/src/app/+video-channels/video-channels.component.ts
client/src/app/app-routing.module.ts
client/src/app/app.component.html
client/src/app/app.component.scss
client/src/app/app.component.ts
client/src/app/app.module.ts
client/src/app/core/auth/auth-user.model.ts
client/src/app/core/auth/auth.service.ts
client/src/app/core/auth/index.ts
client/src/app/core/confirm/index.ts
client/src/app/core/core.module.ts
client/src/app/core/index.ts
client/src/app/core/notification/index.ts [new file with mode: 0644]
client/src/app/core/notification/notifier.service.ts [new file with mode: 0644]
client/src/app/core/notification/user-notification-socket.service.ts [new file with mode: 0644]
client/src/app/core/routing/login-guard.service.ts
client/src/app/core/routing/redirect.service.ts
client/src/app/core/routing/user-right-guard.service.ts
client/src/app/core/server/server.service.ts
client/src/app/header/header.component.html
client/src/app/header/header.component.scss
client/src/app/login/login.component.html
client/src/app/login/login.component.ts
client/src/app/menu/avatar-notification.component.html [new file with mode: 0644]
client/src/app/menu/avatar-notification.component.scss [new file with mode: 0644]
client/src/app/menu/avatar-notification.component.ts [new file with mode: 0644]
client/src/app/menu/index.ts
client/src/app/menu/language-chooser.component.html
client/src/app/menu/language-chooser.component.scss
client/src/app/menu/menu.component.html
client/src/app/menu/menu.component.scss
client/src/app/reset-password/reset-password.component.ts
client/src/app/search/search-filters.component.ts
client/src/app/search/search.component.html
client/src/app/search/search.component.scss
client/src/app/search/search.component.ts
client/src/app/shared/actor/actor.model.ts
client/src/app/shared/buttons/action-dropdown.component.html
client/src/app/shared/buttons/action-dropdown.component.scss
client/src/app/shared/buttons/button.component.html
client/src/app/shared/buttons/button.component.scss
client/src/app/shared/buttons/button.component.ts
client/src/app/shared/buttons/delete-button.component.html
client/src/app/shared/buttons/edit-button.component.html
client/src/app/shared/confirm/confirm.component.html [moved from client/src/app/core/confirm/confirm.component.html with 87% similarity]
client/src/app/shared/confirm/confirm.component.scss [moved from client/src/app/core/confirm/confirm.component.scss with 100% similarity]
client/src/app/shared/confirm/confirm.component.ts [moved from client/src/app/core/confirm/confirm.component.ts with 96% similarity]
client/src/app/shared/forms/form-reactive.ts
client/src/app/shared/forms/form-validators/form-validator.service.ts
client/src/app/shared/forms/form-validators/index.ts
client/src/app/shared/forms/form-validators/instance-validators.service.ts [new file with mode: 0644]
client/src/app/shared/forms/form-validators/user-validators.service.ts
client/src/app/shared/forms/form-validators/video-abuse-validators.service.ts
client/src/app/shared/forms/form-validators/video-channel-validators.service.ts
client/src/app/shared/forms/markdown-textarea.component.ts
client/src/app/shared/forms/peertube-checkbox.component.html
client/src/app/shared/forms/peertube-checkbox.component.ts
client/src/app/shared/forms/reactive-file.component.ts
client/src/app/shared/icons/global-icon.component.html [new file with mode: 0644]
client/src/app/shared/icons/global-icon.component.scss [new file with mode: 0644]
client/src/app/shared/icons/global-icon.component.ts [new file with mode: 0644]
client/src/app/shared/instance/instance.service.ts [new file with mode: 0644]
client/src/app/shared/menu/top-menu-dropdown.component.html [new file with mode: 0644]
client/src/app/shared/menu/top-menu-dropdown.component.scss [new file with mode: 0644]
client/src/app/shared/menu/top-menu-dropdown.component.ts [new file with mode: 0644]
client/src/app/shared/misc/help.component.html
client/src/app/shared/misc/help.component.scss
client/src/app/shared/misc/help.component.ts
client/src/app/shared/misc/utils.ts
client/src/app/shared/moderation/user-ban-modal.component.html
client/src/app/shared/moderation/user-ban-modal.component.ts
client/src/app/shared/moderation/user-moderation-dropdown.component.ts
client/src/app/shared/renderer/html-renderer.service.ts [new file with mode: 0644]
client/src/app/shared/renderer/index.ts [new file with mode: 0644]
client/src/app/shared/renderer/linkifier.service.ts [moved from client/src/app/videos/+video-watch/comment/linkifier.service.ts with 100% similarity]
client/src/app/shared/renderer/markdown.service.ts [moved from client/src/app/videos/shared/markdown.service.ts with 100% similarity]
client/src/app/shared/rest/component-pagination.model.ts
client/src/app/shared/rest/rest-extractor.service.ts
client/src/app/shared/shared.module.ts
client/src/app/shared/user-subscription/remote-subscribe.component.ts
client/src/app/shared/user-subscription/subscribe-button.component.ts
client/src/app/shared/users/index.ts
client/src/app/shared/users/user-history.service.ts [new file with mode: 0644]
client/src/app/shared/users/user-notification.model.ts [new file with mode: 0644]
client/src/app/shared/users/user-notification.service.ts [new file with mode: 0644]
client/src/app/shared/users/user-notifications.component.html [new file with mode: 0644]
client/src/app/shared/users/user-notifications.component.scss [new file with mode: 0644]
client/src/app/shared/users/user-notifications.component.ts [new file with mode: 0644]
client/src/app/shared/users/user.model.ts
client/src/app/shared/video-abuse/video-abuse.service.ts
client/src/app/shared/video-blacklist/video-blacklist.service.ts
client/src/app/shared/video/abstract-video-list.html
client/src/app/shared/video/abstract-video-list.scss
client/src/app/shared/video/abstract-video-list.ts
client/src/app/shared/video/feed.component.html
client/src/app/shared/video/feed.component.scss
client/src/app/shared/video/video-miniature.component.scss
client/src/app/shared/video/video.model.ts
client/src/app/signup/signup.component.html
client/src/app/signup/signup.component.ts
client/src/app/videos/+video-edit/shared/video-caption-add-modal.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.scss
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.scss
client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts
client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html
client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts
client/src/app/videos/+video-edit/video-add-components/video-send.scss [moved from client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss with 57% similarity]
client/src/app/videos/+video-edit/video-add-components/video-send.ts
client/src/app/videos/+video-edit/video-add-components/video-upload.component.html
client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss
client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts
client/src/app/videos/+video-edit/video-add.component.ts
client/src/app/videos/+video-edit/video-update.component.html
client/src/app/videos/+video-edit/video-update.component.ts
client/src/app/videos/+video-watch/comment/video-comment-add.component.ts
client/src/app/videos/+video-watch/comment/video-comment.component.scss
client/src/app/videos/+video-watch/comment/video-comment.component.ts
client/src/app/videos/+video-watch/comment/video-comment.service.ts
client/src/app/videos/+video-watch/comment/video-comments.component.ts
client/src/app/videos/+video-watch/modal/video-blacklist.component.html
client/src/app/videos/+video-watch/modal/video-blacklist.component.ts
client/src/app/videos/+video-watch/modal/video-download.component.html
client/src/app/videos/+video-watch/modal/video-download.component.ts
client/src/app/videos/+video-watch/modal/video-report.component.html
client/src/app/videos/+video-watch/modal/video-report.component.scss
client/src/app/videos/+video-watch/modal/video-report.component.ts
client/src/app/videos/+video-watch/modal/video-share.component.html
client/src/app/videos/+video-watch/modal/video-share.component.ts
client/src/app/videos/+video-watch/modal/video-support.component.html
client/src/app/videos/+video-watch/modal/video-support.component.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.scss
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/app/videos/+video-watch/video-watch.module.ts
client/src/app/videos/shared/index.ts [deleted file]
client/src/app/videos/video-list/video-local.component.ts
client/src/app/videos/video-list/video-overview.component.ts
client/src/app/videos/video-list/video-recently-added.component.ts
client/src/app/videos/video-list/video-trending.component.ts
client/src/app/videos/video-list/video-user-subscriptions.component.ts
client/src/assets/images/global/add.html [moved from client/src/assets/images/global/add.svg with 72% similarity]
client/src/assets/images/global/alert.html [moved from client/src/assets/images/video/alert.svg with 71% similarity]
client/src/assets/images/global/circle-tick.html [new file with mode: 0644]
client/src/assets/images/global/cloud-download.html [new file with mode: 0644]
client/src/assets/images/global/cloud-error.html [new file with mode: 0644]
client/src/assets/images/global/cog.html [new file with mode: 0644]
client/src/assets/images/global/cross.html [moved from client/src/assets/images/global/cross.svg with 63% similarity]
client/src/assets/images/global/delete-black.svg [deleted file]
client/src/assets/images/global/delete-white.svg [deleted file]
client/src/assets/images/global/delete.html [moved from client/src/assets/images/global/delete-grey.svg with 71% similarity]
client/src/assets/images/global/download.html [moved from client/src/assets/images/video/download-black.svg with 64% similarity]
client/src/assets/images/global/edit-black.svg [deleted file]
client/src/assets/images/global/edit.html [moved from client/src/assets/images/global/edit-grey.svg with 62% similarity]
client/src/assets/images/global/help.html [moved from client/src/assets/images/global/help.svg with 76% similarity]
client/src/assets/images/global/im-with-her.html [moved from client/src/assets/images/global/im-with-her.svg with 70% similarity]
client/src/assets/images/global/no.html [new file with mode: 0644]
client/src/assets/images/global/sparkle.html [new file with mode: 0644]
client/src/assets/images/global/syndication.html [moved from client/src/assets/images/global/syndication.svg with 88% similarity]
client/src/assets/images/global/tick.html [moved from client/src/assets/images/global/tick.svg with 63% similarity]
client/src/assets/images/global/undo.html [new file with mode: 0644]
client/src/assets/images/global/undo.svg [deleted file]
client/src/assets/images/global/user-add.html [new file with mode: 0644]
client/src/assets/images/global/validate.html [moved from client/src/assets/images/global/validate.svg with 69% similarity]
client/src/assets/images/video/blacklist.svg [deleted file]
client/src/assets/images/video/dislike-white.svg [deleted file]
client/src/assets/images/video/dislike.html [moved from client/src/assets/images/video/dislike-grey.svg with 77% similarity]
client/src/assets/images/video/download-grey.svg [deleted file]
client/src/assets/images/video/download-white.svg [deleted file]
client/src/assets/images/video/heart.html [moved from client/src/assets/images/video/heart.svg with 66% similarity]
client/src/assets/images/video/like-white.svg [deleted file]
client/src/assets/images/video/like.html [moved from client/src/assets/images/video/like-grey.svg with 61% similarity]
client/src/assets/images/video/more.html [moved from client/src/assets/images/video/more.svg with 76% similarity]
client/src/assets/images/video/share.html [moved from client/src/assets/images/video/share.svg with 62% similarity]
client/src/assets/images/video/upload.html [moved from client/src/assets/images/header/upload-white.svg with 69% similarity]
client/src/assets/images/video/upload.svg [deleted file]
client/src/assets/player/peertube-player-local-storage.ts
client/src/assets/player/peertube-player.ts
client/src/assets/player/peertube-videojs-plugin.ts
client/src/assets/player/peertube-videojs-typings.ts
client/src/assets/player/settings-menu-item.ts
client/src/assets/player/utils.ts
client/src/environments/environment.ts
client/src/locale/source/angular_en_US.xml
client/src/locale/source/server_en_US.xml
client/src/locale/target/angular_ar_001.xml
client/src/locale/target/angular_ca_ES.xml
client/src/locale/target/angular_cs_CZ.xml
client/src/locale/target/angular_de_DE.xml
client/src/locale/target/angular_eo.xml
client/src/locale/target/angular_es_ES.xml
client/src/locale/target/angular_eu_ES.xml
client/src/locale/target/angular_fa_IR.xml
client/src/locale/target/angular_fr_FR.xml
client/src/locale/target/angular_gl_ES.xml
client/src/locale/target/angular_it_IT.xml
client/src/locale/target/angular_ja_JP.xml
client/src/locale/target/angular_jbo.xml
client/src/locale/target/angular_nl_NL.xml
client/src/locale/target/angular_oc.xml
client/src/locale/target/angular_pl_PL.xml
client/src/locale/target/angular_pt_BR.xml
client/src/locale/target/angular_ru_RU.xml
client/src/locale/target/angular_sv_SE.xml
client/src/locale/target/angular_ta.xml
client/src/locale/target/angular_zh_Hans_CN.xml
client/src/locale/target/angular_zh_Hant_TW.xml
client/src/locale/target/iso639_nl_NL.xml
client/src/locale/target/iso639_pl_PL.xml [deleted file]
client/src/locale/target/player_ar_001.xml
client/src/locale/target/player_it_IT.json [new file with mode: 0644]
client/src/locale/target/player_nl_NL.xml
client/src/locale/target/player_pl_PL.json [new file with mode: 0644]
client/src/locale/target/player_pl_PL.xml [deleted file]
client/src/locale/target/player_ru_RU.json [new file with mode: 0644]
client/src/locale/target/server_cs_CZ.json
client/src/locale/target/server_fr_FR.json
client/src/locale/target/server_it_IT.json [new file with mode: 0644]
client/src/locale/target/server_nl_NL.xml
client/src/locale/target/server_pl_PL.json [new file with mode: 0644]
client/src/locale/target/server_pl_PL.xml [deleted file]
client/src/locale/target/server_ru_RU.json [new file with mode: 0644]
client/src/manifest.webmanifest
client/src/polyfills.ts
client/src/sass/application.scss
client/src/sass/include/_bootstrap-variables.scss
client/src/sass/include/_mixins.scss
client/src/sass/include/_variables.scss
client/src/sass/primeng-custom.scss
client/src/standalone/videos/embed.html
client/src/standalone/videos/embed.ts
client/yarn.lock
config/default.yaml
config/production.yaml.example
config/test-1.yaml
config/test-2.yaml
config/test-3.yaml
config/test-4.yaml
config/test-5.yaml
config/test-6.yaml
config/test.yaml
package.json
scripts/build/client.sh
scripts/clean/server/test.sh
scripts/generate-code-contributors.ts
scripts/i18n/create-custom-files.ts
scripts/prune-storage.ts
scripts/upgrade.sh
server.ts
server/controllers/api/accounts.ts
server/controllers/api/config.ts
server/controllers/api/server/contact.ts [new file with mode: 0644]
server/controllers/api/server/index.ts
server/controllers/api/server/stats.ts
server/controllers/api/users/index.ts
server/controllers/api/users/me.ts
server/controllers/api/users/my-history.ts [new file with mode: 0644]
server/controllers/api/users/my-notifications.ts [new file with mode: 0644]
server/controllers/api/users/my-subscriptions.ts [new file with mode: 0644]
server/controllers/api/video-channel.ts
server/controllers/api/videos/abuse.ts
server/controllers/api/videos/blacklist.ts
server/controllers/api/videos/captions.ts
server/controllers/api/videos/comment.ts
server/controllers/api/videos/import.ts
server/controllers/api/videos/index.ts
server/controllers/bots.ts [new file with mode: 0644]
server/controllers/client.ts
server/controllers/feeds.ts
server/controllers/index.ts
server/controllers/static.ts
server/controllers/tracker.ts
server/helpers/activitypub.ts
server/helpers/captions-utils.ts
server/helpers/core-utils.ts
server/helpers/custom-validators/activitypub/activity.ts
server/helpers/custom-validators/activitypub/actor.ts
server/helpers/custom-validators/activitypub/announce.ts [deleted file]
server/helpers/custom-validators/activitypub/cache-file.ts
server/helpers/custom-validators/activitypub/flag.ts [new file with mode: 0644]
server/helpers/custom-validators/activitypub/misc.ts
server/helpers/custom-validators/activitypub/rate.ts
server/helpers/custom-validators/activitypub/undo.ts [deleted file]
server/helpers/custom-validators/activitypub/video-comments.ts
server/helpers/custom-validators/activitypub/videos.ts
server/helpers/custom-validators/activitypub/view.ts
server/helpers/custom-validators/misc.ts
server/helpers/custom-validators/servers.ts
server/helpers/custom-validators/user-notifications.ts [new file with mode: 0644]
server/helpers/custom-validators/users.ts
server/helpers/custom-validators/video-captions.ts
server/helpers/custom-validators/video-imports.ts
server/helpers/custom-validators/videos.ts
server/helpers/express-utils.ts
server/helpers/ffmpeg-utils.ts
server/helpers/image-utils.ts
server/helpers/regexp.ts [new file with mode: 0644]
server/helpers/requests.ts
server/helpers/utils.ts
server/helpers/webtorrent.ts
server/helpers/youtube-dl.ts
server/initializers/checker-after-init.ts
server/initializers/checker-before-init.ts
server/initializers/constants.ts
server/initializers/database.ts
server/initializers/migrations/0295-video-file-extname.ts [new file with mode: 0644]
server/initializers/migrations/0300-user-videos-history-enabled.ts [new file with mode: 0644]
server/initializers/migrations/0305-fix-unfederated-videos.ts [new file with mode: 0644]
server/initializers/migrations/0310-drop-unused-video-indexes.ts [new file with mode: 0644]
server/initializers/migrations/0315-user-notifications.ts [new file with mode: 0644]
server/initializers/migrations/0320-blacklist-unfederate.ts [new file with mode: 0644]
server/initializers/migrations/0325-video-abuse-fields.ts [new file with mode: 0644]
server/lib/activitypub/actor.ts
server/lib/activitypub/process/process-accept.ts
server/lib/activitypub/process/process-announce.ts
server/lib/activitypub/process/process-create.ts
server/lib/activitypub/process/process-dislike.ts [new file with mode: 0644]
server/lib/activitypub/process/process-flag.ts [new file with mode: 0644]
server/lib/activitypub/process/process-follow.ts
server/lib/activitypub/process/process-like.ts
server/lib/activitypub/process/process-undo.ts
server/lib/activitypub/process/process-update.ts
server/lib/activitypub/process/process-view.ts [new file with mode: 0644]
server/lib/activitypub/process/process.ts
server/lib/activitypub/share.ts
server/lib/activitypub/video-comments.ts
server/lib/activitypub/video-rates.ts
server/lib/activitypub/videos.ts
server/lib/cache/actor-follow-score-cache.ts [new file with mode: 0644]
server/lib/cache/index.ts
server/lib/client-html.ts
server/lib/emailer.ts
server/lib/job-queue/handlers/activitypub-follow.ts
server/lib/job-queue/handlers/activitypub-http-broadcast.ts
server/lib/job-queue/handlers/activitypub-http-unicast.ts
server/lib/job-queue/handlers/activitypub-refresher.ts
server/lib/job-queue/handlers/email.ts
server/lib/job-queue/handlers/video-file.ts
server/lib/job-queue/handlers/video-import.ts
server/lib/job-queue/handlers/video-views.ts
server/lib/job-queue/job-queue.ts
server/lib/notifier.ts [new file with mode: 0644]
server/lib/oauth-model.ts
server/lib/peertube-socket.ts [new file with mode: 0644]
server/lib/redis.ts
server/lib/schedulers/abstract-scheduler.ts
server/lib/schedulers/actor-follow-scheduler.ts [moved from server/lib/schedulers/bad-actor-follow-scheduler.ts with 51% similarity]
server/lib/schedulers/remove-old-jobs-scheduler.ts
server/lib/schedulers/update-videos-scheduler.ts
server/lib/schedulers/videos-redundancy-scheduler.ts
server/lib/schedulers/youtube-dl-update-scheduler.ts
server/lib/user.ts
server/lib/video-transcoding.ts
server/middlewares/csp.ts [new file with mode: 0644]
server/middlewares/dnt.ts
server/middlewares/index.ts
server/middlewares/oauth.ts
server/middlewares/validators/config.ts
server/middlewares/validators/index.ts
server/middlewares/validators/server.ts
server/middlewares/validators/sort.ts
server/middlewares/validators/user-history.ts [new file with mode: 0644]
server/middlewares/validators/user-notifications.ts [new file with mode: 0644]
server/middlewares/validators/users.ts
server/middlewares/validators/videos/video-blacklist.ts
server/middlewares/validators/videos/video-watch.ts
server/models/account/account-blocklist.ts
server/models/account/account.ts
server/models/account/user-notification-setting.ts [new file with mode: 0644]
server/models/account/user-notification.ts [new file with mode: 0644]
server/models/account/user-video-history.ts
server/models/account/user.ts
server/models/activitypub/actor-follow.ts
server/models/activitypub/actor.ts
server/models/redundancy/video-redundancy.ts
server/models/utils.ts
server/models/video/video-abuse.ts
server/models/video/video-blacklist.ts
server/models/video/video-channel.ts
server/models/video/video-comment.ts
server/models/video/video-file.ts
server/models/video/video-format-utils.ts
server/models/video/video-import.ts
server/models/video/video.ts
server/tests/api/activitypub/client.ts
server/tests/api/activitypub/fetch.ts
server/tests/api/activitypub/helpers.ts
server/tests/api/activitypub/refresher.ts
server/tests/api/activitypub/security.ts
server/tests/api/check-params/accounts.ts
server/tests/api/check-params/blocklist.ts
server/tests/api/check-params/config.ts
server/tests/api/check-params/contact-form.ts [new file with mode: 0644]
server/tests/api/check-params/follows.ts
server/tests/api/check-params/index.ts
server/tests/api/check-params/jobs.ts
server/tests/api/check-params/redundancy.ts
server/tests/api/check-params/search.ts
server/tests/api/check-params/services.ts
server/tests/api/check-params/user-notifications.ts [new file with mode: 0644]
server/tests/api/check-params/user-subscriptions.ts
server/tests/api/check-params/users.ts
server/tests/api/check-params/video-abuses.ts
server/tests/api/check-params/video-blacklist.ts
server/tests/api/check-params/video-captions.ts
server/tests/api/check-params/video-channels.ts
server/tests/api/check-params/video-comments.ts
server/tests/api/check-params/video-imports.ts
server/tests/api/check-params/videos-filter.ts
server/tests/api/check-params/videos-history.ts
server/tests/api/check-params/videos.ts
server/tests/api/redundancy/redundancy.ts
server/tests/api/search/search-activitypub-video-channels.ts
server/tests/api/search/search-activitypub-videos.ts
server/tests/api/search/search-videos.ts
server/tests/api/server/config.ts
server/tests/api/server/contact-form.ts [new file with mode: 0644]
server/tests/api/server/email.ts
server/tests/api/server/follow-constraints.ts
server/tests/api/server/follows.ts
server/tests/api/server/handle-down.ts
server/tests/api/server/index.ts
server/tests/api/server/jobs.ts
server/tests/api/server/no-client.ts
server/tests/api/server/reverse-proxy.ts
server/tests/api/server/stats.ts
server/tests/api/server/tracker.ts
server/tests/api/users/blocklist.ts
server/tests/api/users/index.ts
server/tests/api/users/user-notifications.ts [new file with mode: 0644]
server/tests/api/users/user-subscriptions.ts
server/tests/api/users/users-multiple-servers.ts
server/tests/api/users/users-verification.ts
server/tests/api/users/users.ts
server/tests/api/videos/index.ts
server/tests/api/videos/multiple-servers.ts
server/tests/api/videos/services.ts
server/tests/api/videos/single-server.ts
server/tests/api/videos/video-abuse.ts
server/tests/api/videos/video-blacklist-management.ts [deleted file]
server/tests/api/videos/video-blacklist.ts
server/tests/api/videos/video-captions.ts
server/tests/api/videos/video-change-ownership.ts
server/tests/api/videos/video-channels.ts
server/tests/api/videos/video-comments.ts
server/tests/api/videos/video-description.ts
server/tests/api/videos/video-imports.ts
server/tests/api/videos/video-nsfw.ts
server/tests/api/videos/video-privacy.ts
server/tests/api/videos/video-schedule-update.ts
server/tests/api/videos/video-transcoder.ts
server/tests/api/videos/videos-filter.ts
server/tests/api/videos/videos-history.ts
server/tests/api/videos/videos-overview.ts
server/tests/cli/create-import-video-file-job.ts
server/tests/cli/create-transcoding-job.ts
server/tests/cli/index.ts
server/tests/cli/optimize-old-videos.ts
server/tests/cli/peertube.ts
server/tests/cli/reset-password.ts
server/tests/cli/update-host.ts
server/tests/client.ts
server/tests/feeds/feeds.ts
server/tests/fixtures/video_short.avi [new file with mode: 0644]
server/tests/fixtures/video_short.mkv [new file with mode: 0644]
server/tests/fixtures/video_short_240p.mp4 [new file with mode: 0644]
server/tests/helpers/comment-model.ts [new file with mode: 0644]
server/tests/helpers/core-utils.ts
server/tests/helpers/index.ts
server/tests/misc-endpoints.ts
server/tests/real-world/populate-database.ts
server/tests/real-world/real-world.ts
server/tests/utils/miscs/email.ts [deleted file]
server/tests/utils/videos/video-history.ts [deleted file]
server/tools/peertube-get-access-token.ts
server/tools/peertube-import-videos.ts
server/tools/peertube-upload.ts
shared/models/activitypub/activity.ts
shared/models/activitypub/objects/object.model.ts [new file with mode: 0644]
shared/models/actors/actor.model.ts
shared/models/i18n/i18n.ts
shared/models/server/contact-form.model.ts [new file with mode: 0644]
shared/models/server/custom-config.model.ts
shared/models/server/index.ts [new file with mode: 0644]
shared/models/server/server-config.model.ts
shared/models/server/server-stats.model.ts
shared/models/users/index.ts
shared/models/users/user-notification-setting.model.ts [new file with mode: 0644]
shared/models/users/user-notification.model.ts [new file with mode: 0644]
shared/models/users/user-right.enum.ts
shared/models/users/user-role.ts
shared/models/users/user-update-me.model.ts
shared/models/users/user.model.ts
shared/models/videos/blacklist/video-blacklist-create.model.ts
shared/models/videos/blacklist/video-blacklist.model.ts
shared/models/videos/video.model.ts
shared/utils/cli/cli.ts [moved from server/tests/utils/cli/cli.ts with 100% similarity]
shared/utils/feeds/feeds.ts [moved from server/tests/utils/feeds/feeds.ts with 100% similarity]
shared/utils/index.ts [moved from server/tests/utils/index.ts with 82% similarity]
shared/utils/miscs/email-child-process.js [new file with mode: 0644]
shared/utils/miscs/email.ts [new file with mode: 0644]
shared/utils/miscs/miscs.ts [moved from server/tests/utils/miscs/miscs.ts with 92% similarity]
shared/utils/miscs/sql.ts [moved from server/tests/utils/miscs/sql.ts with 100% similarity]
shared/utils/miscs/stubs.ts [moved from server/tests/utils/miscs/stubs.ts with 100% similarity]
shared/utils/overviews/overviews.ts [moved from server/tests/utils/overviews/overviews.ts with 100% similarity]
shared/utils/requests/activitypub.ts [moved from server/tests/utils/requests/activitypub.ts with 73% similarity]
shared/utils/requests/check-api-params.ts [moved from server/tests/utils/requests/check-api-params.ts with 100% similarity]
shared/utils/requests/requests.ts [moved from server/tests/utils/requests/requests.ts with 96% similarity]
shared/utils/search/video-channels.ts [moved from server/tests/utils/search/video-channels.ts with 100% similarity]
shared/utils/search/videos.ts [moved from server/tests/utils/search/videos.ts with 96% similarity]
shared/utils/server/activitypub.ts [moved from server/tests/utils/server/activitypub.ts with 100% similarity]
shared/utils/server/clients.ts [moved from server/tests/utils/server/clients.ts with 100% similarity]
shared/utils/server/config.ts [moved from server/tests/utils/server/config.ts with 94% similarity]
shared/utils/server/contact-form.ts [new file with mode: 0644]
shared/utils/server/follows.ts [moved from server/tests/utils/server/follows.ts with 100% similarity]
shared/utils/server/jobs.ts [moved from server/tests/utils/server/jobs.ts with 84% similarity]
shared/utils/server/redundancy.ts [moved from server/tests/utils/server/redundancy.ts with 100% similarity]
shared/utils/server/servers.ts [moved from server/tests/utils/server/servers.ts with 86% similarity]
shared/utils/server/stats.ts [moved from server/tests/utils/server/stats.ts with 100% similarity]
shared/utils/socket/socket-io.ts [new file with mode: 0644]
shared/utils/users/accounts.ts [moved from server/tests/utils/users/accounts.ts with 96% similarity]
shared/utils/users/blocklist.ts [moved from server/tests/utils/users/blocklist.ts with 100% similarity]
shared/utils/users/login.ts [moved from server/tests/utils/users/login.ts with 100% similarity]
shared/utils/users/user-notifications.ts [new file with mode: 0644]
shared/utils/users/user-subscriptions.ts [moved from server/tests/utils/users/user-subscriptions.ts with 100% similarity]
shared/utils/users/users.ts [moved from server/tests/utils/users/users.ts with 94% similarity]
shared/utils/videos/services.ts [moved from server/tests/utils/videos/services.ts with 100% similarity]
shared/utils/videos/video-abuses.ts [moved from server/tests/utils/videos/video-abuses.ts with 94% similarity]
shared/utils/videos/video-blacklist.ts [moved from server/tests/utils/videos/video-blacklist.ts with 90% similarity]
shared/utils/videos/video-captions.ts [moved from server/tests/utils/videos/video-captions.ts with 100% similarity]
shared/utils/videos/video-change-ownership.ts [moved from server/tests/utils/videos/video-change-ownership.ts with 100% similarity]
shared/utils/videos/video-channels.ts [moved from server/tests/utils/videos/video-channels.ts with 97% similarity]
shared/utils/videos/video-comments.ts [moved from server/tests/utils/videos/video-comments.ts with 100% similarity]
shared/utils/videos/video-history.ts [new file with mode: 0644]
shared/utils/videos/video-imports.ts [moved from server/tests/utils/videos/video-imports.ts with 89% similarity]
shared/utils/videos/videos.ts [moved from server/tests/utils/videos/videos.ts with 98% similarity]
support/doc/api/openapi.yaml
support/doc/tools.md
support/docker/production/.env
support/docker/production/config/custom-environment-variables.yaml
support/docker/production/config/production.yaml
support/docker/production/docker-entrypoint.sh
support/freebsd/peertube
support/nginx/peertube
support/systemd/peertube.service
yarn.lock

index 3a73e4fc01da64a4ebee8d36a4730d308f6d772c..d252ae6252b80ab99896ee83147d1cf32599fd45 100644 (file)
@@ -48,12 +48,12 @@ matrix:
   - env: TEST_SUITE=jest
 
 script:
-  - travis_retry npm run travis -- "$TEST_SUITE"
+  - NODE_PENDING_JOB_WAIT=1000 travis_retry npm run travis -- "$TEST_SUITE"
 
 after_failure:
-  - cat test1/logs/all-logs.log
-  - cat test2/logs/all-logs.log
-  - cat test3/logs/all-logs.log
-  - cat test4/logs/all-logs.log
-  - cat test5/logs/all-logs.log
-  - cat test6/logs/all-logs.log
+  - cat test1/logs/peertube.log
+  - cat test2/logs/peertube.log
+  - cat test3/logs/peertube.log
+  - cat test4/logs/peertube.log
+  - cat test5/logs/peertube.log
+  - cat test6/logs/peertube.log
index cce6e740220457ceb35be8d5b03081cdefbfcd96..13bec75351660892df2610a60e905f72be7f70ad 100644 (file)
@@ -1,9 +1,101 @@
 # Changelog
 
+## v1.2.0
+
+### BREAKING CHANGES
+
+ * **Docker:** `PEERTUBE_TRUST_PROXY` env variable is now an array ([LecygneNoir](https://github.com/LecygneNoir))
+ * **Docker:** Check you have all the storage fields in your `/config/production.yaml` file: https://github.com/Chocobozzz/PeerTube/blob/develop/support/docker/production/config/production.yaml#L34
+ * **nginx:** Add redundancy endpoint in static file. **You should add it in your nginx configuration: https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/production.md#nginx**
+ * **nginx:** Add socket io endpoint. **You should add it in your nginx configuration: https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/production.md#nginx**
+ * Moderators can manage users now (add/delete/update/block)
+ * Add `tmp` and `redundancy` directories in configuration file. **You should configure them in your production.yaml**
+
+### Maintenance
+
+ * Check free storage before upgrading in upgrade script ([@Nutomic](https://github.com/nutomic))
+ * Explain that PeerTube must be stopped in prune storage script
+ * Add some security directives in the systemd unit configuration file ([@rigelk](https://github.com/rigelk) & [@mkoppmann](https://github.com/mkoppmann))
+ * Update FreeBSD startup script ([@gegeweb](https://github.com/gegeweb))
+
+### Docker
+
+ * Patch docker entrypoint to speed up the chown at startup ([LecygneNoir](https://github.com/LecygneNoir))
+
+### Features
+
+ * Add Russian, Polish and Italian languages
+ * Add user notifications:
+   * Notification types:
+     * Comment on my video
+     * New video from my subscriptions
+     * New video abuses (for moderators)
+     * Blacklist/Unblacklist on my video
+     * Video import finished (error or success)
+     * Pending video published (after transcoding or a scheduled update)
+     * My account or one of my channel has a new follower
+     * Someone (except muted accounts) mentioned me in comments
+     * A user registered on the instance (for moderators)
+   * Notification actions:
+     * Add a web notification
+     * Send an english email
+ * Add contact form in about page (**enabled by default**)
+ * Add ability to unfederate a local video in blacklist modal (**checkbox checked by default**)
+ * Support additional video extensions if transcoding is enabled (**enabled by default**)
+ * Redirect to the last url on login
+ * Add ability to automatically set the video caption in URL. Example: https://peertube2.cpy.re/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d?subtitle=ru
+ * Automatically enable the last selected caption when watching a video
+ * Add ability to disable, clear and list user videos history
+ * Add a button to help to translate peertube
+ * Add text in the report modal to explain to whom the report will be sent
+ * Open my account menu entries on hover
+ * Explain what features are enabled on the instance in the about page
+ * Add an error message in the forgot password modal if the instance email system is not configured
+ * Add sitemap
+ * Add well known url to change password ([@rigelk](https://github.com/rigelk))
+ * Remove 8GB video upload limit on client side. There may still be such limit depending on the reverse proxy configuration ([@scanlime](https://github.com/scanlime))
+ * Add CSP ([@rigelk](https://github.com/rigelk) & [@Nutomic](https://github.com/nutomic))
+ * Update title and description HTML tags when rendering video HTML page
+ * Add webfinger support for remote follows ([@acid-chicken](https://github.com/acid-chicken))
+ * Add tooltip to explain how the trending algorithm works ([@auberanger](https://github.com/auberanger))
+ * Warn users when they want to delete a channel because they will not be able to create another channel with the same name
+ * Warn users when they leave the video upload/update (on page refresh/tab close)
+ * Set max user name, user display name, channel name and channel display name lengths to 50 characters ([@McFlat](https://github.com/mcflat))
+ * Increase video abuse length to 3000 characters
+ * Add totalLocalVideoFilesSize in the stats endpoint
+
+## Bug fixes
+
+ * Fix the addition of captions to a video
+ * Fix federation of some videos
+ * Fix NSFW blur on search
+ * Add error message when trying to upload .ass subtitles
+ * Fix default homepage in the progressive web application
+ * Don't crash on queue error
+ * Fix EXDEV errors if you have multiple mount points
+ * Fix broken audio in transcoding with some videos
+ * Fix crash on getVideoFileStream issue
+ * Fix followers search
+ * Remove trailing `/` in CLI import script ([@HesioZ](https://github.com/HesioZ/))
+ * Use origin video url in canonical tag
+ * Fix captions in HTTP fallback
+ * Automatically refresh remote actors to fix deleted remote actors that are still displayed on some instances
+ * Add missing translations in video embed page
+ * Fix some styling issues in dark mode
+ * Fix transcoding issues with some videos
+ * Fix Mac OS mkv/avi upload
+ * Fix menu overflow on mobile
+ * Fix ownership button icons ([@joshmorel](https://github.com/joshmorel))
+
+
 ## v1.1.0
 
 ***Since v1.0.1***
 
+### BREAKING CHANGES
+
+ * **Docker:** `PEERTUBE_TRUST_PROXY` env variable is now an array ([LecygneNoir](https://github.com/LecygneNoir))
+
 ### Maintenance
 
  * Improve REST API documentation: https://docs.joinpeertube.org/api.html ([@rigelk](https://github.com/rigelk))
    * Add postfix image
    * Redirect HTTP -> HTTPS
    * Disable Træfik web UI
- * Add ability to set an array in `PEERTUBE_TRUST_PROXY` ([LecygneNoir](https://github.com/LecygneNoir))
 
 ### Features
  
index ad5125227d1fa438a09371d1fe69a0f2b3ef3089..509f9800dd3a92efb180e0e85f8c6878ecf07489 100644 (file)
@@ -6,25 +6,27 @@
  * [Nutomic](https://github.com/Nutomic)
  * [Jorropo](https://github.com/Jorropo)
  * [BO41](https://github.com/BO41)
+ * [joshmorel](https://github.com/joshmorel)
+ * [buoyantair](https://github.com/buoyantair)
  * [bnjbvr](https://github.com/bnjbvr)
  * [DavidLibeau](https://github.com/DavidLibeau)
  * [jankeromnes](https://github.com/jankeromnes)
- * [joshmorel](https://github.com/joshmorel)
  * [JohnXLivingston](https://github.com/JohnXLivingston)
  * [kaiyou](https://github.com/kaiyou)
+ * [McFlat](https://github.com/McFlat)
  * [DimitriGilbert](https://github.com/DimitriGilbert)
  * [floSoX](https://github.com/floSoX)
  * [Green-Star](https://github.com/Green-Star)
+ * [thomaskuntzz](https://github.com/thomaskuntzz)
  * [rezonant](https://github.com/rezonant)
  * [ldidry](https://github.com/ldidry)
- * [McFlat](https://github.com/McFlat)
  * [okhin](https://github.com/okhin)
  * [daftaupe](https://github.com/daftaupe)
- * [thomaskuntzz](https://github.com/thomaskuntzz)
  * [LecygneNoir](https://github.com/LecygneNoir)
  * [fflorent](https://github.com/fflorent)
  * [dedesite](https://github.com/dedesite)
  * [Nautigsam](https://github.com/Nautigsam)
+ * [scanlime](https://github.com/scanlime)
  * [tcitworld](https://github.com/tcitworld)
  * [am97](https://github.com/am97)
  * [dadall](https://github.com/dadall)
@@ -35,7 +37,6 @@
  * [jocelynj](https://github.com/jocelynj)
  * [lucas-dclrcq](https://github.com/lucas-dclrcq)
  * [lucaspontoexe](https://github.com/lucaspontoexe)
- * [scanlime](https://github.com/scanlime)
  * [flyingrub](https://github.com/flyingrub)
  * [SerCom-KC](https://github.com/SerCom-KC)
  * [valvin1](https://github.com/valvin1)
@@ -43,6 +44,7 @@
  * [sticmac](https://github.com/sticmac)
  * [barbeque](https://github.com/barbeque)
  * [luzpaz](https://github.com/luzpaz)
+ * [acid-chicken](https://github.com/acid-chicken)
  * [louistio](https://github.com/louistio)
  * [qsypoq](https://github.com/qsypoq)
  * [daker](https://github.com/daker)
@@ -65,6 +67,7 @@
  * [grizio](https://github.com/grizio)
  * [Glandos](https://github.com/Glandos)
  * [lanodan](https://github.com/lanodan)
+ * [HesioZ](https://github.com/HesioZ)
  * [jagannathBhat](https://github.com/jagannathBhat)
  * [jlebras](https://github.com/jlebras)
  * [alcalyn](https://github.com/alcalyn)
@@ -73,7 +76,9 @@
  * [zapashcanon](https://github.com/zapashcanon)
  * [mart-e](https://github.com/mart-e)
  * [0mp](https://github.com/0mp)
+ * [mkoppmann](https://github.com/mkoppmann)
  * [1000i100](https://github.com/1000i100)
+ * [roipoussiere](https://github.com/roipoussiere)
  * [zeograd](https://github.com/zeograd)
  * [PhieF](https://github.com/PhieF)
  * [Quenty31](https://github.com/Quenty31)
  * [h3zjp](https://trad.framasoft.org/zanata/profile/view/h3zjp)
  * [jfblanc](https://trad.framasoft.org/zanata/profile/view/jfblanc)
  * [jhertel](https://trad.framasoft.org/zanata/profile/view/jhertel)
+ * [jmf](https://trad.framasoft.org/zanata/profile/view/jmf)
  * [jorropo](https://trad.framasoft.org/zanata/profile/view/jorropo)
+ * [kairozen](https://trad.framasoft.org/zanata/profile/view/kairozen)
  * [kedemferre](https://trad.framasoft.org/zanata/profile/view/kedemferre)
  * [kousha](https://trad.framasoft.org/zanata/profile/view/kousha)
  * [krkk](https://trad.framasoft.org/zanata/profile/view/krkk)
  * [landrok](https://trad.framasoft.org/zanata/profile/view/landrok)
+ * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48)
  * [m4sk1n](https://trad.framasoft.org/zanata/profile/view/m4sk1n)
  * [matograine](https://trad.framasoft.org/zanata/profile/view/matograine)
  * [medow](https://trad.framasoft.org/zanata/profile/view/medow)
  * [xinayder](https://trad.framasoft.org/zanata/profile/view/xinayder)
  * [xosem](https://trad.framasoft.org/zanata/profile/view/xosem)
  * [zveryok](https://trad.framasoft.org/zanata/profile/view/zveryok)
+ * [aditoo](https://trad.framasoft.org/zanata/profile/view/aditoo)
+ * [autom](https://trad.framasoft.org/zanata/profile/view/autom)
+ * [curupira](https://trad.framasoft.org/zanata/profile/view/curupira)
+ * [leeroyepold48](https://trad.framasoft.org/zanata/profile/view/leeroyepold48)
 
 
 # Design
diff --git a/FAQ.md b/FAQ.md
index 1e586161c8f86f9b4dd6943b20bde94db18e8032..e335868f8194a6b05ec93cd62e1b007e2419e3bb 100644 (file)
--- a/FAQ.md
+++ b/FAQ.md
@@ -13,6 +13,8 @@
 - [Will an index of all the videos of servers you follow be too large for small servers?](#will-an-index-of-all-the-videos-of-servers-you-follow-be-too-large-for-small-servers)
 - [Which container formats can I use for the videos I want to upload?](#which-container-formats-can-i-use-for-the-videos-i-want-to-upload)
 - [I want to change my domain name, how can I do that?](#i-want-to-change-my-domain-name-how-can-i-do-that)
+- [Why do we have to put our Twitter username in PeerTube configuration?](#why-do-we-have-to-put-our-twitter-username-in-peertube-configuration)
+- [How video views are calculated?](#how-video-views-are-calculated)
 - [Should I have a big server to run PeerTube?](#should-i-have-a-big-server-to-run-peertube)
 - [Can I seed videos with my classic BitTorrent client (Transmission, rTorrent...)?](#can-i-seed-videos-with-my-classic-bittorrent-client-transmission-rtorrent)
 - [Why host on GitHub and Framagit?](#why-host-on-github-and-framagit)
@@ -89,6 +91,18 @@ WEBM, MP4 or OGV videos.
 You can't. You'll need to reinstall an instance and reupload your videos.
 
 
+## Why do we have to put our Twitter username in PeerTube configuration?
+
+You don't have to: we set a default value if you don't have a Twitter account.
+We need this information because Twitter requires an account for links share/videos embed on their platform.
+
+
+## How video views are calculated?
+
+Your web browser sends a view to the server after 30 seconds of playback. Then, the IP cannot send another view in the next hour.
+Views are buffered, so don't panic if the view counter stays the same after you watched a video.
+
+
 ## Should I have a big server to run PeerTube?
 
 Not really. For instance, the demonstration server [https://peertube.cpy.re](https://peertube.cpy.re) has 2 vCore and 2GB of RAM and consumes on average:
index a3669353b109c26797df2a284a091e8ba8c80784..a9b4eb54a0d7e9eca4b323040dbbe194077bc3c6 100644 (file)
--- a/README.md
+++ b/README.md
 Be part of a network of multiple small federated, interoperable video hosting providers. Follow video creators and create videos. No vendor lock-in. All on a platform that is community-owned and ad-free.
 </p>
 
+<p align="center">
+  <strong>Developed with &#10084; by <a href="https://framasoft.org">Framasoft</a></strong>
+</p>
+
+<p align="center">
+  <a href="https://framasoft.org">
+    <img width="150px" src="http://lutim.cpy.re/Prd3ci7G.png" alt="Framasoft logo"/>
+  </a>
+</p>
+
 <p align="center">
   <strong>Client</strong>
 
@@ -123,7 +133,7 @@ You can also join the cheerful bunch that makes our community:
 
 * Chat<a name="contact"></a>:
   * IRC : **[#peertube on chat.freenode.net:6697](https://kiwiirc.com/client/irc.freenode.net/#peertube)**
-  * Matrix (bridged on the IRC channel) : **[#peertube:matrix.org](https://matrix.to/#/#peertube:matrix.org)**
+  * Matrix (bridged on IRC and [Discord](https://discord.gg/wj8DDUT)) : **[#peertube:matrix.org](https://matrix.to/#/#peertube:matrix.org)**
 * Forum:
   * Framacolibri: [https://framacolibri.org/c/peertube](https://framacolibri.org/c/peertube)
     
index 3761a641a140490d728e137c5a0c2d2a2e06dda1..31fc778876fb76a77c8fdaaada38ae3231e947f1 100644 (file)
@@ -1,6 +1,6 @@
 {
   "name": "peertube-client",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "private": true,
   "licence": "GPLv3",
   "author": {
     "setupTestFrameworkScriptFile": "<rootDir>/src/setupJest.ts"
   },
   "devDependencies": {
-    "@angular-devkit/build-angular": "~0.10.0",
-    "@angular/animations": "~7.0.2",
-    "@angular/cli": "~7.0.4",
-    "@angular/common": "~7.0.2",
-    "@angular/compiler": "~7.0.2",
-    "@angular/compiler-cli": "~7.0.2",
-    "@angular/core": "~7.0.2",
-    "@angular/forms": "~7.0.2",
-    "@angular/http": "~7.0.2",
-    "@angular/language-service": "~7.0.2",
-    "@angular/platform-browser": "~7.0.2",
-    "@angular/platform-browser-dynamic": "~7.0.2",
-    "@angular/router": "~7.0.2",
-    "@angular/service-worker": "~7.0.2",
+    "@angular-devkit/build-angular": "~0.11.1",
+    "@angular/animations": "~7.1.1",
+    "@angular/cli": "~7.1.1",
+    "@angular/common": "~7.1.1",
+    "@angular/compiler": "~7.1.1",
+    "@angular/compiler-cli": "~7.1.1",
+    "@angular/core": "~7.1.1",
+    "@angular/forms": "~7.1.1",
+    "@angular/http": "~7.1.1",
+    "@angular/language-service": "~7.1.1",
+    "@angular/platform-browser": "~7.1.1",
+    "@angular/platform-browser-dynamic": "~7.1.1",
+    "@angular/router": "~7.1.1",
+    "@angular/service-worker": "~7.1.1",
     "@angularclass/hmr": "^2.1.3",
     "@neos21/bootstrap3-glyphicons": "^1.0.1",
     "@ng-bootstrap/ng-bootstrap": "^4.0.0",
-    "@ngx-loading-bar/core": "^2.2.0",
-    "@ngx-loading-bar/http-client": "^2.2.0",
-    "@ngx-loading-bar/router": "^2.2.0",
+    "@ngx-loading-bar/core": "^3.0.0",
+    "@ngx-loading-bar/http-client": "^3.0.0",
+    "@ngx-loading-bar/router": "^3.0.0",
     "@ngx-meta/core": "^6.0.0-rc.1",
     "@ngx-translate/i18n-polyfill": "^1.0.0",
     "@types/core-js": "^2.5.0",
     "@types/markdown-it": "^0.0.5",
     "@types/node": "^10.9.2",
     "@types/sanitize-html": "1.18.0",
+    "@types/socket.io-client": "^1.4.32",
     "@types/video.js": "^7.2.5",
     "@types/webtorrent": "^0.98.4",
     "angular2-hotkeys": "^2.1.2",
-    "angular2-notifications": "^1.0.2",
     "awesome-typescript-loader": "5.2.1",
     "bootstrap": "^4.1.3",
     "buffer": "^5.1.0",
     "node-sass": "^4.9.3",
     "npm-font-source-sans-pro": "^1.0.2",
     "path-browserify": "^1.0.0",
-    "primeng": "^6.1.2",
+    "primeng": "^7.0.0",
     "process": "^0.11.10",
     "protractor": "^5.3.2",
     "purify-css": "^1.2.5",
     "sanitize-html": "^1.18.4",
     "sass-loader": "^7.1.0",
     "sass-resources-loader": "^2.0.0",
+    "socket.io-client": "^2.2.0",
     "stream-browserify": "^2.0.1",
     "stream-http": "^3.0.0",
     "terser-webpack-plugin": "^1.1.0",
     "videojs-contextmenu-ui": "^5.0.0",
     "videojs-dock": "^2.0.2",
     "videojs-hotkeys": "^0.2.21",
-    "webpack": "^4.17.1",
     "webpack-bundle-analyzer": "^3.0.2",
     "webpack-cli": "^3.0.8",
     "webtorrent": "https://github.com/webtorrent/webtorrent#e9b209c7970816fc29e0cc871157a4918d66001d",
index 5970cac0167835c9a40caffafc05a1f61f334cec..8c700752e8ca35c4c01be0309d42f10039f955cf 100644 (file)
@@ -1,39 +1,52 @@
-<div i18n class="about-instance-title">
-  About {{ instanceName }} instance
-</div>
+<div class="row">
+  <div class="col-md-12 col-xl-6">
+    <div class="about-instance-title">
+      <div i18n>About {{ instanceName }} instance</div>
 
-<div class="short-description">
-  <div>{{ shortDescription }}</div>
-</div>
+      <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div>
+    </div>
 
-<div class="description">
-  <div i18n class="section-title">Description</div>
+    <div class="short-description">
+      <div>{{ shortDescription }}</div>
+    </div>
 
-  <div [innerHTML]="descriptionHTML"></div>
-</div>
+    <div class="description">
+      <div i18n class="section-title">Description</div>
 
-<div class="terms" id="terms-section">
-  <div i18n class="section-title">Terms</div>
+      <div [innerHTML]="descriptionHTML"></div>
+    </div>
 
-  <div [innerHTML]="termsHTML"></div>
-</div>
+    <div class="terms" id="terms-section">
+      <div i18n class="section-title">Terms</div>
 
-<div class="signup">
-  <div i18n class="section-title">Signup</div>
+      <div [innerHTML]="termsHTML"></div>
+    </div>
 
-  <div *ngIf="isSignupAllowed">
-    <ng-container i18n>User registration is allowed and</ng-container>
+    <div class="signup">
+      <div i18n class="section-title">Signup</div>
 
-    <ng-container i18n *ngIf="userVideoQuota !== -1">
-      this instance provides a baseline quota of {{ userVideoQuota | bytes: 0 }} space for the videos of its users.
-    </ng-container>
+      <div *ngIf="isSignupAllowed">
+        <ng-container i18n>User registration is allowed and</ng-container>
 
-    <ng-container i18n *ngIf="userVideoQuota === -1">
-      this instance provides unlimited space for the videos of its users.
-    </ng-container>
+        <ng-container i18n *ngIf="userVideoQuota !== -1">
+          this instance provides a baseline quota of {{ userVideoQuota | bytes: 0 }} space for the videos of its users.
+        </ng-container>
+
+        <ng-container i18n *ngIf="userVideoQuota === -1">
+          this instance provides unlimited space for the videos of its users.
+        </ng-container>
+      </div>
+
+      <div i18n *ngIf="isSignupAllowed === false">
+        User registration is currently not allowed.
+      </div>
+    </div>
   </div>
 
-  <div i18n *ngIf="isSignupAllowed === false">
-    User registration is currently not allowed.
+  <div class="col-md-12 col-xl-6">
+    <label>Features found on this instance</label>
+    <my-instance-features-table></my-instance-features-table>
   </div>
-</div>
\ No newline at end of file
+</div>
+
+<my-contact-admin-modal #contactAdminModal></my-contact-admin-modal>
index b451e85aa452ea2c46911bc4c5c8eedc5d645653..75cf573220df28e4fe45792f11010147a7f4c3ea 100644 (file)
@@ -2,9 +2,19 @@
 @import '_mixins';
 
 .about-instance-title {
-  font-size: 20px;
-  font-weight: bold;
-  margin-bottom: 15px;
+  display: flex;
+  justify-content: space-between;
+
+  & > div {
+    font-size: 20px;
+    font-weight: bold;
+    margin-bottom: 15px;
+  }
+
+  & > .contact-admin {
+    @include peertube-button;
+    @include orange-button;
+  }
 }
 
 .section-title {
index 354f52ce7956366a4e6be1f4ecba17b3a435d802..a1b30fa8cfb94f2b95c79f0c69ed8b5ccf5ca836 100644 (file)
@@ -1,23 +1,26 @@
-import { Component, OnInit } from '@angular/core'
-import { ServerService } from '@app/core'
-import { MarkdownService } from '@app/videos/shared'
-import { NotificationsService } from 'angular2-notifications'
+import { Component, OnInit, ViewChild } from '@angular/core'
+import { Notifier, ServerService } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
+import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
+import { InstanceService } from '@app/shared/instance/instance.service'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-about-instance',
   templateUrl: './about-instance.component.html',
   styleUrls: [ './about-instance.component.scss' ]
 })
-
 export class AboutInstanceComponent implements OnInit {
+  @ViewChild('contactAdminModal') contactAdminModal: ContactAdminModalComponent
+
   shortDescription = ''
   descriptionHTML = ''
   termsHTML = ''
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private serverService: ServerService,
+    private instanceService: InstanceService,
     private markdownService: MarkdownService,
     private i18n: I18n
   ) {}
@@ -34,8 +37,12 @@ export class AboutInstanceComponent implements OnInit {
     return this.serverService.getConfig().signup.allowed
   }
 
+  get isContactFormEnabled () {
+    return this.serverService.getConfig().email.enabled && this.serverService.getConfig().contactForm.enabled
+  }
+
   ngOnInit () {
-    this.serverService.getAbout()
+    this.instanceService.getAbout()
       .subscribe(
         res => {
           this.shortDescription = res.instance.shortDescription
@@ -43,8 +50,12 @@ export class AboutInstanceComponent implements OnInit {
           this.termsHTML = this.markdownService.textMarkdownToHTML(res.instance.terms)
         },
 
-        err => this.notificationsService.error(this.i18n('Error getting about from server'), err)
+        () => this.notifier.error(this.i18n('Cannot get about information from server'))
       )
   }
 
+  openContactModal () {
+    return this.contactAdminModal.show()
+  }
+
 }
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.html b/client/src/app/+about/about-instance/contact-admin-modal.component.html
new file mode 100644 (file)
index 0000000..b2cbd08
--- /dev/null
@@ -0,0 +1,50 @@
+<ng-template #modal>
+  <div class="modal-header">
+    <h4 i18n class="modal-title">Contact {{ instanceName }} administrator</h4>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
+  </div>
+
+  <div class="modal-body">
+
+    <form novalidate [formGroup]="form" (ngSubmit)="sendForm()">
+      <div class="form-group">
+        <label i18n for="fromName">Your name</label>
+        <input
+          type="text" id="fromName"
+          formControlName="fromName" [ngClass]="{ 'input-error': formErrors.fromName }"
+        >
+        <div *ngIf="formErrors.fromName" class="form-error">{{ formErrors.fromName }}</div>
+      </div>
+
+      <div class="form-group">
+        <label i18n for="fromEmail">Your email</label>
+        <input
+          type="text" id="fromEmail"
+          formControlName="fromEmail" [ngClass]="{ 'input-error': formErrors['fromEmail'] }"
+        >
+        <div *ngIf="formErrors.fromEmail" class="form-error">{{ formErrors.fromEmail }}</div>
+      </div>
+
+      <div class="form-group">
+        <label i18n for="body">Your message</label>
+        <textarea id="body" formControlName="body" [ngClass]="{ 'input-error': formErrors['body'] }">
+        </textarea>
+        <div *ngIf="formErrors.body" class="form-error">{{ formErrors.body }}</div>
+      </div>
+
+      <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
+
+      <div class="form-group inputs">
+        <span i18n class="action-button action-button-cancel" (click)="hide()">
+          Cancel
+        </span>
+
+        <input
+          type="submit" i18n-value value="Submit" class="action-button-submit"
+          [disabled]="!form.valid"
+        >
+      </div>
+    </form>
+
+  </div>
+</ng-template>
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.scss b/client/src/app/+about/about-instance/contact-admin-modal.component.scss
new file mode 100644 (file)
index 0000000..260d778
--- /dev/null
@@ -0,0 +1,11 @@
+@import 'variables';
+@import 'mixins';
+
+input[type=text] {
+  @include peertube-input-text(340px);
+  display: block;
+}
+
+textarea {
+  @include peertube-textarea(100%, 200px);
+}
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
new file mode 100644 (file)
index 0000000..7d79c22
--- /dev/null
@@ -0,0 +1,77 @@
+import { Component, OnInit, ViewChild } from '@angular/core'
+import { Notifier, ServerService } from '@app/core'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
+import { FormReactive, InstanceValidatorsService } from '@app/shared'
+import { InstanceService } from '@app/shared/instance/instance.service'
+
+@Component({
+  selector: 'my-contact-admin-modal',
+  templateUrl: './contact-admin-modal.component.html',
+  styleUrls: [ './contact-admin-modal.component.scss' ]
+})
+export class ContactAdminModalComponent extends FormReactive implements OnInit {
+  @ViewChild('modal') modal: NgbModal
+
+  error: string
+
+  private openedModal: NgbModalRef
+
+  constructor (
+    protected formValidatorService: FormValidatorService,
+    private modalService: NgbModal,
+    private instanceValidatorsService: InstanceValidatorsService,
+    private instanceService: InstanceService,
+    private serverService: ServerService,
+    private notifier: Notifier,
+    private i18n: I18n
+  ) {
+    super()
+  }
+
+  get instanceName () {
+    return this.serverService.getConfig().instance.name
+  }
+
+  ngOnInit () {
+    this.buildForm({
+      fromName: this.instanceValidatorsService.FROM_NAME,
+      fromEmail: this.instanceValidatorsService.FROM_EMAIL,
+      body: this.instanceValidatorsService.BODY
+    })
+  }
+
+  show () {
+    this.openedModal = this.modalService.open(this.modal, { keyboard: false })
+  }
+
+  hide () {
+    this.form.reset()
+    this.error = undefined
+
+    this.openedModal.close()
+    this.openedModal = null
+  }
+
+  sendForm () {
+    const fromName = this.form.value['fromName']
+    const fromEmail = this.form.value[ 'fromEmail' ]
+    const body = this.form.value[ 'body' ]
+
+    this.instanceService.contactAdministrator(fromEmail, fromName, body)
+        .subscribe(
+          () => {
+            this.notifier.success(this.i18n('Your message has been sent.'))
+            this.hide()
+          },
+
+          err => {
+            this.error = err.status === 403
+              ? this.i18n('You already sent this form recently')
+              : err.message
+          }
+        )
+  }
+}
index ff6e8ef414a41848ae64d924b00d348f6393bf5e..9c6b29740dd4f833f044e1c84506709474c7aeff 100644 (file)
@@ -5,6 +5,7 @@ import { AboutComponent } from './about.component'
 import { SharedModule } from '../shared'
 import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
 import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
+import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
 
 @NgModule({
   imports: [
@@ -15,7 +16,8 @@ import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertub
   declarations: [
     AboutComponent,
     AboutInstanceComponent,
-    AboutPeertubeComponent
+    AboutPeertubeComponent,
+    ContactAdminModalComponent
   ],
 
   exports: [
index 6f3e6caa0487b77812817f0c1ef5229bd351eff0..13890a0ee89df4c109fdf8ddd2d683870ddd99b8 100644 (file)
@@ -1,9 +1,9 @@
-import { Component, OnInit, OnDestroy } from '@angular/core'
+import { Component, OnDestroy, OnInit } from '@angular/core'
 import { Account } from '@app/shared/account/account.model'
 import { AccountService } from '@app/shared/account/account.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { Subscription } from 'rxjs'
-import { MarkdownService } from '@app/videos/shared'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-account-about',
index e5c1f58b0a0ce01c7807693e6c6096befa6abfc4..13b634a0137f52190fac56ed9de54f8107a6bceb 100644 (file)
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { Location } from '@angular/common'
 import { immutableAssign } from '@app/shared/misc/utils'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../../core/auth'
 import { ConfirmService } from '../../core/confirm'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -13,6 +12,7 @@ import { tap } from 'rxjs/operators'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { Subscription } from 'rxjs'
 import { ScreenService } from '@app/shared/misc/screen.service'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-account-videos',
@@ -35,7 +35,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
     protected router: Router,
     protected route: ActivatedRoute,
     protected authService: AuthService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected confirmService: ConfirmService,
     protected location: Location,
     protected screenService: ScreenService,
index e19927d6b08b0a83204bb495f4848390f448ce42..e8339b78bd76d80a95a16de0d4607a6d751b18b1 100644 (file)
@@ -5,10 +5,9 @@ import { Account } from '@app/shared/account/account.model'
 import { RestExtractor, UserService } from '@app/shared'
 import { catchError, distinctUntilChanged, map, switchMap, tap } from 'rxjs/operators'
 import { Subscription } from 'rxjs'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier, RedirectService } from '@app/core'
 import { User, UserRight } from '../../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { AuthService, RedirectService } from '@app/core'
 
 @Component({
   templateUrl: './accounts.component.html',
@@ -24,11 +23,10 @@ export class AccountsComponent implements OnInit, OnDestroy {
     private route: ActivatedRoute,
     private userService: UserService,
     private accountService: AccountService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private restExtractor: RestExtractor,
     private redirectService: RedirectService,
-    private authService: AuthService,
-    private i18n: I18n
+    private authService: AuthService
   ) {}
 
   ngOnInit () {
@@ -43,7 +41,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
       .subscribe(
         account => this.account = account,
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -69,7 +67,7 @@ export class AccountsComponent implements OnInit, OnDestroy {
           .subscribe(
             user => this.user = user,
 
-            err => this.notificationsService.error(this.i18n('Error'), err.message)
+            err => this.notifier.error(err.message)
           )
     }
   }
index fd4d3d9c9e4181a24f487ff1f0e46f6b61deaf2b..52eb00d93f4accf0039190e53324ebb9e71d7abc 100644 (file)
 
         <div i18n class="inner-form-title">Instance</div>
 
-        <div class="form-group">
-          <label i18n for="instanceName">Name</label>
-          <input
-            type="text" id="instanceName"
-            formControlName="instanceName" [ngClass]="{ 'input-error': formErrors['instanceName'] }"
-          >
-          <div *ngIf="formErrors.instanceName" class="form-error">
-            {{ formErrors.instanceName }}
+        <ng-container formGroupName="instance">
+          <div class="form-group">
+            <label i18n for="instanceName">Name</label>
+            <input
+              type="text" id="instanceName"
+              formControlName="name" [ngClass]="{ 'input-error': formErrors.instance.name }"
+            >
+            <div *ngIf="formErrors.instance.name" class="form-error">{{ formErrors.instance.name }}</div>
           </div>
-        </div>
 
-        <div class="form-group">
-          <label i18n for="instanceShortDescription">Short description</label>
-          <textarea
-            id="instanceShortDescription" formControlName="instanceShortDescription"
-            [ngClass]="{ 'input-error': formErrors['instanceShortDescription'] }"
-          ></textarea>
-          <div *ngIf="formErrors.instanceShortDescription" class="form-error">
-            {{ formErrors.instanceShortDescription }}
+          <div class="form-group">
+            <label i18n for="instanceShortDescription">Short description</label>
+            <textarea
+              id="instanceShortDescription" formControlName="shortDescription"
+              [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }"
+            ></textarea>
+            <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div>
           </div>
-        </div>
 
-        <div class="form-group">
-          <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help>
-          <my-markdown-textarea
-            id="instanceDescription" formControlName="instanceDescription" textareaWidth="500px" [previewColumn]="true"
-            [classes]="{ 'input-error': formErrors['instanceDescription'] }"
-          ></my-markdown-textarea>
-          <div *ngIf="formErrors.instanceDescription" class="form-error">
-            {{ formErrors.instanceDescription }}
+          <div class="form-group">
+            <label i18n for="instanceDescription">Description</label><my-help helpType="markdownText"></my-help>
+            <my-markdown-textarea
+              id="instanceDescription" formControlName="description" textareaWidth="500px" [previewColumn]="true"
+              [classes]="{ 'input-error': formErrors['instance.description'] }"
+            ></my-markdown-textarea>
+            <div *ngIf="formErrors.instance.description" class="form-error">{{ formErrors.instance.description }}</div>
           </div>
-        </div>
 
-        <div class="form-group">
-          <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
-          <my-markdown-textarea
-            id="instanceTerms" formControlName="instanceTerms" textareaWidth="500px" [previewColumn]="true"
-            [ngClass]="{ 'input-error': formErrors['instanceTerms'] }"
-          ></my-markdown-textarea>
-          <div *ngIf="formErrors.instanceTerms" class="form-error">
-            {{ formErrors.instanceTerms }}
+          <div class="form-group">
+            <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help>
+            <my-markdown-textarea
+              id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true"
+              [ngClass]="{ 'input-error': formErrors['instance.terms'] }"
+            ></my-markdown-textarea>
+            <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div>
           </div>
-        </div>
 
-        <div class="form-group">
-          <label i18n for="instanceDefaultClientRoute">Default client route</label>
-          <div class="peertube-select-container">
-            <select id="instanceDefaultClientRoute" formControlName="instanceDefaultClientRoute">
-              <option i18n value="/videos/overview">Videos Overview</option>
-              <option i18n value="/videos/trending">Videos Trending</option>
-              <option i18n value="/videos/recently-added">Videos Recently Added</option>
-              <option i18n value="/videos/local">Local videos</option>
-            </select>
+          <div class="form-group">
+            <label i18n for="instanceDefaultClientRoute">Default client route</label>
+            <div class="peertube-select-container">
+              <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute">
+                <option i18n value="/videos/overview">Videos Overview</option>
+                <option i18n value="/videos/trending">Videos Trending</option>
+                <option i18n value="/videos/recently-added">Videos Recently Added</option>
+                <option i18n value="/videos/local">Local videos</option>
+              </select>
+            </div>
+            <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div>
           </div>
-          <div *ngIf="formErrors.instanceDefaultClientRoute" class="form-error">
-            {{ formErrors.instanceDefaultClientRoute }}
+
+          <div class="form-group">
+            <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
+            <my-help
+              helpType="custom" i18n-customHtml
+              customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."
+            ></my-help>
+
+            <div class="peertube-select-container">
+              <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy">
+                <option i18n value="do_not_list">Do not list</option>
+                <option i18n value="blur">Blur thumbnails</option>
+                <option i18n value="display">Display</option>
+              </select>
+            </div>
+            <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div>
           </div>
-        </div>
+        </ng-container>
 
-        <div class="form-group">
-          <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label>
-          <my-help
-            helpType="custom" i18n-customHtml
-            customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video."
-          ></my-help>
+        <div i18n class="inner-form-title">Signup</div>
 
-          <div class="peertube-select-container">
-            <select id="instanceDefaultNSFWPolicy" formControlName="instanceDefaultNSFWPolicy">
-              <option i18n value="do_not_list">Do not list</option>
-              <option i18n value="blur">Blur thumbnails</option>
-              <option i18n value="display">Display</option>
-            </select>
+        <ng-container formGroupName="signup">
+          <div class="form-group">
+            <my-peertube-checkbox
+              inputName="signupEnabled" formControlName="enabled"
+              i18n-labelText labelText="Signup enabled"
+            ></my-peertube-checkbox>
           </div>
-          <div *ngIf="formErrors.instanceDefaultNSFWPolicy" class="form-error">
-            {{ formErrors.instanceDefaultNSFWPolicy }}
+
+          <div class="form-group">
+            <my-peertube-checkbox *ngIf="isSignupEnabled()"
+              inputName="signupRequiresEmailVerification" formControlName="requiresEmailVerification"
+              i18n-labelText labelText="Signup requires email verification"
+            ></my-peertube-checkbox>
           </div>
-        </div>
 
-        <div i18n class="inner-form-title">Signup</div>
+          <div *ngIf="isSignupEnabled()" class="form-group">
+            <label i18n for="signupLimit">Signup limit</label>
+            <input
+              type="text" id="signupLimit"
+              formControlName="limit" [ngClass]="{ 'input-error': formErrors['signup.limit'] }"
+            >
+            <div *ngIf="formErrors.signup.limit" class="form-error">{{ formErrors.signup.limit }}</div>
+          </div>
+        </ng-container>
 
-        <div class="form-group">
-          <my-peertube-checkbox
-            inputName="signupEnabled" formControlName="signupEnabled"
-            i18n-labelText labelText="Signup enabled"
-          ></my-peertube-checkbox>
-        </div>
+        <div i18n class="inner-form-title">Users</div>
 
-        <div class="form-group">
-          <my-peertube-checkbox *ngIf="isSignupEnabled()"
-            inputName="signupRequiresEmailVerification" formControlName="signupRequiresEmailVerification"
-            i18n-labelText labelText="Signup requires email verification"
-          ></my-peertube-checkbox>
-        </div>
+        <ng-container formGroupName="user">
+          <div class="form-group">
+            <label i18n for="userVideoQuota">User default video quota</label>
+            <div class="peertube-select-container">
+              <select id="userVideoQuota" formControlName="videoQuota">
+                <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
+                  {{ videoQuotaOption.label }}
+                </option>
+              </select>
+            </div>
+            <div *ngIf="formErrors.user.videoQuota" class="form-error">{{ formErrors.user.videoQuota }}</div>
+          </div>
 
-        <div *ngIf="isSignupEnabled()" class="form-group">
-          <label i18n for="signupLimit">Signup limit</label>
-          <input
-            type="text" id="signupLimit"
-            formControlName="signupLimit" [ngClass]="{ 'input-error': formErrors['signupLimit'] }"
-          >
-          <div *ngIf="formErrors.signupLimit" class="form-error">
-            {{ formErrors.signupLimit }}
+          <div class="form-group">
+            <label i18n for="userVideoQuotaDaily">User default daily upload limit</label>
+            <div class="peertube-select-container">
+              <select id="userVideoQuotaDaily" formControlName="videoQuotaDaily">
+                <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
+                  {{ videoQuotaDailyOption.label }}
+                </option>
+              </select>
+            </div>
+            <div *ngIf="formErrors.user.videoQuotaDaily" class="form-error">{{ formErrors.user.videoQuotaDaily }}</div>
           </div>
-        </div>
+        </ng-container>
 
         <div i18n class="inner-form-title">Import</div>
 
-        <div class="form-group">
-          <my-peertube-checkbox
-            inputName="importVideosHttpEnabled" formControlName="importVideosHttpEnabled"
-            i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled"
-          ></my-peertube-checkbox>
-        </div>
+        <ng-container formGroupName="import">
+          <ng-container formGroupName="videos">
 
-        <div class="form-group">
-          <my-peertube-checkbox
-            inputName="importVideosTorrentEnabled" formControlName="importVideosTorrentEnabled"
-            i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled"
-          ></my-peertube-checkbox>
-        </div>
+            <div class="form-group" formGroupName="http">
+              <my-peertube-checkbox
+                inputName="importVideosHttpEnabled" formControlName="enabled"
+                i18n-labelText labelText="Video import with HTTP URL (i.e. YouTube) enabled"
+              ></my-peertube-checkbox>
+            </div>
+
+            <div class="form-group" formGroupName="torrent">
+              <my-peertube-checkbox
+                inputName="importVideosTorrentEnabled" formControlName="enabled"
+                i18n-labelText labelText="Video import with a torrent file or a magnet URI enabled"
+              ></my-peertube-checkbox>
+            </div>
+
+          </ng-container>
+        </ng-container>
 
         <div i18n class="inner-form-title">Administrator</div>
 
-        <div class="form-group">
+        <div class="form-group" formGroupName="admin">
           <label i18n for="adminEmail">Admin email</label>
           <input
             type="text" id="adminEmail"
-            formControlName="adminEmail" [ngClass]="{ 'input-error': formErrors['adminEmail'] }"
+            formControlName="email" [ngClass]="{ 'input-error': formErrors['admin.email'] }"
           >
-          <div *ngIf="formErrors.adminEmail" class="form-error">
-            {{ formErrors.adminEmail }}
-          </div>
+          <div *ngIf="formErrors.admin.email" class="form-error">{{ formErrors.admin.email }}</div>
         </div>
 
-        <div i18n class="inner-form-title">Users</div>
-
-        <div class="form-group">
-          <label i18n for="userVideoQuota">User default video quota</label>
-          <div class="peertube-select-container">
-            <select id="userVideoQuota" formControlName="userVideoQuota">
-              <option *ngFor="let videoQuotaOption of videoQuotaOptions" [value]="videoQuotaOption.value">
-                {{ videoQuotaOption.label }}
-              </option>
-            </select>
-          </div>
-          <div *ngIf="formErrors.userVideoQuota" class="form-error">
-            {{ formErrors.userVideoQuota }}
-          </div>
+        <div class="form-group" formGroupName="contactForm">
+          <my-peertube-checkbox
+            inputName="enableContactForm" formControlName="enabled"
+            i18n-labelText labelText="Enable contact form"
+          ></my-peertube-checkbox>
         </div>
 
-        <div class="form-group">
-          <label i18n for="userVideoQuotaDaily">User default daily upload limit</label>
-          <div class="peertube-select-container">
-            <select id="userVideoQuotaDaily" formControlName="userVideoQuotaDaily">
-              <option *ngFor="let videoQuotaDailyOption of videoQuotaDailyOptions" [value]="videoQuotaDailyOption.value">
-                {{ videoQuotaDailyOption.label }}
-              </option>
-            </select>
-          </div>
-          <div *ngIf="formErrors.userVideoQuotaDaily" class="form-error">
-            {{ formErrors.userVideoQuotaDaily }}
-          </div>
-        </div>
       </ng-template>
     </ngb-tab>
 
       <ng-template ngbTabContent>
         <div i18n class="inner-form-title">Twitter</div>
 
-        <div class="form-group">
-          <label i18n for="signupLimit">Your Twitter username</label>
-          <my-help
-            helpType="custom" i18n-customHtml
-            customHtml="Indicates the Twitter account for the website or platform on which the content was published."
-          ></my-help>
-          <input
-            type="text" id="servicesTwitterUsername"
-            formControlName="servicesTwitterUsername" [ngClass]="{ 'input-error': formErrors['servicesTwitterUsername'] }"
-          >
-          <div *ngIf="formErrors.servicesTwitterUsername" class="form-error">
-            {{ formErrors.servicesTwitterUsername }}
-          </div>
-        </div>
+        <ng-container formGroupName="services">
+          <ng-container formGroupName="twitter">
+
+            <div class="form-group">
+              <label i18n for="signupLimit">Your Twitter username</label>
+              <my-help
+                helpType="custom" i18n-customHtml
+                customHtml="Indicates the Twitter account for the website or platform on which the content was published."
+              ></my-help>
+              <input
+                type="text" id="servicesTwitterUsername"
+                formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }"
+              >
+              <div *ngIf="formErrors.services.twitter.username" class="form-error">{{ formErrors.services.twitter.username }}</div>
+            </div>
+
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="servicesTwitterWhitelisted" formControlName="whitelisted"
+                i18n-labelText labelText="Instance whitelisted by Twitter"
+                i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
+        If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br />
+        Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted."
+              ></my-peertube-checkbox>
+            </div>
+
+          </ng-container>
+        </ng-container>
 
-        <div class="form-group">
-          <my-peertube-checkbox
-            inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted"
-            i18n-labelText labelText="Instance whitelisted by Twitter"
-            i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
-    If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br />
-    Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted."
-          ></my-peertube-checkbox>
-        </div>
     </ng-template>
     </ngb-tab>
 
 
         <div i18n class="inner-form-title">Transcoding</div>
 
-        <div class="form-group">
-          <my-peertube-checkbox
-            inputName="transcodingEnabled" formControlName="transcodingEnabled"
-            i18n-labelText labelText="Transcoding enabled"
-            i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!"
-          ></my-peertube-checkbox>
-        </div>
+        <ng-container formGroupName="transcoding">
+          <div class="form-group">
+            <my-peertube-checkbox
+              inputName="transcodingEnabled" formControlName="enabled"
+              i18n-labelText labelText="Transcoding enabled"
+              i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!"
+            ></my-peertube-checkbox>
+          </div>
 
-        <ng-template [ngIf]="isTranscodingEnabled()">
+          <ng-container *ngIf="isTranscodingEnabled()">
 
-          <div class="form-group">
-            <label i18n for="transcodingThreads">Transcoding threads</label>
-            <div class="peertube-select-container">
-              <select id="transcodingThreads" formControlName="transcodingThreads">
-                <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value">
-                  {{ transcodingThreadOption.label }}
-                </option>
-              </select>
+            <div class="form-group">
+              <my-peertube-checkbox
+                inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions"
+                i18n-labelText labelText="Allow additional extensions"
+                i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos"
+              ></my-peertube-checkbox>
             </div>
-            <div *ngIf="formErrors.transcodingThreads" class="form-error">
-              {{ formErrors.transcodingThreads }}
+
+            <div class="form-group">
+              <label i18n for="transcodingThreads">Transcoding threads</label>
+              <div class="peertube-select-container">
+                <select id="transcodingThreads" formControlName="threads">
+                  <option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value">
+                    {{ transcodingThreadOption.label }}
+                  </option>
+                </select>
+              </div>
+              <div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div>
             </div>
-          </div>
 
-          <div class="form-group" *ngFor="let resolution of resolutions">
-            <my-peertube-checkbox
-              [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)"
-              i18n-labelText labelText="Resolution {{resolution}} enabled"
-            ></my-peertube-checkbox>
-          </div>
-        </ng-template>
+            <ng-container formGroupName="resolutions">
+              <div class="form-group" *ngFor="let resolution of resolutions">
+                <my-peertube-checkbox
+                  [inputName]="getResolutionKey(resolution)" [formControlName]="resolution"
+                  i18n-labelText labelText="Resolution {{resolution}} enabled"
+                ></my-peertube-checkbox>
+              </div>
+            </ng-container>
+
+          </ng-container>
+        </ng-container>
 
         <div i18n class="inner-form-title">
           Cache
           ></my-help>
         </div>
 
-        <div class="form-group">
-          <label i18n for="cachePreviewsSize">Previews cache size</label>
-          <input
-            type="text" id="cachePreviewsSize"
-            formControlName="cachePreviewsSize" [ngClass]="{ 'input-error': formErrors['cachePreviewsSize'] }"
-          >
-          <div *ngIf="formErrors.cachePreviewsSize" class="form-error">
-            {{ formErrors.cachePreviewsSize }}
+        <ng-container formGroupName="cache">
+          <div class="form-group" formGroupName="previews">
+            <label i18n for="cachePreviewsSize">Previews cache size</label>
+            <input
+              type="text" id="cachePreviewsSize"
+              formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.previews.size'] }"
+            >
+            <div *ngIf="formErrors.cache.previews.size" class="form-error">{{ formErrors.cache.previews.size }}</div>
           </div>
-        </div>
 
-        <div class="form-group">
-          <label i18n for="cachePreviewsSize">Video captions cache size</label>
-          <input
-            type="text" id="cacheCaptionsSize"
-            formControlName="cacheCaptionsSize" [ngClass]="{ 'input-error': formErrors['cacheCaptionsSize'] }"
-          >
-          <div *ngIf="formErrors.cacheCaptionsSize" class="form-error">
-            {{ formErrors.cacheCaptionsSize }}
+          <div class="form-group" formGroupName="captions">
+            <label i18n for="cacheCaptionsSize">Video captions cache size</label>
+            <input
+              type="text" id="cacheCaptionsSize"
+              formControlName="size" [ngClass]="{ 'input-error': formErrors['cache.captions.size'] }"
+            >
+            <div *ngIf="formErrors.cache.captions.size" class="form-error">{{ formErrors.cache.captions.size }}</div>
           </div>
-        </div>
+        </ng-container>
 
         <div i18n class="inner-form-title">Customizations</div>
 
-        <div class="form-group">
-          <label i18n for="customizationJavascript">JavaScript</label>
-          <my-help
-            helpType="custom" i18n-customHtml
-            customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>"
-          ></my-help>
-          <textarea
-            id="customizationJavascript" formControlName="customizationJavascript"
-            [ngClass]="{ 'input-error': formErrors['customizationJavascript'] }"
-          ></textarea>
-          <div *ngIf="formErrors.customizationJavascript" class="form-error">
-            {{ formErrors.customizationJavascript }}
-          </div>
-        </div>
+        <ng-container formGroupName="instance">
+          <ng-container formGroupName="customizations">
+            <div class="form-group">
+              <label i18n for="customizationJavascript">JavaScript</label>
+              <my-help
+                helpType="custom" i18n-customHtml
+                customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>"
+              ></my-help>
+              <textarea
+                id="customizationJavascript" formControlName="javascript"
+                [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }"
+              ></textarea>
+              <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div>
+            </div>
+
+            <div class="form-group">
+              <label for="customizationCSS">CSS</label>
+              <my-help
+                  helpType="custom"
+                  i18n-customHtml
+                  customHtml="
+                    Write directly CSS code. Example:<br />
+                    <pre>
+          body {{ '{' }}
+            background-color: red;
+          {{ '}' }}
+                    </pre>
+
+                    Prepend with <em>#custom-css</em> to override styles. Example:
+                    <pre>
+          #custom-css .logged-in-email {{ '{' }}
+            color: red;
+          {{ '}' }}
+                    </pre>
+                  "
+              ></my-help>
+              <textarea
+                id="customizationCSS" formControlName="css"
+                [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }"
+              ></textarea>
+              <div *ngIf="formErrors.instance.customizations.css" class="form-error">{{ formErrors.instance.customizations.css }}</div>
+            </div>
+          </ng-container>
+        </ng-container>
 
-        <div class="form-group">
-          <label for="customizationCSS">CSS</label>
-          <my-help
-              helpType="custom"
-              i18n-customHtml
-              customHtml="
-                Write directly CSS code. Example:<br />
-                <pre>
-      body {{ '{' }}
-        background-color: red;
-      {{ '}' }}
-                </pre>
-
-                Prepend with <em>#custom-css</em> to override styles. Example:
-                <pre>
-      #custom-css .logged-in-email {{ '{' }}
-        color: red;
-      {{ '}' }}
-                </pre>
-              "
-          ></my-help>
-          <textarea
-            id="customizationCSS" formControlName="customizationCSS"
-            [ngClass]="{ 'input-error': formErrors['customizationCSS'] }"
-          ></textarea>
-          <div *ngIf="formErrors.customizationCSS" class="form-error">
-            {{ formErrors.customizationCSS }}
-          </div>
-        </div>
       </ng-template>
     </ngb-tab>
   </ngb-tabset>
index f48b6fc1aa1ac91038c71c31d5cfa16871f568f4..654a076b0d242088c3bc2567ac4eb7ae3990a90b 100644 (file)
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'
 import { ConfigService } from '@app/+admin/config/shared/config.service'
 import { ServerService } from '@app/core/server/server.service'
 import { CustomConfigValidatorsService, FormReactive, UserValidatorsService } from '@app/shared'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
@@ -18,14 +18,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
   resolutions: string[] = []
   transcodingThreadOptions: { label: string, value: number }[] = []
 
-  private oldCustomJavascript: string
-  private oldCustomCSS: string
-
   constructor (
     protected formValidatorService: FormValidatorService,
     private customConfigValidatorsService: CustomConfigValidatorsService,
     private userValidatorsService: UserValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private configService: ConfigService,
     private serverService: ServerService,
     private i18n: I18n
@@ -58,40 +55,78 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
   }
 
   getResolutionKey (resolution: string) {
-    return 'transcodingResolution' + resolution
+    return 'transcoding.resolutions.' + resolution
   }
 
   ngOnInit () {
-    const formGroupData: { [key: string]: any } = {
-      instanceName: this.customConfigValidatorsService.INSTANCE_NAME,
-      instanceShortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION,
-      instanceDescription: null,
-      instanceTerms: null,
-      instanceDefaultClientRoute: null,
-      instanceDefaultNSFWPolicy: null,
-      servicesTwitterUsername: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME,
-      servicesTwitterWhitelisted: null,
-      cachePreviewsSize: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE,
-      cacheCaptionsSize: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE,
-      signupEnabled: null,
-      signupLimit: this.customConfigValidatorsService.SIGNUP_LIMIT,
-      signupRequiresEmailVerification: null,
-      importVideosHttpEnabled: null,
-      importVideosTorrentEnabled: null,
-      adminEmail: this.customConfigValidatorsService.ADMIN_EMAIL,
-      userVideoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
-      userVideoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY,
-      transcodingThreads: this.customConfigValidatorsService.TRANSCODING_THREADS,
-      transcodingEnabled: null,
-      customizationJavascript: null,
-      customizationCSS: null
+    const formGroupData: { [key in keyof CustomConfig ]: any } = {
+      instance: {
+        name: this.customConfigValidatorsService.INSTANCE_NAME,
+        shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION,
+        description: null,
+        terms: null,
+        defaultClientRoute: null,
+        defaultNSFWPolicy: null,
+        customizations: {
+          javascript: null,
+          css: null
+        }
+      },
+      services: {
+        twitter: {
+          username: this.customConfigValidatorsService.SERVICES_TWITTER_USERNAME,
+          whitelisted: null
+        }
+      },
+      cache: {
+        previews: {
+          size: this.customConfigValidatorsService.CACHE_PREVIEWS_SIZE
+        },
+        captions: {
+          size: this.customConfigValidatorsService.CACHE_CAPTIONS_SIZE
+        }
+      },
+      signup: {
+        enabled: null,
+        limit: this.customConfigValidatorsService.SIGNUP_LIMIT,
+        requiresEmailVerification: null
+      },
+      import: {
+        videos: {
+          http: {
+            enabled: null
+          },
+          torrent: {
+            enabled: null
+          }
+        }
+      },
+      admin: {
+        email: this.customConfigValidatorsService.ADMIN_EMAIL
+      },
+      contactForm: {
+        enabled: null
+      },
+      user: {
+        videoQuota: this.userValidatorsService.USER_VIDEO_QUOTA,
+        videoQuotaDaily: this.userValidatorsService.USER_VIDEO_QUOTA_DAILY
+      },
+      transcoding: {
+        enabled: null,
+        threads: this.customConfigValidatorsService.TRANSCODING_THREADS,
+        allowAdditionalExtensions: null,
+        resolutions: {}
+      }
     }
 
-    const defaultValues: BuildFormDefaultValues = {}
+    const defaultValues = {
+      transcoding: {
+        resolutions: {}
+      }
+    }
     for (const resolution of this.resolutions) {
-      const key = this.getResolutionKey(resolution)
-      defaultValues[key] = 'false'
-      formGroupData[key] = null
+      defaultValues.transcoding.resolutions[resolution] = 'false'
+      formGroupData.transcoding.resolutions[resolution] = null
     }
 
     this.buildForm(formGroupData)
@@ -101,90 +136,25 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
         res => {
           this.customConfig = res
 
-          this.oldCustomCSS = this.customConfig.instance.customizations.css
-          this.oldCustomJavascript = this.customConfig.instance.customizations.javascript
-
           this.updateForm()
           // Force form validation
           this.forceCheck()
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
   isTranscodingEnabled () {
-    return this.form.value['transcodingEnabled'] === true
+    return this.form.value['transcoding']['enabled'] === true
   }
 
   isSignupEnabled () {
-    return this.form.value['signupEnabled'] === true
+    return this.form.value['signup']['enabled'] === true
   }
 
   async formValidated () {
-    const data: CustomConfig = {
-      instance: {
-        name: this.form.value['instanceName'],
-        shortDescription: this.form.value['instanceShortDescription'],
-        description: this.form.value['instanceDescription'],
-        terms: this.form.value['instanceTerms'],
-        defaultClientRoute: this.form.value['instanceDefaultClientRoute'],
-        defaultNSFWPolicy: this.form.value['instanceDefaultNSFWPolicy'],
-        customizations: {
-          javascript: this.form.value['customizationJavascript'],
-          css: this.form.value['customizationCSS']
-        }
-      },
-      services: {
-        twitter: {
-          username: this.form.value['servicesTwitterUsername'],
-          whitelisted: this.form.value['servicesTwitterWhitelisted']
-        }
-      },
-      cache: {
-        previews: {
-          size: this.form.value['cachePreviewsSize']
-        },
-        captions: {
-          size: this.form.value['cacheCaptionsSize']
-        }
-      },
-      signup: {
-        enabled: this.form.value['signupEnabled'],
-        limit: this.form.value['signupLimit'],
-        requiresEmailVerification: this.form.value['signupRequiresEmailVerification']
-      },
-      admin: {
-        email: this.form.value['adminEmail']
-      },
-      user: {
-        videoQuota: this.form.value['userVideoQuota'],
-        videoQuotaDaily: this.form.value['userVideoQuotaDaily']
-      },
-      transcoding: {
-        enabled: this.form.value['transcodingEnabled'],
-        threads: this.form.value['transcodingThreads'],
-        resolutions: {
-          '240p': this.form.value[this.getResolutionKey('240p')],
-          '360p': this.form.value[this.getResolutionKey('360p')],
-          '480p': this.form.value[this.getResolutionKey('480p')],
-          '720p': this.form.value[this.getResolutionKey('720p')],
-          '1080p': this.form.value[this.getResolutionKey('1080p')]
-        }
-      },
-      import: {
-        videos: {
-          http: {
-            enabled: this.form.value['importVideosHttpEnabled']
-          },
-          torrent: {
-            enabled: this.form.value['importVideosTorrentEnabled']
-          }
-        }
-      }
-    }
-
-    this.configService.updateCustomConfig(data)
+    this.configService.updateCustomConfig(this.form.value)
       .subscribe(
         res => {
           this.customConfig = res
@@ -194,45 +164,15 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit {
 
           this.updateForm()
 
-          this.notificationsService.success(this.i18n('Success'), this.i18n('Configuration updated.'))
+          this.notifier.success(this.i18n('Configuration updated.'))
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
   private updateForm () {
-    const data: { [key: string]: any } = {
-      instanceName: this.customConfig.instance.name,
-      instanceShortDescription: this.customConfig.instance.shortDescription,
-      instanceDescription: this.customConfig.instance.description,
-      instanceTerms: this.customConfig.instance.terms,
-      instanceDefaultClientRoute: this.customConfig.instance.defaultClientRoute,
-      instanceDefaultNSFWPolicy: this.customConfig.instance.defaultNSFWPolicy,
-      servicesTwitterUsername: this.customConfig.services.twitter.username,
-      servicesTwitterWhitelisted: this.customConfig.services.twitter.whitelisted,
-      cachePreviewsSize: this.customConfig.cache.previews.size,
-      cacheCaptionsSize: this.customConfig.cache.captions.size,
-      signupEnabled: this.customConfig.signup.enabled,
-      signupLimit: this.customConfig.signup.limit,
-      signupRequiresEmailVerification: this.customConfig.signup.requiresEmailVerification,
-      adminEmail: this.customConfig.admin.email,
-      userVideoQuota: this.customConfig.user.videoQuota,
-      userVideoQuotaDaily: this.customConfig.user.videoQuotaDaily,
-      transcodingThreads: this.customConfig.transcoding.threads,
-      transcodingEnabled: this.customConfig.transcoding.enabled,
-      customizationJavascript: this.customConfig.instance.customizations.javascript,
-      customizationCSS: this.customConfig.instance.customizations.css,
-      importVideosHttpEnabled: this.customConfig.import.videos.http.enabled,
-      importVideosTorrentEnabled: this.customConfig.import.videos.torrent.enabled
-    }
-
-    for (const resolution of this.resolutions) {
-      const key = this.getResolutionKey(resolution)
-      data[key] = this.customConfig.transcoding.resolutions[resolution]
-    }
-
-    this.form.patchValue(data)
+    this.form.patchValue(this.customConfig)
   }
 
 }
index 4a25b7ff32075c7c4d6d52517d1644ec26a17c6f..9a8848bfb2eb48aff92f445d5f6d34d84e6b61ca 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, OnInit } from '@angular/core'
 
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { SortMeta } from 'primeng/primeng'
 import { ActorFollow } from '../../../../../../shared/models/actors/follow.model'
 import { RestPagination, RestTable } from '../../../shared'
@@ -20,7 +20,7 @@ export class FollowersListComponent extends RestTable implements OnInit {
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private followService: FollowService,
     private i18n: I18n
   ) {
@@ -32,14 +32,14 @@ export class FollowersListComponent extends RestTable implements OnInit {
   }
 
   protected loadData () {
-    this.followService.getFollowers(this.pagination, this.sort)
+    this.followService.getFollowers(this.pagination, this.sort, this.search)
                       .subscribe(
                         resultList => {
                           this.followers = resultList.data
                           this.totalRecords = resultList.total
                         },
 
-                        err => this.notificationsService.error(this.i18n('Error'), err.message)
+                        err => this.notifier.error(err.message)
                       )
   }
 }
index bd9cc022ba466967fe0712d747faccfd0779bea9..2bb2497466d21c05c70182b5ca9f81c6ac3aa4b4 100644 (file)
@@ -1,6 +1,6 @@
 import { Component } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { ConfirmService } from '../../../core'
 import { validateHost } from '../../../shared'
 import { FollowService } from '../shared'
@@ -18,7 +18,7 @@ export class FollowingAddComponent {
 
   constructor (
     private router: Router,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private followService: FollowService,
     private i18n: I18n
@@ -64,12 +64,12 @@ export class FollowingAddComponent {
 
     this.followService.follow(hosts).subscribe(
       () => {
-        this.notificationsService.success(this.i18n('Success'), this.i18n('Follow request(s) sent!'))
+        this.notifier.success(this.i18n('Follow request(s) sent!'))
 
         setTimeout(() => this.router.navigate([ '/admin/follows/following-list' ]), 500)
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
index 9b7029f755f425dd0695072ae6abd4bdcd0bbe96..4517a721e1494471e4874d52def6e35e04c6319f 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { SortMeta } from 'primeng/primeng'
 import { ActorFollow } from '../../../../../../shared/models/actors/follow.model'
 import { ConfirmService } from '../../../core/confirm/confirm.service'
@@ -20,7 +20,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private followService: FollowService,
     private i18n: I18n
@@ -41,14 +41,11 @@ export class FollowingListComponent extends RestTable implements OnInit {
 
     this.followService.unfollow(follow).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('You are not following {{host}} anymore.', { host: follow.following.host })
-        )
+        this.notifier.success(this.i18n('You are not following {{host}} anymore.', { host: follow.following.host }))
         this.loadData()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
@@ -60,7 +57,7 @@ export class FollowingListComponent extends RestTable implements OnInit {
                           this.totalRecords = resultList.total
                         },
 
-                        err => this.notificationsService.error(this.i18n('Error'), err.message)
+                        err => this.notifier.error(err.message)
                       )
   }
 }
index 6d77a0eb4364f26eab40e5f6a00d50c3e001fd32..fa1da26bfccdb93e1ede0845250726bf49afdc14 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { RedundancyService } from '@app/+admin/follows/shared/redundancy.service'
 
@@ -13,24 +13,21 @@ export class RedundancyCheckboxComponent {
   @Input() host: string
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private redundancyService: RedundancyService,
     private i18n: I18n
   ) { }
 
   updateRedundancyState () {
     this.redundancyService.updateRedundancy(this.host, this.redundancyAllowed)
-      .subscribe(
-        () => {
-          const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled')
+        .subscribe(
+          () => {
+            const stateLabel = this.redundancyAllowed ? this.i18n('enabled') : this.i18n('disabled')
 
-          this.notificationsService.success(
-            this.i18n('Success'),
-            this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel })
-          )
-        },
+            this.notifier.success(this.i18n('Redundancy for {{host}} is {{stateLabel}}', { host: this.host, stateLabel }))
+          },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
-      )
+          err => this.notifier.error(err.message)
+        )
   }
 }
index 44778ab5668253ef8ade3897ffcffc521992523d..b265e1dd639bb3fb9f8de12631c0b52c948463da 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, OnInit } from '@angular/core'
 import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { SortMeta } from 'primeng/primeng'
 import { Job } from '../../../../../../shared/index'
 import { JobState } from '../../../../../../shared/models'
@@ -25,7 +25,7 @@ export class JobsListComponent extends RestTable implements OnInit {
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private jobsService: JobService,
     private i18n: I18n
   ) {
@@ -53,7 +53,7 @@ export class JobsListComponent extends RestTable implements OnInit {
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
index 3f243aee4c6ad0c057e7dcfeca862370da14c371..032bf745a3c636c2311b14ac60527978f28a559f 100644 (file)
@@ -1,9 +1,9 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { RestPagination, RestTable } from '@app/shared'
 import { SortMeta } from 'primeng/components/common/sortmeta'
-import { BlocklistService, AccountBlock } from '@app/shared/blocklist'
+import { AccountBlock, BlocklistService } from '@app/shared/blocklist'
 
 @Component({
   selector: 'my-instance-account-blocklist',
@@ -18,7 +18,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private blocklistService: BlocklistService,
     private i18n: I18n
   ) {
@@ -35,8 +35,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn
     this.blocklistService.unblockAccountByInstance(blockedAccount)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
+            this.notifier.success(
               this.i18n('Account {{nameWithHost}} unmuted by your instance.', { nameWithHost: blockedAccount.nameWithHost })
             )
 
@@ -53,7 +52,7 @@ export class InstanceAccountBlocklistComponent extends RestTable implements OnIn
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index 130009dc73ec8093bd2ed5548344f980040cbdc1..db3dfcd1c0fb7d744619e74c0475ff2341656d12 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { RestPagination, RestTable } from '@app/shared'
 import { SortMeta } from 'primeng/components/common/sortmeta'
@@ -19,7 +19,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private blocklistService: BlocklistService,
     private i18n: I18n
   ) {
@@ -36,10 +36,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni
     this.blocklistService.unblockServerByInstance(host)
       .subscribe(
         () => {
-          this.notificationsService.success(
-            this.i18n('Success'),
-            this.i18n('Instance {{host}} unmuted by your instance.', { host })
-          )
+          this.notifier.success(this.i18n('Instance {{host}} unmuted by your instance.', { host }))
 
           this.loadData()
         }
@@ -54,7 +51,7 @@ export class InstanceServerBlocklistComponent extends RestTable implements OnIni
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index 02ccfc8cab621ee215f58da1e5e6e8512fb9ac0b..13b019c5bdd34dd506e900dd2dbaf0f156c8f18f 100644 (file)
@@ -10,6 +10,7 @@
   font-weight: $font-semibold;
   min-width: 200px;
   display: inline-block;
+  vertical-align: top;
 }
 
 .moderation-expanded-text {
index 3a8424f689fa47951d10babb430ad89d7fc24233..303a788d2e4b5970d019c2c87b7c4ebe709c2324 100644 (file)
@@ -1,7 +1,8 @@
 <ng-template #modal>
   <div class="modal-header">
     <h4 i18n class="modal-title">Moderation comment</h4>
-    <span class="close" aria-hidden="true" (click)="hideModerationCommentModal()"></span>
+
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body">
         </div>
       </div>
 
-      <div i18n>
+      <div class="form-group" i18n>
         This comment can only be seen by you or the other moderators.
       </div>
 
       <div class="form-group inputs">
-        <span i18n class="action-button action-button-cancel" (click)="hideModerationCommentModal()">Cancel</span>
+        <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span>
 
         <input
           type="submit" i18n-value value="Update this comment" class="action-button-submit"
@@ -29,4 +30,4 @@
     </form>
   </div>
 
-</ng-template>
\ No newline at end of file
+</ng-template>
index 34ab384d134792afa5fb2db47f9d5194f540134a..f915978ee99e6ce20ac9cffed33306aaad363291 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { FormReactive, VideoAbuseService, VideoAbuseValidatorsService } from '../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
@@ -22,7 +22,7 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
   constructor (
     protected formValidatorService: FormValidatorService,
     private modalService: NgbModal,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private videoAbuseService: VideoAbuseService,
     private videoAbuseValidatorsService: VideoAbuseValidatorsService,
     private i18n: I18n
@@ -45,29 +45,26 @@ export class ModerationCommentModalComponent extends FormReactive implements OnI
     })
   }
 
-  hideModerationCommentModal () {
+  hide () {
     this.abuseToComment = undefined
     this.openedModal.close()
     this.form.reset()
   }
 
   async banUser () {
-    const moderationComment: string = this.form.value['moderationComment']
+    const moderationComment: string = this.form.value[ 'moderationComment' ]
 
     this.videoAbuseService.updateVideoAbuse(this.abuseToComment, { moderationComment })
-      .subscribe(
-        () => {
-          this.notificationsService.success(
-            this.i18n('Success'),
-            this.i18n('Comment updated.')
-          )
+        .subscribe(
+          () => {
+            this.notifier.success(this.i18n('Comment updated.'))
 
-          this.commentUpdated.emit(moderationComment)
-          this.hideModerationCommentModal()
-        },
+            this.commentUpdated.emit(moderationComment)
+            this.hide()
+          },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
-      )
+          err => this.notifier.error(err.message)
+        )
   }
 
 }
index 0374b70ef26c7c0e06fe44298565721e71bb19b8..05b549de68497cfbe75c634f275a521c48b2f72c 100644 (file)
@@ -41,7 +41,7 @@
       </td>
 
       <td class="action-cell">
-        <my-action-dropdown i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown>
+        <my-action-dropdown placement="bottom-right" i18n-label label="Actions" [actions]="videoAbuseActions" [entry]="videoAbuse"></my-action-dropdown>
       </td>
     </tr>
   </ng-template>
         <td class="moderation-expanded" colspan="6">
           <div>
             <span i18n class="moderation-expanded-label">Reason:</span>
-            <span class="moderation-expanded-text">{{ videoAbuse.reason }}</span>
+            <span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.reason)"></span>
           </div>
           <div *ngIf="videoAbuse.moderationComment">
             <span i18n class="moderation-expanded-label">Moderation comment:</span>
-            <span class="moderation-expanded-text">{{ videoAbuse.moderationComment }}</span>
+            <span class="moderation-expanded-text" [innerHTML]="toHtml(videoAbuse.moderationComment)"></span>
           </div>
         </td>
       </tr>
   </ng-template>
 </p-table>
 
-<my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal>
\ No newline at end of file
+<my-moderation-comment-modal #moderationCommentModal (commentUpdated)="onModerationCommentUpdated()"></my-moderation-comment-modal>
index 7a219c846addead14cab409ad6ac3e9b678d74b4..00c8716599692146b1a4a6a29466eb6c5b67279b 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, OnInit, ViewChild } from '@angular/core'
 import { Account } from '../../../shared/account/account.model'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { SortMeta } from 'primeng/components/common/sortmeta'
 import { VideoAbuse, VideoAbuseState } from '../../../../../../shared'
 import { RestPagination, RestTable, VideoAbuseService } from '../../../shared'
@@ -9,6 +9,7 @@ import { DropdownAction } from '../../../shared/buttons/action-dropdown.componen
 import { ConfirmService } from '../../../core/index'
 import { ModerationCommentModalComponent } from './moderation-comment-modal.component'
 import { Video } from '../../../shared/video/video.model'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-abuse-list',
@@ -27,10 +28,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
   videoAbuseActions: DropdownAction<VideoAbuse>[] = []
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private videoAbuseService: VideoAbuseService,
     private confirmService: ConfirmService,
-    private i18n: I18n
+    private i18n: I18n,
+    private markdownRenderer: MarkdownService
   ) {
     super()
 
@@ -90,14 +92,11 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
 
     this.videoAbuseService.removeVideoAbuse(videoAbuse).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('Abuse deleted.')
-        )
+        this.notifier.success(this.i18n('Abuse deleted.'))
         this.loadData()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
@@ -106,11 +105,15 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
       .subscribe(
         () => this.loadData(),
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
 
   }
 
+  toHtml (text: string) {
+    return this.markdownRenderer.textMarkdownToHTML(text)
+  }
+
   protected loadData () {
     return this.videoAbuseService.getVideoAbuses(this.pagination, this.sort)
                .subscribe(
@@ -119,7 +122,7 @@ export class VideoAbuseListComponent extends RestTable implements OnInit {
                    this.totalRecords = resultList.total
                  },
 
-                 err => this.notificationsService.error(this.i18n('Error'), err.message)
+                 err => this.notifier.error(err.message)
                )
   }
 }
index ff4543b975334a41fe4b4cebbbe55b85b8adfa4e..247f441c1712dcef87f6ed9d7733b5edc802c33e 100644 (file)
@@ -7,6 +7,7 @@
       <th style="width: 40px"></th>
       <th i18n pSortableColumn="name">Video name <p-sortIcon field="name"></p-sortIcon></th>
       <th i18n>Sensitive</th>
+      <th i18n>Unfederated</th>
       <th i18n pSortableColumn="createdAt">Date <p-sortIcon field="createdAt"></p-sortIcon></th>
       <th style="width: 120px;"></th>
     </tr>
         </a>
       </td>
 
-      <td>{{ videoBlacklist.video.nsfw }}</td>
+      <td>{{ booleanToText(videoBlacklist.video.nsfw) }}</td>
+      <td>{{ booleanToText(videoBlacklist.unfederated) }}</td>
       <td>{{ videoBlacklist.createdAt }}</td>
 
       <td class="action-cell">
-        <my-action-dropdown i18n-label label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown>
+        <my-action-dropdown i18n-label  placement="bottom-right" label="Actions" [actions]="videoBlacklistActions" [entry]="videoBlacklist"></my-action-dropdown>
       </td>
     </tr>
   </ng-template>
 
   <ng-template pTemplate="rowexpansion" let-videoBlacklist>
     <tr>
-      <td class="moderation-expanded" colspan="5">
+      <td class="moderation-expanded" colspan="6">
         <span i18n class="moderation-expanded-label">Blacklist reason:</span>
-        <span class="moderation-expanded-text">{{ videoBlacklist.reason }}</span>
+        <span class="moderation-expanded-text" [innerHTML]="toHtml(videoBlacklist.reason)"></span>
       </td>
     </tr>
   </ng-template>
index e491edacac61e2c8dff18ea69642b0f869f64a2d..b27bbbfef000d0dff7747bdb8dca79f09cf47ec8 100644 (file)
@@ -1,12 +1,13 @@
 import { Component, OnInit } from '@angular/core'
 import { SortMeta } from 'primeng/components/common/sortmeta'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { ConfirmService } from '../../../core'
 import { RestPagination, RestTable, VideoBlacklistService } from '../../../shared'
 import { VideoBlacklist } from '../../../../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { DropdownAction } from '../../../shared/buttons/action-dropdown.component'
 import { Video } from '../../../shared/video/video.model'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-blacklist-list',
@@ -23,9 +24,10 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
   videoBlacklistActions: DropdownAction<VideoBlacklist>[] = []
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private videoBlacklistService: VideoBlacklistService,
+    private markdownRenderer: MarkdownService,
     private i18n: I18n
   ) {
     super()
@@ -46,6 +48,16 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
     return Video.buildClientUrl(videoBlacklist.video.uuid)
   }
 
+  booleanToText (value: boolean) {
+    if (value === true) return this.i18n('yes')
+
+    return this.i18n('no')
+  }
+
+  toHtml (text: string) {
+    return this.markdownRenderer.textMarkdownToHTML(text)
+  }
+
   async removeVideoFromBlacklist (entry: VideoBlacklist) {
     const confirmMessage = this.i18n(
       'Do you really want to remove this video from the blacklist? It will be available again in the videos list.'
@@ -56,14 +68,11 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
 
     this.videoBlacklistService.removeVideoFromBlacklist(entry.video.id).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name })
-        )
+        this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: entry.video.name }))
         this.loadData()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
@@ -75,7 +84,7 @@ export class VideoBlacklistListComponent extends RestTable implements OnInit {
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index dd8e4efd53e21920a75402f6922346190e0a78e3..137ecfcbddc1c7d51d995f3458e7c34c715afe9f 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, OnInit } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
-import { ServerService } from '../../../core'
+import { Notifier, ServerService } from '@app/core'
 import { UserCreate, UserRole } from '../../../../../../shared'
 import { UserEdit } from './user-edit'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -24,7 +23,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
     protected configService: ConfigService,
     private userValidatorsService: UserValidatorsService,
     private router: Router,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private i18n: I18n
   ) {
@@ -60,10 +59,7 @@ export class UserCreateComponent extends UserEdit implements OnInit {
 
     this.userService.addUser(userCreate).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('User {{username}} created.', { username: userCreate.username })
-        )
+        this.notifier.success(this.i18n('User {{username}} created.', { username: userCreate.username }))
         this.router.navigate([ '/admin/users/list' ])
       },
 
index cd3885a9922824c077495cd8a09cec210fd71964..61e64182391173e4674329c66b9157175860e7c3 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { Subscription } from 'rxjs'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { ServerService } from '../../../core'
 import { UserEdit } from './user-edit'
 import { User, UserUpdate } from '../../../../../../shared'
@@ -30,7 +30,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
     private userValidatorsService: UserValidatorsService,
     private route: ActivatedRoute,
     private router: Router,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private i18n: I18n
   ) {
@@ -73,10 +73,7 @@ export class UserUpdateComponent extends UserEdit implements OnInit, OnDestroy {
 
     this.userService.updateUser(this.userId, userUpdate).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('User {{username}} updated.', { username: this.username })
-        )
+        this.notifier.success(this.i18n('User {{username}} updated.', { username: this.username }))
         this.router.navigate([ '/admin/users/list' ])
       },
 
index 556ab3c5db3a8bd9521d55e1a77392e0ba403237..69a4616a3a6e6c44ec8830a8c045d7e6a8dfc343 100644 (file)
@@ -2,7 +2,7 @@
   <div i18n class="form-sub-title">Users list</div>
 
   <a class="add-button" routerLink="/admin/users/create">
-    <span class="icon icon-add"></span>
+    <my-global-icon iconName="add"></my-global-icon>
     <ng-container i18n>Create user</ng-container>
   </a>
 </div>
@@ -65,7 +65,9 @@
           <span i18n *ngIf="user.blocked" class="banned-info">(banned)</span>
         </a>
       </td>
+
       <td *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">{{ user.email }}</td>
+
       <ng-template #emailWithVerificationStatus>
         <td *ngIf="user.emailVerified === false; else emailVerifiedNotFalse" i18n-title title="User's email must be verified to login">
           <em>? {{ user.email }}</em>
@@ -76,6 +78,7 @@
           </td>
         </ng-template>
       </ng-template>
+
       <td>{{ user.videoQuotaUsed }} / {{ user.videoQuota }}</td>
       <td>{{ user.roleLabel }}</td>
       <td>{{ user.createdAt }}</td>
index f235769f06bdecd252ab7db4a89d6ace1e32f6a2..5274be01cf938d55485c67c28ac7b70f621a1c21 100644 (file)
@@ -2,7 +2,7 @@
 @import '_mixins';
 
 .add-button {
-  @include create-button('../../../../assets/images/global/add.svg');
+  @include create-button;
 }
 
 tr.banned {
@@ -23,4 +23,4 @@ tr.banned {
   input {
     @include peertube-input-text(250px);
   }
-}
\ No newline at end of file
+}
index fb085c1331f6f1a30261f175d8bffeaabdfa91da..66ab796f9bee2af8270663cf9710fbfc12b9a685 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { SortMeta } from 'primeng/components/common/sortmeta'
 import { ConfirmService, ServerService } from '../../../core'
 import { RestPagination, RestTable, UserService } from '../../../shared'
@@ -26,7 +26,7 @@ export class UserListComponent extends RestTable implements OnInit {
   bulkUserActions: DropdownAction<User[]>[] = []
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private serverService: ServerService,
     private userService: UserService,
@@ -68,7 +68,7 @@ export class UserListComponent extends RestTable implements OnInit {
   openBanUserModal (users: User[]) {
     for (const user of users) {
       if (user.username === 'root') {
-        this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
+        this.notifier.error(this.i18n('You cannot ban root.'))
         return
       }
     }
@@ -91,18 +91,18 @@ export class UserListComponent extends RestTable implements OnInit {
           () => {
             const message = this.i18n('{{num}} users unbanned.', { num: users.length })
 
-            this.notificationsService.success(this.i18n('Success'), message)
+            this.notifier.success(message)
             this.loadData()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
   async removeUsers (users: User[]) {
     for (const user of users) {
       if (user.username === 'root') {
-        this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
+        this.notifier.error(this.i18n('You cannot delete root.'))
         return
       }
     }
@@ -113,28 +113,22 @@ export class UserListComponent extends RestTable implements OnInit {
 
     this.userService.removeUser(users).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('{{num}} users deleted.', { num: users.length })
-        )
+        this.notifier.success(this.i18n('{{num}} users deleted.', { num: users.length }))
         this.loadData()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
   async setEmailsAsVerified (users: User[]) {
     this.userService.updateUsers(users, { emailVerified: true }).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('{{num}} users email set as verified.', { num: users.length })
-        )
+        this.notifier.success(this.i18n('{{num}} users email set as verified.', { num: users.length }))
         this.loadData()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
@@ -146,13 +140,13 @@ export class UserListComponent extends RestTable implements OnInit {
     this.selectedUsers = []
 
     this.userService.getUsers(this.pagination, this.sort, this.search)
-                    .subscribe(
-                      resultList => {
-                        this.users = resultList.data
-                        this.totalRecords = resultList.total
-                      },
-
-                      err => this.notificationsService.error(this.i18n('Error'), err.message)
-                    )
+        .subscribe(
+          resultList => {
+            this.users = resultList.data
+            this.totalRecords = resultList.total
+          },
+
+          err => this.notifier.error(err.message)
+        )
   }
 }
index fbad28410b8c4d3cec1cc9139ed1153b4573e744..e3025dec458c133fa37eb9cc959b7d2511df1e9d 100644 (file)
@@ -1,9 +1,9 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { RestPagination, RestTable } from '@app/shared'
 import { SortMeta } from 'primeng/components/common/sortmeta'
-import { BlocklistService, AccountBlock } from '@app/shared/blocklist'
+import { AccountBlock, BlocklistService } from '@app/shared/blocklist'
 
 @Component({
   selector: 'my-account-blocklist',
@@ -18,7 +18,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit {
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private blocklistService: BlocklistService,
     private i18n: I18n
   ) {
@@ -35,10 +35,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit {
     this.blocklistService.unblockAccountByUser(blockedAccount)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost })
-            )
+            this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: blockedAccount.nameWithHost }))
 
             this.loadData()
           }
@@ -53,7 +50,7 @@ export class MyAccountBlocklistComponent extends RestTable implements OnInit {
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index b411d6926bfe8b8bc6b82a1de07df30ce57d7aa9..4c5cc28b89c8187c9f4ad35615c20bf091a041bc 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { RestPagination, RestTable } from '@app/shared'
 import { SortMeta } from 'primeng/components/common/sortmeta'
@@ -19,7 +19,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private blocklistService: BlocklistService,
     private i18n: I18n
   ) {
@@ -36,10 +36,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn
     this.blocklistService.unblockServerByUser(host)
       .subscribe(
         () => {
-          this.notificationsService.success(
-            this.i18n('Success'),
-            this.i18n('Instance {{host}} unmuted.', { host })
-          )
+          this.notifier.success(this.i18n('Instance {{host}} unmuted.', { host }))
 
           this.loadData()
         }
@@ -54,7 +51,7 @@ export class MyAccountServerBlocklistComponent extends RestTable implements OnIn
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.html b/client/src/app/+my-account/my-account-history/my-account-history.component.html
new file mode 100644 (file)
index 0000000..d42af37
--- /dev/null
@@ -0,0 +1,27 @@
+<div class="top-buttons">
+  <div class="history-switch">
+    <p-inputSwitch [(ngModel)]="videosHistoryEnabled" (ngModelChange)="onVideosHistoryChange()"></p-inputSwitch>
+    <label i18n>History enabled</label>
+  </div>
+
+  <div class="delete-history">
+    <button (click)="deleteHistory()" i18n>Delete history</button>
+  </div>
+</div>
+
+
+<div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div>
+
+<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" class="videos" #videosElement>
+  <div *ngFor="let videos of videoPages;" class="videos-page">
+    <div class="video" *ngFor="let video of videos">
+      <my-video-thumbnail [video]="video"></my-video-thumbnail>
+
+      <div class="video-info">
+        <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
+        <span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span>
+        <a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.scss b/client/src/app/+my-account/my-account-history/my-account-history.component.scss
new file mode 100644 (file)
index 0000000..e7c6863
--- /dev/null
@@ -0,0 +1,99 @@
+@import '_variables';
+@import '_mixins';
+
+.no-history {
+  display: flex;
+  justify-content: center;
+  margin-top: 50px;
+  font-weight: $font-semibold;
+  font-size: 16px;
+}
+
+.top-buttons {
+  margin-bottom: 20px;
+  display: flex;
+
+  .history-switch {
+    display: flex;
+    flex-grow: 1;
+
+    label {
+      margin: 0 0 0 5px;
+    }
+  }
+
+  .delete-history {
+    font-size: 15px;
+
+    button {
+      @include peertube-button;
+      @include grey-button;
+    }
+  }
+}
+
+.video {
+  @include row-blocks;
+
+  my-video-thumbnail {
+    margin-right: 10px;
+  }
+
+  .video-info {
+    flex-grow: 1;
+
+    .video-info-name {
+      @include disable-default-a-behaviour;
+
+      color: var(--mainForegroundColor);
+      display: block;
+      width: fit-content;
+      font-size: 18px;
+      font-weight: $font-semibold;
+    }
+
+    .video-info-date-views {
+      font-size: 14px;
+    }
+
+    .video-info-account {
+      @include disable-default-a-behaviour;
+
+      display: block;
+      width: fit-content;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      font-size: 14px;
+      color: $grey-foreground-color;
+
+      &:hover {
+        color: $grey-foreground-hover-color;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: $small-view) {
+  .video {
+    flex-direction: column;
+    height: auto;
+    text-align: center;
+
+    .video-info-name {
+      margin: auto;
+    }
+
+    input[type=checkbox] {
+      display: none;
+    }
+
+    my-video-thumbnail {
+      margin-right: 0;
+    }
+
+    .video-buttons {
+      margin-top: 10px;
+    }
+  }
+}
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
new file mode 100644 (file)
index 0000000..394091b
--- /dev/null
@@ -0,0 +1,107 @@
+import { Component, OnDestroy, OnInit } from '@angular/core'
+import { ActivatedRoute, Router } from '@angular/router'
+import { Location } from '@angular/common'
+import { immutableAssign } from '@app/shared/misc/utils'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { AuthService } from '../../core/auth'
+import { ConfirmService } from '../../core/confirm'
+import { AbstractVideoList } from '../../shared/video/abstract-video-list'
+import { VideoService } from '../../shared/video/video.service'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { ScreenService } from '@app/shared/misc/screen.service'
+import { UserHistoryService } from '@app/shared/users/user-history.service'
+import { UserService } from '@app/shared'
+import { Notifier } from '@app/core'
+
+@Component({
+  selector: 'my-account-history',
+  templateUrl: './my-account-history.component.html',
+  styleUrls: [ './my-account-history.component.scss' ]
+})
+export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy {
+  titlePage: string
+  currentRoute = '/my-account/history/videos'
+  pagination: ComponentPagination = {
+    currentPage: 1,
+    itemsPerPage: 5,
+    totalItems: null
+  }
+  videosHistoryEnabled: boolean
+
+  protected baseVideoWidth = -1
+  protected baseVideoHeight = 155
+
+  constructor (
+    protected router: Router,
+    protected route: ActivatedRoute,
+    protected authService: AuthService,
+    protected userService: UserService,
+    protected notifier: Notifier,
+    protected location: Location,
+    protected screenService: ScreenService,
+    protected i18n: I18n,
+    private confirmService: ConfirmService,
+    private videoService: VideoService,
+    private userHistoryService: UserHistoryService
+  ) {
+    super()
+
+    this.titlePage = this.i18n('My videos history')
+  }
+
+  ngOnInit () {
+    super.ngOnInit()
+
+    this.videosHistoryEnabled = this.authService.getUser().videosHistoryEnabled
+  }
+
+  ngOnDestroy () {
+    super.ngOnDestroy()
+  }
+
+  getVideosObservable (page: number) {
+    const newPagination = immutableAssign(this.pagination, { currentPage: page })
+
+    return this.userHistoryService.getUserVideosHistory(newPagination)
+  }
+
+  generateSyndicationList () {
+    throw new Error('Method not implemented.')
+  }
+
+  onVideosHistoryChange () {
+    this.userService.updateMyProfile({ videosHistoryEnabled: this.videosHistoryEnabled })
+      .subscribe(
+        () => {
+          const message = this.videosHistoryEnabled === true ?
+            this.i18n('Videos history is enabled') :
+            this.i18n('Videos history is disabled')
+
+          this.notifier.success(message)
+
+          this.authService.refreshUserInformation()
+        },
+
+        err => this.notifier.error(err.message)
+      )
+  }
+
+  async deleteHistory () {
+    const title = this.i18n('Delete videos history')
+    const message = this.i18n('Are you sure you want to delete all your videos history?')
+
+    const res = await this.confirmService.confirm(message, title)
+    if (res !== true) return
+
+    this.userHistoryService.deleteUserVideosHistory()
+        .subscribe(
+          () => {
+            this.notifier.success(this.i18n('Videos history deleted'))
+
+            this.reloadVideos()
+          },
+
+          err => this.notifier.error(err.message)
+        )
+  }
+}
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.html
new file mode 100644 (file)
index 0000000..d518b22
--- /dev/null
@@ -0,0 +1,13 @@
+<div class="header">
+  <a routerLink="/my-account/settings" fragment="notifications" i18n>
+    <my-global-icon iconName="cog"></my-global-icon>
+    Notification preferences
+  </a>
+
+  <button (click)="markAllAsRead()" i18n>
+    <my-global-icon iconName="circle-tick"></my-global-icon>
+    Mark all as read
+  </button>
+</div>
+
+<my-user-notifications #userNotification></my-user-notifications>
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.scss
new file mode 100644 (file)
index 0000000..43d1f82
--- /dev/null
@@ -0,0 +1,25 @@
+@import '_variables';
+@import '_mixins';
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  font-size: 15px;
+  margin-bottom: 20px;
+
+  a {
+    @include peertube-button-link;
+    @include grey-button;
+    @include button-with-icon(18px, 3px, -1px);
+  }
+
+  button {
+    @include peertube-button;
+    @include grey-button;
+    @include button-with-icon(20px, 3px, -1px);
+  }
+}
+
+my-user-notifications {
+  font-size: 15px;
+}
diff --git a/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts b/client/src/app/+my-account/my-account-notifications/my-account-notifications.component.ts
new file mode 100644 (file)
index 0000000..3e19708
--- /dev/null
@@ -0,0 +1,14 @@
+import { Component, ViewChild } from '@angular/core'
+import { UserNotificationsComponent } from '@app/shared'
+
+@Component({
+  templateUrl: './my-account-notifications.component.html',
+  styleUrls: [ './my-account-notifications.component.scss' ]
+})
+export class MyAccountNotificationsComponent {
+  @ViewChild('userNotification') userNotification: UserNotificationsComponent
+
+  markAllAsRead () {
+    this.userNotification.markAllAsRead()
+  }
+}
index fd7d7d23bf0eb6b560d6a61636f99543ddadc958..674a4e8a27faeaf78782f2ebcbc30d59c9ddc280 100644 (file)
@@ -1,7 +1,8 @@
 <ng-template #modal let-close="close" let-dismiss="dismiss">
   <div class="modal-header">
     <h4 i18n class="modal-title">Accept ownership</h4>
-    <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span>
+
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
   </div>
 
   <div class="modal-body" [formGroup]="form">
index a68b452ecf49da35c57ae3c8185c9467a253b45c..79d29b139ff0507a2fa21d2b2a2b72fffd029501 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier } from '@app/core'
 import { FormReactive } from '@app/shared'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { VideoOwnershipService } from '@app/shared/video-ownership'
@@ -8,7 +8,6 @@ import { VideoAcceptOwnershipValidatorsService } from '@app/shared/forms/form-va
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { AuthService } from '@app/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 
 @Component({
@@ -31,7 +30,7 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O
     protected formValidatorService: FormValidatorService,
     private videoChangeOwnershipValidatorsService: VideoAcceptOwnershipValidatorsService,
     private videoOwnershipService: VideoOwnershipService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private authService: AuthService,
     private videoChannelService: VideoChannelService,
     private modalService: NgbModal,
@@ -68,12 +67,12 @@ export class MyAccountAcceptOwnershipComponent extends FormReactive implements O
       .acceptOwnership(videoChangeOwnership.id, { channelId: channel })
       .subscribe(
         () => {
-          this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership accepted'))
+          this.notifier.success(this.i18n('Ownership accepted'))
           if (this.accepted) this.accepted.emit()
           this.videoChangeOwnership = undefined
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index 379fd8bb17580b8850e49ad39490cb416cdd01c1..5709e9f543c44f219f5f28c26bfbd0b708940955 100644 (file)
       <td class="action-cell">
         <ng-container *ngIf="videoChangeOwnership.status === 'WAITING'">
           <my-button i18n label="Accept"
-                     icon="icon-tick"
+                     icon="tick"
                      (click)="openAcceptModal(videoChangeOwnership)"></my-button>
           <my-button i18n label="Refuse"
-                     icon="icon-cross"
+                     icon="cross"
                      (click)="refuse(videoChangeOwnership)">Refuse</my-button>
         </ng-container>
       </td>
index 0b51ac13cafc6f060574a0c6c78936bdc2d27920..77857c4fdc41ba456a4b5d251ae2bc3e4b3f56fa 100644 (file)
@@ -1,13 +1,11 @@
 import { Component, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
-import { I18n } from '@ngx-translate/i18n-polyfill'
+import { Notifier } from '@app/core'
 import { RestPagination, RestTable } from '@app/shared'
 import { SortMeta } from 'primeng/components/common/sortmeta'
 import { VideoChangeOwnership } from '../../../../../shared'
 import { VideoOwnershipService } from '@app/shared/video-ownership'
 import { Account } from '@app/shared/account/account.model'
-import { MyAccountAcceptOwnershipComponent }
-from '@app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component'
+import { MyAccountAcceptOwnershipComponent } from './my-account-accept-ownership/my-account-accept-ownership.component'
 
 @Component({
   selector: 'my-account-ownership',
@@ -23,9 +21,8 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit {
   @ViewChild('myAccountAcceptOwnershipComponent') myAccountAcceptOwnershipComponent: MyAccountAcceptOwnershipComponent
 
   constructor (
-    private notificationsService: NotificationsService,
-    private videoOwnershipService: VideoOwnershipService,
-    private i18n: I18n
+    private notifier: Notifier,
+    private videoOwnershipService: VideoOwnershipService
   ) {
     super()
   }
@@ -50,7 +47,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit {
     this.videoOwnershipService.refuseOwnership(videoChangeOwnership.id)
       .subscribe(
         () => this.loadData(),
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -62,7 +59,7 @@ export class MyAccountOwnershipComponent extends RestTable implements OnInit {
           this.totalRecords = resultList.total
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index 601e517b47075ea97e8822387bac7c52c5827aab..9996218ca1032cd586da3f58b625a79aaccf7ea4 100644 (file)
@@ -13,6 +13,8 @@ import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-sub
 import { MyAccountOwnershipComponent } from '@app/+my-account/my-account-ownership/my-account-ownership.component'
 import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component'
 import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component'
+import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component'
+import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component'
 
 const myAccountRoutes: Routes = [
   {
@@ -114,6 +116,24 @@ const myAccountRoutes: Routes = [
             title: 'Muted instances'
           }
         }
+      },
+      {
+        path: 'history/videos',
+        component: MyAccountHistoryComponent,
+        data: {
+          meta: {
+            title: 'Videos history'
+          }
+        }
+      },
+      {
+        path: 'notifications',
+        component: MyAccountNotificationsComponent,
+        data: {
+          meta: {
+            title: 'Notifications'
+          }
+        }
       }
     ]
   }
index e5343b33d6798bce06fdc9ff9e9c9ca121227451..cbb068c7ce76575380bfef8f6558cb1a983279d0 100644 (file)
@@ -1,11 +1,10 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier } from '@app/core'
 import { FormReactive, UserService } from '../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
 import { filter } from 'rxjs/operators'
-import { AuthService } from '@app/core'
 import { User } from '../../../../../../shared'
 
 @Component({
@@ -20,7 +19,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
   constructor (
     protected formValidatorService: FormValidatorService,
     private userValidatorsService: UserValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private authService: AuthService,
     private userService: UserService,
     private i18n: I18n
@@ -50,7 +49,7 @@ export class MyAccountChangePasswordComponent extends FormReactive implements On
 
     this.userService.changePassword(currentPassword, newPassword).subscribe(
       () => {
-        this.notificationsService.success(this.i18n('Success'), this.i18n('Password updated.'))
+        this.notifier.success(this.i18n('Password updated.'))
 
         this.form.reset()
         this.error = null
index 63a121f645e4f1a6fa04fa43b04bdfd532c91d07..3f79efe20237493ca0f2d37726d2895d1d296e0b 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { AuthService, ConfirmService, RedirectService } from '../../../core'
 import { UserService } from '../../../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -15,7 +15,7 @@ export class MyAccountDangerZoneComponent {
 
   constructor (
     private authService: AuthService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private confirmService: ConfirmService,
     private redirectService: RedirectService,
@@ -34,13 +34,13 @@ export class MyAccountDangerZoneComponent {
 
     this.userService.deleteMe().subscribe(
       () => {
-        this.notificationsService.success(this.i18n('Success'), this.i18n('Your account is deleted.'))
+        this.notifier.success(this.i18n('Your account is deleted.'))
 
         this.authService.logout()
         this.redirectService.redirectToHomepage()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 }
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/index.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/index.ts
new file mode 100644 (file)
index 0000000..5e1d513
--- /dev/null
@@ -0,0 +1 @@
+export * from './my-account-notification-preferences.component'
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html
new file mode 100644 (file)
index 0000000..59422d6
--- /dev/null
@@ -0,0 +1,19 @@
+<div class="custom-row">
+  <div i18n>Activities</div>
+  <div i18n>Web</div>
+  <div i18n *ngIf="emailEnabled">Email</div>
+</div>
+
+<div class="custom-row" *ngFor="let notificationType of notificationSettingKeys">
+  <ng-container *ngIf="hasUserRight(notificationType)">
+    <div>{{ labelNotifications[notificationType] }}</div>
+
+    <div>
+      <p-inputSwitch [(ngModel)]="webNotifications[notificationType]" (onChange)="updateWebSetting(notificationType, $event.checked)"></p-inputSwitch>
+    </div>
+
+    <div *ngIf="emailEnabled">
+      <p-inputSwitch [(ngModel)]="emailNotifications[notificationType]" (onChange)="updateEmailSetting(notificationType, $event.checked)"></p-inputSwitch>
+    </div>
+  </ng-container>
+</div>
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.scss
new file mode 100644 (file)
index 0000000..6feb16a
--- /dev/null
@@ -0,0 +1,25 @@
+@import '_variables';
+@import '_mixins';
+
+.custom-row {
+  display: flex;
+  align-items: center;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.10);
+
+  &:first-child {
+    font-size: 16px;
+
+    & > div {
+      font-weight: $font-semibold;
+    }
+  }
+
+  & > div {
+    width: 350px;
+  }
+
+  & > div {
+    padding: 10px
+  }
+}
+
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts
new file mode 100644 (file)
index 0000000..519bdfa
--- /dev/null
@@ -0,0 +1,99 @@
+import { Component, Input, OnInit } from '@angular/core'
+import { User } from '@app/shared'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { Subject } from 'rxjs'
+import { UserNotificationSetting, UserNotificationSettingValue, UserRight } from '../../../../../../shared'
+import { Notifier, ServerService } from '@app/core'
+import { debounce } from 'lodash-es'
+import { UserNotificationService } from '@app/shared/users/user-notification.service'
+
+@Component({
+  selector: 'my-account-notification-preferences',
+  templateUrl: './my-account-notification-preferences.component.html',
+  styleUrls: [ './my-account-notification-preferences.component.scss' ]
+})
+export class MyAccountNotificationPreferencesComponent implements OnInit {
+  @Input() user: User = null
+  @Input() userInformationLoaded: Subject<any>
+
+  notificationSettingKeys: (keyof UserNotificationSetting)[] = []
+  emailNotifications: { [ id in keyof UserNotificationSetting ]: boolean } = {} as any
+  webNotifications: { [ id in keyof UserNotificationSetting ]: boolean } = {} as any
+  labelNotifications: { [ id in keyof UserNotificationSetting ]: string } = {} as any
+  rightNotifications: { [ id in keyof Partial<UserNotificationSetting> ]: UserRight } = {} as any
+  emailEnabled: boolean
+
+  private savePreferences = debounce(this.savePreferencesImpl.bind(this), 500)
+
+  constructor (
+    private i18n: I18n,
+    private userNotificationService: UserNotificationService,
+    private serverService: ServerService,
+    private notifier: Notifier
+  ) {
+    this.labelNotifications = {
+      newVideoFromSubscription: this.i18n('New video from your subscriptions'),
+      newCommentOnMyVideo: this.i18n('New comment on your video'),
+      videoAbuseAsModerator: this.i18n('New video abuse on local video'),
+      blacklistOnMyVideo: this.i18n('One of your video is blacklisted/unblacklisted'),
+      myVideoPublished: this.i18n('Video published (after transcoding/scheduled update)'),
+      myVideoImportFinished: this.i18n('Video import finished'),
+      newUserRegistration: this.i18n('A new user registered on your instance'),
+      newFollow: this.i18n('You or your channel(s) has a new follower'),
+      commentMention: this.i18n('Someone mentioned you in video comments')
+    }
+    this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[]
+
+    this.rightNotifications = {
+      videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES,
+      newUserRegistration: UserRight.MANAGE_USERS
+    }
+
+    this.emailEnabled = this.serverService.getConfig().email.enabled
+  }
+
+  ngOnInit () {
+    this.userInformationLoaded.subscribe(() => this.loadNotificationSettings())
+  }
+
+  hasUserRight (field: keyof UserNotificationSetting) {
+    const rightToHave = this.rightNotifications[field]
+    if (!rightToHave) return true // No rights needed
+
+    return this.user.hasRight(rightToHave)
+  }
+
+  updateEmailSetting (field: keyof UserNotificationSetting, value: boolean) {
+    if (value === true) this.user.notificationSettings[field] |= UserNotificationSettingValue.EMAIL
+    else this.user.notificationSettings[field] &= ~UserNotificationSettingValue.EMAIL
+
+    this.savePreferences()
+  }
+
+  updateWebSetting (field: keyof UserNotificationSetting, value: boolean) {
+    if (value === true) this.user.notificationSettings[field] |= UserNotificationSettingValue.WEB
+    else this.user.notificationSettings[field] &= ~UserNotificationSettingValue.WEB
+
+    this.savePreferences()
+  }
+
+  private savePreferencesImpl () {
+    this.userNotificationService.updateNotificationSettings(this.user, this.user.notificationSettings)
+      .subscribe(
+        () => {
+          this.notifier.success(this.i18n('Preferences saved'), undefined, 2000)
+        },
+
+        err => this.notifier.error(err.message)
+      )
+  }
+
+  private loadNotificationSettings () {
+    for (const key of Object.keys(this.user.notificationSettings)) {
+      const value = this.user.notificationSettings[key]
+      this.emailNotifications[key] = value & UserNotificationSettingValue.EMAIL
+
+      this.webNotifications[key] = value & UserNotificationSettingValue.WEB
+    }
+  }
+}
index 967e21f0bd8c2b93be68f6241a01321d23127d7d..a9503ed1b8eb9769fb5292b357df98b3024490f7 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { FormReactive, UserService } from '../../../shared'
 import { User } from '@app/shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -21,7 +21,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
   constructor (
     protected formValidatorService: FormValidatorService,
     private userValidatorsService: UserValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private i18n: I18n
   ) {
@@ -53,7 +53,7 @@ export class MyAccountProfileComponent extends FormReactive implements OnInit {
         this.user.account.displayName = displayName
         this.user.account.description = description
 
-        this.notificationsService.success(this.i18n('Success'), this.i18n('Profile updated.'))
+        this.notifier.success(this.i18n('Profile updated.'))
       },
 
       err => this.error = err.message
index c7e23cd1ff79453b42a1bebb97540cba2fbf0f82..ad64f28fed3407e1557215742a8ae719ce114301 100644 (file)
@@ -4,10 +4,11 @@
   <span i18n class="user-quota-label">Video quota:</span> {{ userVideoQuotaUsed | bytes: 0 }} / {{ userVideoQuota }}
 </div>
 
-<ng-template [ngIf]="user && user.account">
-  <div i18n class="account-title">Profile</div>
-  <my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile>
-</ng-template>
+<div i18n class="account-title">Profile</div>
+<my-account-profile [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-profile>
+
+<div i18n class="account-title" id="notifications">Notifications</div>
+<my-account-notification-preferences [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-notification-preferences>
 
 <div i18n class="account-title">Password</div>
 <my-account-change-password></my-account-change-password>
@@ -16,4 +17,4 @@
 <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings>
 
 <div i18n class="account-title">Danger zone</div>
-<my-account-danger-zone [user]="user"></my-account-danger-zone>
\ No newline at end of file
+<my-account-danger-zone [user]="user"></my-account-danger-zone>
index 62053d97b2003220cf0f7da19c3bfc3858c66001..f4b954e5491264415f6033e3403240f06c5fca57 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { BytesPipe } from 'ngx-pipes'
 import { AuthService } from '../../core'
 import { User } from '../../shared'
@@ -19,7 +19,7 @@ export class MyAccountSettingsComponent implements OnInit {
   constructor (
     private userService: UserService,
     private authService: AuthService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private i18n: I18n
   ) {}
 
@@ -48,12 +48,12 @@ export class MyAccountSettingsComponent implements OnInit {
     this.userService.changeAvatar(formData)
       .subscribe(
         data => {
-          this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.'))
+          this.notifier.success(this.i18n('Avatar changed.'))
 
           this.user.updateAccountAvatar(data.avatar)
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index 6c9a7ce75aaa6f80ab83155c3739c30948301944..b8f80bc1a344527ae0ab56c807a54a13e01158e0 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { UserUpdateMe } from '../../../../../../shared'
 import { AuthService } from '../../../core'
 import { FormReactive, User, UserService } from '../../../shared'
@@ -19,7 +19,7 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
   constructor (
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private i18n: I18n
   ) {
@@ -54,12 +54,12 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI
 
     this.userService.updateMyProfile(details).subscribe(
       () => {
-        this.notificationsService.success(this.i18n('Success'), this.i18n('Information updated.'))
+        this.notifier.success(this.i18n('Information updated.'))
 
         this.authService.refreshUserInformation()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 }
index 9517a37059fbc97d9f9f5ba224c529a391ea2ae6..9d2dccdf0accc3e879547d898b31aadfefab329e 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { UserSubscriptionService } from '@app/shared/user-subscription'
@@ -21,7 +21,7 @@ export class MyAccountSubscriptionsComponent implements OnInit {
 
   constructor (
     private userSubscriptionService: UserSubscriptionService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private i18n: I18n
   ) {}
 
@@ -37,7 +37,7 @@ export class MyAccountSubscriptionsComponent implements OnInit {
             this.pagination.totalItems = res.total
           },
 
-          error => this.notificationsService.error(this.i18n('Error'), error.message)
+          error => this.notifier.error(error.message)
         )
   }
 
index 81608d837b2e30aaf28ec7fb283d7a7d49808b9e..a68f79b470c994374023da69f9e2dd89ca3ba967 100644 (file)
@@ -1,10 +1,9 @@
 import { Component, OnInit } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier } from '@app/core'
 import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
 import { VideoChannelCreate } from '../../../../../shared/models/videos'
 import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
-import { AuthService } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service'
@@ -21,7 +20,7 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
     private videoChannelValidatorsService: VideoChannelValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private router: Router,
     private videoChannelService: VideoChannelService,
     private i18n: I18n
@@ -56,8 +55,8 @@ export class MyAccountVideoChannelCreateComponent extends MyAccountVideoChannelE
     this.videoChannelService.createVideoChannel(videoChannelCreate).subscribe(
       () => {
         this.authService.refreshUserInformation()
-        this.notificationsService.success(
-          this.i18n('Success'),
+
+        this.notifier.success(
           this.i18n('Video channel {{videoChannelName}} created.', { videoChannelName: videoChannelCreate.displayName })
         )
         this.router.navigate([ '/my-account', 'video-channels' ])
index 5d43956f24d40488582f7c98db91f4128c044ab3..da4fb645ae92e8b73b20a5918e67be46bc708c99 100644 (file)
@@ -1,12 +1,11 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier, ServerService } from '@app/core'
 import { MyAccountVideoChannelEdit } from './my-account-video-channel-edit'
 import { VideoChannelUpdate } from '../../../../../shared/models/videos'
 import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
 import { Subscription } from 'rxjs'
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
-import { AuthService, ServerService } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { VideoChannelValidatorsService } from '@app/shared/forms/form-validators/video-channel-validators.service'
@@ -26,7 +25,7 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
     private videoChannelValidatorsService: VideoChannelValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private router: Router,
     private route: ActivatedRoute,
     private videoChannelService: VideoChannelService,
@@ -79,10 +78,11 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
     this.videoChannelService.updateVideoChannel(this.videoChannelToUpdate.name, videoChannelUpdate).subscribe(
       () => {
         this.authService.refreshUserInformation()
-        this.notificationsService.success(
-          this.i18n('Success'),
+
+        this.notifier.success(
           this.i18n('Video channel {{videoChannelName}} updated.', { videoChannelName: videoChannelUpdate.displayName })
         )
+
         this.router.navigate([ '/my-account', 'video-channels' ])
       },
 
@@ -94,12 +94,12 @@ export class MyAccountVideoChannelUpdateComponent extends MyAccountVideoChannelE
     this.videoChannelService.changeVideoChannelAvatar(this.videoChannelToUpdate.name, formData)
         .subscribe(
           data => {
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Avatar changed.'))
+            this.notifier.success(this.i18n('Avatar changed.'))
 
             this.videoChannelToUpdate.updateAvatar(data.avatar)
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
index df74b19b66a009aeb526dbd7513c9c131e527736..51db2e75d7d4c0e3e2df1ebd46156068d224d05b 100644 (file)
@@ -1,6 +1,6 @@
 <div class="video-channels-header">
   <a class="create-button" routerLink="create">
-    <span class="icon icon-add"></span>
+    <my-global-icon iconName="add"></my-global-icon>
     <ng-container i18n>Create another video channel</ng-container>
   </a>
 </div>
index 472cbb723721de3f16f1d936e90b5bc3ed9e6443..77fce138b67ba71c24e66c65b02fb206295ab0f6 100644 (file)
@@ -2,7 +2,7 @@
 @import '_mixins';
 
 .create-button {
-  @include create-button('../../../assets/images/global/add.svg');
+  @include create-button;
 }
 
 /deep/ .action-button {
index 6d1098865d6d8f9d67e78845564664e234466d58..da2c5bcd3b37531dea9ad14b085bd2281e934bd6 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { AuthService } from '../../core/auth'
 import { ConfirmService } from '../../core/confirm'
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
@@ -20,7 +20,7 @@ export class MyAccountVideoChannelsComponent implements OnInit {
 
   constructor (
     private authService: AuthService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private videoChannelService: VideoChannelService,
     private i18n: I18n
@@ -35,10 +35,14 @@ export class MyAccountVideoChannelsComponent implements OnInit {
   async deleteVideoChannel (videoChannel: VideoChannel) {
     const res = await this.confirmService.confirmWithInput(
       this.i18n(
-        'Do you really want to delete {{videoChannelName}}? It will delete all videos uploaded in this channel too.',
-        { videoChannelName: videoChannel.displayName }
+        'Do you really want to delete {{channelDisplayName}}? It will delete all videos uploaded in this channel, ' +
+        'and you will not be able to create another channel with the same name ({{channelName}})!',
+        { channelDisplayName: videoChannel.displayName, channelName: videoChannel.name }
+      ),
+      this.i18n(
+        'Please type the display name of the video channel ({{displayName}}) to confirm',
+        { displayName: videoChannel.displayName }
       ),
-      this.i18n('Please type the name of the video channel to confirm'),
       videoChannel.displayName,
       this.i18n('Delete')
     )
@@ -46,15 +50,14 @@ export class MyAccountVideoChannelsComponent implements OnInit {
 
     this.videoChannelService.removeVideoChannel(videoChannel)
       .subscribe(
-        status => {
+        () => {
           this.loadVideoChannels()
-          this.notificationsService.success(
-            this.i18n('Success'),
+          this.notifier.success(
             this.i18n('Video channel {{videoChannelName}} deleted.', { videoChannelName: videoChannel.displayName })
           )
         },
 
-        error => this.notificationsService.error(this.i18n('Error'), error.message)
+        error => this.notifier.error(error.message)
       )
   }
 
index 5b920c98d9ea2af78704a1fb56b77f3a7a96830c..21a10c8ffc7f61a8e86194846d838a65131462cb 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, OnInit } from '@angular/core'
 import { RestPagination, RestTable } from '@app/shared'
 import { SortMeta } from 'primeng/components/common/sortmeta'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { VideoImport, VideoImportState } from '../../../../../shared/models/videos'
 import { VideoImportService } from '@app/shared/video-import'
@@ -19,7 +19,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit
   pagination: RestPagination = { count: this.rowsPerPage, start: 0 }
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private videoImportService: VideoImportService,
     private i18n: I18n
   ) {
@@ -58,7 +58,7 @@ export class MyAccountVideoImportsComponent extends RestTable implements OnInit
             this.totalRecords = resultList.total
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 }
index a6911e4bf888fb8434ffd84d637e187b63a6e692..69748ef372ab29cdd3b18a3c0aa5e16c348dce2c 100644 (file)
@@ -32,7 +32,7 @@
           </span>
 
           <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
-            <span class="icon icon-delete-white"></span>
+            <my-global-icon iconName="delete"></my-global-icon>
             <ng-container i18n>Delete</ng-container>
           </span>
         </div>
@@ -45,7 +45,7 @@
 
         <my-button i18n-label label="Change ownership"
                    className="action-button-change-ownership"
-                   icon="icon-im-with-her"
+                   icon="im-with-her"
                    (click)="changeOwnership($event, video)"
         ></my-button>
       </div>
@@ -53,4 +53,4 @@
   </div>
 </div>
 
-<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
\ No newline at end of file
+<my-video-change-ownership #videoChangeOwnershipModal></my-video-change-ownership>
index 2db81a3fe5ff47b4eed031d77aaaf829fbf2b74a..39d0cf2f7997838047bbd6b2eb420670d82db8b3 100644 (file)
     .action-button-delete-selection {
       @include peertube-button;
       @include orange-button;
-    }
-
-    .icon.icon-delete-white {
-      @include icon(21px);
+      @include button-with-icon(21px);
 
-      position: relative;
-      top: -2px;
-      background-image: url('../../../assets/images/global/delete-white.svg');
+      my-global-icon {
+        @include apply-svg-color(#fff);
+      }
     }
   }
 }
@@ -97,7 +94,7 @@
   }
 }
 
-@media screen and (max-width: 800px) {
+@media screen and (max-width: $small-view) {
   .video {
     flex-direction: column;
     height: auto;
index 2d88ac760bc6254fb9e3a08d3432a4f23f7896d4..41608f796f69562c1aea5f9ce760f64a688890a8 100644 (file)
@@ -5,7 +5,7 @@ import { ActivatedRoute, Router } from '@angular/router'
 import { Location } from '@angular/common'
 import { immutableAssign } from '@app/shared/misc/utils'
 import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { AuthService } from '../../core/auth'
 import { ConfirmService } from '../../core/confirm'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -40,7 +40,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
     protected router: Router,
     protected route: ActivatedRoute,
     protected authService: AuthService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected location: Location,
     protected screenService: ScreenService,
     protected i18n: I18n,
@@ -102,16 +102,13 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
       .pipe(concatAll())
       .subscribe(
         res => {
-          this.notificationsService.success(
-            this.i18n('Success'),
-            this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })
-          )
+          this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
 
           this.abortSelectionMode()
           this.reloadVideos()
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -124,15 +121,12 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
 
     this.videoService.removeVideo(video.id)
         .subscribe(
-          status => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Video {{videoName}} deleted.', { videoName: video.name })
-            )
+          () => {
+            this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: video.name }))
             this.reloadVideos()
           },
 
-          error => this.notificationsService.error(this.i18n('Error'), error.message)
+          error => this.notifier.error(error.message)
         )
   }
 
index 7c0df850dab84761695dd38f47323738a312c0b2..22f1279044430f01c77ee4a46638038bfaf127e0 100644 (file)
@@ -1,7 +1,8 @@
 <ng-template #modal let-close="close" let-dismiss="dismiss">
   <div class="modal-header">
     <h4 i18n class="modal-title">Change ownership</h4>
-    <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span>
+
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
   </div>
 
   <div class="modal-body" [formGroup]="form">
index 9f94f3c13fe7c3acc32db24a27b20262e260c837..37d7cf2a4843611c6a3e1b53031499d50f2df404 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { FormReactive, UserService } from '../../../shared/index'
 import { Video } from '@app/shared/video/video.model'
@@ -25,7 +25,7 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
     protected formValidatorService: FormValidatorService,
     private videoChangeOwnershipValidatorsService: VideoChangeOwnershipValidatorsService,
     private videoOwnershipService: VideoOwnershipService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private modalService: NgbModal,
     private i18n: I18n
@@ -53,11 +53,9 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
     const query = event.query
     this.userService.autocomplete(query)
       .subscribe(
-        usernames => {
-          this.usernamePropositions = usernames
-        },
+        usernames => this.usernamePropositions = usernames,
 
-        err => this.notificationsService.error('Error', err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -67,9 +65,9 @@ export class VideoChangeOwnershipComponent extends FormReactive implements OnIni
     this.videoOwnershipService
       .changeOwnership(this.video.id, username)
       .subscribe(
-        () => this.notificationsService.success(this.i18n('Success'), this.i18n('Ownership change request sent.')),
+        () => this.notifier.success(this.i18n('Ownership change request sent.')),
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 }
index 41333c25a78787728dab52aca0957c03b8168369..3999252beb60fdb7668011bcadc7ee4f1f7a90de 100644 (file)
@@ -1,40 +1,5 @@
 <div class="row">
-  <div class="sub-menu">
-    <a i18n routerLink="/my-account/settings" routerLinkActive="active" class="title-page">My settings</a>
-
-    <div ngbDropdown class="my-library">
-      <span role="button" class="title-page" [ngClass]="{ active: libraryLabel !== '' }" ngbDropdownToggle>
-        <ng-container i18n>My library</ng-container>
-        <ng-container *ngIf="libraryLabel"> - {{ libraryLabel }}</ng-container>
-      </span>
-
-      <div ngbDropdownMenu>
-        <a class="dropdown-item" i18n routerLink="/my-account/video-channels">My channels</a>
-
-        <a class="dropdown-item" i18n routerLink="/my-account/videos">My videos</a>
-
-        <a class="dropdown-item" i18n routerLink="/my-account/subscriptions">My subscriptions</a>
-
-        <a class="dropdown-item" *ngIf="isVideoImportEnabled()" i18n routerLink="/my-account/video-imports">My imports</a>
-      </div>
-    </div>
-
-    <div ngbDropdown class="misc">
-      <span role="button" class="title-page" [ngClass]="{ active: miscLabel !== '' }" ngbDropdownToggle>
-        <ng-container i18n>Misc</ng-container>
-        <ng-container *ngIf="miscLabel"> - {{ miscLabel }}</ng-container>
-      </span>
-
-      <div ngbDropdownMenu>
-        <a class="dropdown-item" i18n routerLink="/my-account/blocklist/accounts">Muted accounts</a>
-
-        <a class="dropdown-item" i18n routerLink="/my-account/blocklist/servers">Muted instances</a>
-
-        <a class="dropdown-item" i18n routerLink="/my-account/ownership">Ownership changes</a>
-      </div>
-    </div>
-
-  </div>
+  <my-top-menu-dropdown [menuEntries]="menuEntries"></my-top-menu-dropdown>
 
   <div class="margin-content">
     <router-outlet></router-outlet>
index 6243c6dcfe21806368e4b98c579445d0c016bdfc..4f111efdf76bb326526a93a8bb9d95004dfa844c 100644 (file)
@@ -1,14 +1,3 @@
-.my-library, .misc {
-  span[role=button] {
-    cursor: pointer;
-  }
-
-  a {
-    display: block;
-  }
+.row {
+  flex-direction: column;
 }
-
-/deep/ .dropdown-toggle::after {
-  position: relative;
-  top: 2px;
-}
\ No newline at end of file
index d728caf0758ded3dea274d8f6941241b4017e24b..8a4102d806725ca2349775b11c39e52b6322bd02 100644 (file)
@@ -1,38 +1,80 @@
-import { Component, OnDestroy, OnInit } from '@angular/core'
+import { Component } from '@angular/core'
 import { ServerService } from '@app/core'
-import { NavigationStart, Router } from '@angular/router'
-import { filter } from 'rxjs/operators'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { Subscription } from 'rxjs'
+import { TopMenuDropdownParam } from '@app/shared/menu/top-menu-dropdown.component'
 
 @Component({
   selector: 'my-my-account',
   templateUrl: './my-account.component.html',
   styleUrls: [ './my-account.component.scss' ]
 })
-export class MyAccountComponent implements OnInit, OnDestroy {
-
-  libraryLabel = ''
-  miscLabel = ''
-
-  private routeSub: Subscription
+export class MyAccountComponent {
+  menuEntries: TopMenuDropdownParam[] = []
 
   constructor (
     private serverService: ServerService,
-    private router: Router,
     private i18n: I18n
-  ) {}
+  ) {
+
+    const libraryEntries: TopMenuDropdownParam = {
+      label: this.i18n('My library'),
+      children: [
+        {
+          label: this.i18n('My channels'),
+          routerLink: '/my-account/video-channels'
+        },
+        {
+          label: this.i18n('My videos'),
+          routerLink: '/my-account/videos'
+        },
+        {
+          label: this.i18n('My subscriptions'),
+          routerLink: '/my-account/subscriptions'
+        },
+        {
+          label: this.i18n('My history'),
+          routerLink: '/my-account/history/videos'
+        }
+      ]
+    }
 
-  ngOnInit () {
-    this.updateLabels(this.router.url)
+    if (this.isVideoImportEnabled()) {
+      libraryEntries.children.push({
+        label: 'My imports',
+        routerLink: '/my-account/video-imports'
+      })
+    }
 
-    this.routeSub = this.router.events
-        .pipe(filter(event => event instanceof NavigationStart))
-        .subscribe((event: NavigationStart) => this.updateLabels(event.url))
-  }
+    const miscEntries: TopMenuDropdownParam = {
+      label: this.i18n('Misc'),
+      children: [
+        {
+          label: this.i18n('Muted accounts'),
+          routerLink: '/my-account/blocklist/accounts'
+        },
+        {
+          label: this.i18n('Muted instances'),
+          routerLink: '/my-account/blocklist/servers'
+        },
+        {
+          label: this.i18n('Ownership changes'),
+          routerLink: '/my-account/ownership'
+        }
+      ]
+    }
 
-  ngOnDestroy () {
-    if (this.routeSub) this.routeSub.unsubscribe()
+    this.menuEntries = [
+      {
+        label: this.i18n('My settings'),
+        routerLink: '/my-account/settings'
+      },
+      {
+        label: this.i18n('My notifications'),
+        routerLink: '/my-account/notifications'
+      },
+      libraryEntries,
+      miscEntries
+    ]
   }
 
   isVideoImportEnabled () {
@@ -41,27 +83,4 @@ export class MyAccountComponent implements OnInit, OnDestroy {
     return importConfig.http.enabled || importConfig.torrent.enabled
   }
 
-  private updateLabels (url: string) {
-    const [ path ] = url.split('?')
-
-    if (path.startsWith('/my-account/video-channels')) {
-      this.libraryLabel = this.i18n('Channels')
-    } else if (path.startsWith('/my-account/videos')) {
-      this.libraryLabel = this.i18n('Videos')
-    } else if (path.startsWith('/my-account/subscriptions')) {
-      this.libraryLabel = this.i18n('Subscriptions')
-    } else if (path.startsWith('/my-account/video-imports')) {
-      this.libraryLabel = this.i18n('Video imports')
-    } else {
-      this.libraryLabel = ''
-    }
-
-    if (path.startsWith('/my-account/blocklist/accounts')) {
-      this.miscLabel = this.i18n('Muted accounts')
-    } else if (path.startsWith('/my-account/blocklist/servers')) {
-      this.miscLabel = this.i18n('Muted instances')
-    } else {
-      this.miscLabel = ''
-    }
-  }
 }
index 017ebd57d8773500baeb375b55eb0bf699533e5c..18f51f171391c4d7ba6e5219ff89b2ac363f6df7 100644 (file)
@@ -1,6 +1,7 @@
 import { TableModule } from 'primeng/table'
 import { NgModule } from '@angular/core'
 import { AutoCompleteModule } from 'primeng/autocomplete'
+import { InputSwitchModule } from 'primeng/inputswitch'
 import { SharedModule } from '../shared'
 import { MyAccountRoutingModule } from './my-account-routing.module'
 import { MyAccountChangePasswordComponent } from './my-account-settings/my-account-change-password/my-account-change-password.component'
@@ -21,6 +22,9 @@ import { MyAccountDangerZoneComponent } from '@app/+my-account/my-account-settin
 import { MyAccountSubscriptionsComponent } from '@app/+my-account/my-account-subscriptions/my-account-subscriptions.component'
 import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-blocklist.component'
 import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component'
+import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component'
+import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component'
+import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences'
 
 @NgModule({
   imports: [
@@ -28,7 +32,8 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b
     MyAccountRoutingModule,
     AutoCompleteModule,
     SharedModule,
-    TableModule
+    TableModule,
+    InputSwitchModule
   ],
 
   declarations: [
@@ -49,7 +54,10 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b
     MyAccountDangerZoneComponent,
     MyAccountSubscriptionsComponent,
     MyAccountBlocklistComponent,
-    MyAccountServerBlocklistComponent
+    MyAccountServerBlocklistComponent,
+    MyAccountHistoryComponent,
+    MyAccountNotificationsComponent,
+    MyAccountNotificationPreferencesComponent
   ],
 
   exports: [
index 54bacc21229cdb641aaf32ed0d5c4b3938369fc8..72c815a0c7778c265b4a1ff548cdb403ebbf49b1 100644 (file)
@@ -1,8 +1,8 @@
 import { Component, ElementRef, EventEmitter, Input, Output, ViewChild } from '@angular/core'
 import { ServerService } from '../../core/server'
-import { NotificationsService } from 'angular2-notifications'
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 import { Account } from '@app/shared/account/account.model'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-actor-avatar-info',
@@ -18,13 +18,13 @@ export class ActorAvatarInfoComponent {
 
   constructor (
     private serverService: ServerService,
-    private notificationsService: NotificationsService
+    private notifier: Notifier
   ) {}
 
   onAvatarChange () {
     const avatarfile = this.avatarfileInput.nativeElement.files[ 0 ]
     if (avatarfile.size > this.maxAvatarSize) {
-      this.notificationsService.error('Error', 'This image is too large.')
+      this.notifier.error('Error', 'This image is too large.')
       return
     }
 
index 995f42ffceff93b50d267289477a69ec3295441c..cfd471fa400eaeb210e5930475f9708643246759 100644 (file)
@@ -1,9 +1,8 @@
 import { Component, OnInit } from '@angular/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier, RedirectService } from '@app/core'
 import { ServerService } from '@app/core/server'
-import { RedirectService } from '@app/core'
-import { UserService, FormReactive } from '@app/shared'
+import { FormReactive, UserService } from '@app/shared'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service'
 
@@ -20,7 +19,7 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
     private userValidatorsService: UserValidatorsService,
     private userService: UserService,
     private serverService: ServerService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private redirectService: RedirectService,
     private i18n: I18n
   ) {
@@ -46,12 +45,12 @@ export class VerifyAccountAskSendEmailComponent extends FormReactive implements
             'An email with verification link will be sent to {{email}}.',
             { email }
           )
-          this.notificationsService.success(this.i18n('Success'), message)
+          this.notifier.success(message)
           this.redirectService.redirectToHomepage()
         },
 
         err => {
-          this.notificationsService.error(this.i18n('Error'), err.message)
+          this.notifier.error(err.message)
         }
       )
   }
index 30ace5e107ce04b4bd298577e5eadaf7297a17f7..a83d4a3c237db3c9c738eb00e34ca993576547e0 100644 (file)
@@ -9,7 +9,7 @@
   <ng-template #verificationError>
     <div>
       <span i18n>An error occurred. </span>
-      <a i18n routerLink="/verify-account/ask-email">Request new verification email.</a>
+      <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a>
     </div>
   </ng-template>
 </div>
index e4a5522c8e8ad970e7f393725e03dccea4bcbbb1..f9ecf664b9e613421982f19fcf9c6bae5a59c710 100644 (file)
@@ -1,7 +1,7 @@
 import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { UserService } from '@app/shared'
 
 @Component({
@@ -17,7 +17,7 @@ export class VerifyAccountEmailComponent implements OnInit {
 
   constructor (
     private userService: UserService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private router: Router,
     private route: ActivatedRoute,
     private i18n: I18n
@@ -29,7 +29,7 @@ export class VerifyAccountEmailComponent implements OnInit {
     this.verificationString = this.route.snapshot.queryParams['verificationString']
 
     if (!this.userId || !this.verificationString) {
-      this.notificationsService.error(this.i18n('Error'), this.i18n('Unable to find user id or verification string.'))
+      this.notifier.error(this.i18n('Unable to find user id or verification string.'))
     } else {
       this.verifyEmail()
     }
@@ -46,7 +46,7 @@ export class VerifyAccountEmailComponent implements OnInit {
         },
 
         err => {
-          this.notificationsService.error(this.i18n('Error'), err.message)
+          this.notifier.error(err.message)
         }
       )
   }
index ea7b0e118875f41b2945aeacfd4f37fdd98c91b2..895b190648e28c31766e2e5a22973cbdb6b7505a 100644 (file)
@@ -3,7 +3,7 @@ import { VideoChannelService } from '@app/shared/video-channel/video-channel.ser
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { Subscription } from 'rxjs'
-import { MarkdownService } from '@app/videos/shared'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-channel-about',
index 1f0744fb13cc9cc71d67fc0f0da30f2bca072a9a..dea378a6e6a638c4e05e86e50318fc46f2d81a8b 100644 (file)
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { Location } from '@angular/common'
 import { immutableAssign } from '@app/shared/misc/utils'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../../core/auth'
 import { ConfirmService } from '../../core/confirm'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -13,6 +12,7 @@ import { tap } from 'rxjs/operators'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { Subscription } from 'rxjs'
 import { ScreenService } from '@app/shared/misc/screen.service'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-video-channel-videos',
@@ -35,7 +35,7 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
     protected router: Router,
     protected route: ActivatedRoute,
     protected authService: AuthService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected confirmService: ConfirmService,
     protected location: Location,
     protected screenService: ScreenService,
@@ -55,7 +55,7 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
     this.videoChannelSub = this.videoChannelService.videoChannelLoaded
       .subscribe(videoChannel => {
         this.videoChannel = videoChannel
-        this.currentRoute = '/video-channels/' + this.videoChannel.uuid + '/videos'
+        this.currentRoute = '/video-channels/' + this.videoChannel.nameWithHost + '/videos'
 
         this.reloadVideos()
         this.generateSyndicationList()
index 935578d2a4ff20486d9589c23b09a8405b973403..3ac3533d9e41f9941b5d4249e68f53f0bc3d4196 100644 (file)
@@ -7,7 +7,7 @@ import { VideoChannelAboutComponent } from './video-channel-about/video-channel-
 
 const videoChannelsRoutes: Routes = [
   {
-    path: ':videoChannelId',
+    path: ':videoChannelName',
     component: VideoChannelsComponent,
     canActivateChild: [ MetaGuard ],
     children: [
index 0c5c814c74d9dbd6065e93b924c1d056bca5ca2f..41ff82e98d6febc285dd3ab284febb0a734ba4ea 100644 (file)
@@ -34,9 +34,9 @@ export class VideoChannelsComponent implements OnInit, OnDestroy {
   ngOnInit () {
     this.routeSub = this.route.params
                         .pipe(
-                          map(params => params[ 'videoChannelId' ]),
+                          map(params => params[ 'videoChannelName' ]),
                           distinctUntilChanged(),
-                          switchMap(videoChannelId => this.videoChannelService.getVideoChannel(videoChannelId)),
+                          switchMap(videoChannelName => this.videoChannelService.getVideoChannel(videoChannelName)),
                           catchError(err => this.restExtractor.redirectTo404IfNotFound(err, [ 400, 404 ]))
                         )
                         .subscribe(videoChannel => this.videoChannel = videoChannel)
index 545d6aedab8e257358805f1a87ba87ae4572c188..cff37a7d6f187930eb7622881cdac10ce493aa58 100644 (file)
@@ -43,7 +43,8 @@ const routes: Routes = [
   imports: [
     RouterModule.forRoot(routes, {
       useHash: Boolean(history.pushState) === false,
-      preloadingStrategy: PreloadSelectedModulesList
+      preloadingStrategy: PreloadSelectedModulesList,
+      anchorScrolling: 'enabled'
     })
   ],
   providers: [
index 03f7e88ed6a9249ad5fa4ce827dbff3e2125ac00..d398d4f351a61574138a0bc28e32ba6d47a3aff8 100644 (file)
 
       <footer class="row">
         <a href="https://joinpeertube.org" title="PeerTube website" target="_blank" rel="noopener noreferrer">PeerTube v{{ serverVersion }}{{ serverCommit }}</a>&nbsp;-&nbsp;
-        <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" title="PeerTube license" target="_blank" rel="noopener noreferrer">CopyLeft 2015-2018</a>
+        <a href="https://github.com/Chocobozzz/PeerTube/blob/develop/LICENSE" title="PeerTube license" target="_blank" rel="noopener noreferrer">CopyLeft 2015-2019</a>
       </footer>
     </div>
   </div>
 </div>
 
 <ngx-loading-bar [includeSpinner]="false"></ngx-loading-bar>
+
 <my-confirm></my-confirm>
-<simple-notifications [options]="notificationOptions"></simple-notifications>
+
+<p-toast position="bottom-right">
+  <ng-template let-message pTemplate="message">
+    <div class="notification-block">
+      <div class="message">
+        <h3>{{ message.summary }}</h3>
+        <p>{{ message.detail }}</p>
+      </div>
+
+      <span *ngIf="message.severity === 'success'" class="glyphicon glyphicon-ok"></span>
+      <span *ngIf="message.severity === 'info'" class="glyphicon glyphicon-info-sign"></span>
+      <span *ngIf="message.severity === 'error'" class="glyphicon glyphicon-remove"></span>
+    </div>
+  </ng-template>
+</p-toast>
index b51a81eb1e41abb0f27ca9d8662a5a4319457792..881f3ff318fa4f3e8ab94930ec313f3adf7fe3bb 100644 (file)
@@ -91,8 +91,3 @@ footer {
   height: $footer-height;
   justify-content: center;
 }
-
-simple-notifications {
-  position: relative;
-  z-index: 1500;
-}
index dc4d0bf6a39b94685ebbe418533c2755ab9cf137..7583fdee88712a0b1e0a5655646b8eaa61100a9d 100644 (file)
@@ -15,19 +15,6 @@ import { fromEvent } from 'rxjs'
   styleUrls: [ './app.component.scss' ]
 })
 export class AppComponent implements OnInit {
-  notificationOptions = {
-    timeOut: 5000,
-    lastOnBottom: true,
-    clickToClose: true,
-    maxLength: 0,
-    maxStack: 7,
-    showProgressBar: false,
-    pauseOnHover: false,
-    preventDuplicates: false,
-    preventLastDuplicates: 'visible',
-    rtl: false
-  }
-
   isMenuDisplayed = true
   isMenuChangedByUser = false
 
index 37119944227d427107d979862c023594f84fac76..0bbc2e08b18ead9f25c12ea0a1554c79b4362d9a 100644 (file)
@@ -12,13 +12,12 @@ import { AppComponent } from './app.component'
 import { CoreModule } from './core'
 import { HeaderComponent } from './header'
 import { LoginModule } from './login'
-import { MenuComponent } from './menu'
+import { AvatarNotificationComponent, LanguageChooserComponent, MenuComponent } from './menu'
 import { SharedModule } from './shared'
 import { SignupModule } from './signup'
 import { VideosModule } from './videos'
 import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n'
 import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
-import { LanguageChooserComponent } from '@app/menu/language-chooser.component'
 import { SearchModule } from '@app/search'
 
 export function metaFactory (serverService: ServerService): MetaLoader {
@@ -40,6 +39,7 @@ export function metaFactory (serverService: ServerService): MetaLoader {
 
     MenuComponent,
     LanguageChooserComponent,
+    AvatarNotificationComponent,
     HeaderComponent
   ],
   imports: [
index acd13d9c560ab435b78bad1de1020ec5f4c46909..abb11fdc271aa1c38709f9a704544fd5abad9b44 100644 (file)
@@ -1,8 +1,9 @@
 import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
 import { UserRight } from '../../../../../shared/models/users/user-right.enum'
+import { User as ServerUserModel } from '../../../../../shared/models/users/user.model'
 // Do not use the barrel (dependency loop)
 import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
-import { User, UserConstructorHash } from '../../shared/users/user.model'
+import { User } from '../../shared/users/user.model'
 import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
 
 export type TokenOptions = {
@@ -70,6 +71,7 @@ export class AuthUser extends User {
     ID: 'id',
     ROLE: 'role',
     EMAIL: 'email',
+    VIDEOS_HISTORY_ENABLED: 'videos-history-enabled',
     USERNAME: 'username',
     NSFW_POLICY: 'nsfw_policy',
     WEBTORRENT_ENABLED: 'peertube-videojs-' + 'webtorrent_enabled',
@@ -89,7 +91,8 @@ export class AuthUser extends User {
           role: parseInt(peertubeLocalStorage.getItem(this.KEYS.ROLE), 10) as UserRole,
           nsfwPolicy: peertubeLocalStorage.getItem(this.KEYS.NSFW_POLICY) as NSFWPolicyType,
           webTorrentEnabled: peertubeLocalStorage.getItem(this.KEYS.WEBTORRENT_ENABLED) === 'true',
-          autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true'
+          autoPlayVideo: peertubeLocalStorage.getItem(this.KEYS.AUTO_PLAY_VIDEO) === 'true',
+          videosHistoryEnabled: peertubeLocalStorage.getItem(this.KEYS.VIDEOS_HISTORY_ENABLED) === 'true'
         },
         Tokens.load()
       )
@@ -104,12 +107,13 @@ export class AuthUser extends User {
     peertubeLocalStorage.removeItem(this.KEYS.ROLE)
     peertubeLocalStorage.removeItem(this.KEYS.NSFW_POLICY)
     peertubeLocalStorage.removeItem(this.KEYS.WEBTORRENT_ENABLED)
+    peertubeLocalStorage.removeItem(this.KEYS.VIDEOS_HISTORY_ENABLED)
     peertubeLocalStorage.removeItem(this.KEYS.AUTO_PLAY_VIDEO)
     peertubeLocalStorage.removeItem(this.KEYS.EMAIL)
     Tokens.flush()
   }
 
-  constructor (userHash: UserConstructorHash, hashTokens: TokenOptions) {
+  constructor (userHash: Partial<ServerUserModel>, hashTokens: TokenOptions) {
     super(userHash)
     this.tokens = new Tokens(hashTokens)
   }
index 443772c9e7b2d6f74813ae0711b41635912662f4..eaa822e0f99aea41852099b1175807e96928be07 100644 (file)
@@ -3,18 +3,18 @@ import { catchError, map, mergeMap, share, tap } from 'rxjs/operators'
 import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core/notification/notifier.service'
 import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared'
 import { User } from '../../../../../shared/models/users'
 import { UserLogin } from '../../../../../shared/models/users/user-login.model'
 import { environment } from '../../../environments/environment'
-import { RestExtractor } from '../../shared/rest'
+import { RestExtractor } from '../../shared/rest/rest-extractor.service'
 import { AuthStatus } from './auth-status.model'
 import { AuthUser } from './auth-user.model'
 import { objectToUrlEncoded } from '@app/shared/misc/utils'
 import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { HotkeysService, Hotkey } from 'angular2-hotkeys'
+import { Hotkey, HotkeysService } from 'angular2-hotkeys'
 
 interface UserLoginWithUsername extends UserLogin {
   access_token: string
@@ -38,7 +38,6 @@ export class AuthService {
   loginChangedSource: Observable<AuthStatus>
   userInformationLoaded = new ReplaySubject<boolean>(1)
   hotkeys: Hotkey[]
-  redirectUrl: string
 
   private clientId: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_ID)
   private clientSecret: string = peertubeLocalStorage.getItem(AuthService.LOCAL_STORAGE_OAUTH_CLIENT_KEYS.CLIENT_SECRET)
@@ -48,7 +47,7 @@ export class AuthService {
 
   constructor (
     private http: HttpClient,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private hotkeysService: HotkeysService,
     private restExtractor: RestExtractor,
     private router: Router,
@@ -106,9 +105,8 @@ export class AuthService {
               )
             }
 
-            // We put a bigger timeout
-            // This is an important message
-            this.notificationsService.error(this.i18n('Error'), errorMessage, { timeOut: 7000 })
+            // We put a bigger timeout: this is an important message
+            this.notifier.error(errorMessage, this.i18n('Error'), 7000)
           }
         )
   }
@@ -178,8 +176,6 @@ export class AuthService {
     this.setStatus(AuthStatus.LoggedOut)
 
     this.hotkeysService.remove(this.hotkeys)
-
-    this.redirectUrl = null
   }
 
   refreshAccessToken () {
index bc7bfec0eed3c5b1f07fa982836f2c3e414844fb..8e5caa7ed23d00a8127b7ed886e6561cc8766463 100644 (file)
@@ -1,4 +1,3 @@
 export * from './auth-status.model'
 export * from './auth-user.model'
 export * from './auth.service'
-export * from '../routing/login-guard.service'
index 44aabfc130c38095f981404bf75f640e82f8adc1..aca591e1aceb2272280855264aaa19e2f2025d66 100644 (file)
@@ -1,2 +1 @@
-export * from './confirm.component'
 export * from './confirm.service'
index df2ec696d1a620f758276a4e754527e64014b2bf..4ef3b1e735dda920d20469332fd16171303a53dd 100644 (file)
@@ -7,16 +7,18 @@ import { LoadingBarModule } from '@ngx-loading-bar/core'
 import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
 import { LoadingBarRouterModule } from '@ngx-loading-bar/router'
 
-import { SimpleNotificationsModule } from 'angular2-notifications'
-
 import { AuthService } from './auth'
-import { ConfirmComponent, ConfirmService } from './confirm'
+import { ConfirmService } from './confirm'
 import { throwIfAlreadyLoaded } from './module-import-guard'
 import { LoginGuard, RedirectService, UserRightGuard } from './routing'
 import { ServerService } from './server'
 import { ThemeService } from './theme'
 import { HotkeyModule } from 'angular2-hotkeys'
-import { CheatSheetComponent } from '@app/core/hotkeys'
+import { CheatSheetComponent } from './hotkeys'
+import { ToastModule } from 'primeng/toast'
+import { Notifier } from './notification'
+import { MessageService } from 'primeng/api'
+import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service'
 
 @NgModule({
   imports: [
@@ -25,11 +27,10 @@ import { CheatSheetComponent } from '@app/core/hotkeys'
     FormsModule,
     BrowserAnimationsModule,
 
-    SimpleNotificationsModule.forRoot(),
-
     LoadingBarHttpClientModule,
     LoadingBarRouterModule,
-    LoadingBarModule.forRoot(),
+    LoadingBarModule,
+    ToastModule,
 
     HotkeyModule.forRoot({
       cheatSheetCloseEsc: true
@@ -37,16 +38,15 @@ import { CheatSheetComponent } from '@app/core/hotkeys'
   ],
 
   declarations: [
-    ConfirmComponent,
     CheatSheetComponent
   ],
 
   exports: [
-    SimpleNotificationsModule,
     LoadingBarHttpClientModule,
     LoadingBarModule,
 
-    ConfirmComponent,
+    ToastModule,
+
     CheatSheetComponent
   ],
 
@@ -57,7 +57,10 @@ import { CheatSheetComponent } from '@app/core/hotkeys'
     ThemeService,
     LoginGuard,
     UserRightGuard,
-    RedirectService
+    RedirectService,
+    Notifier,
+    MessageService,
+    UserNotificationSocket
   ]
 })
 export class CoreModule {
index 524589d74268e5afe1bc5223c5ca2f84fd6fef48..f664aff41d482a55859863d3d25cd6c588651090 100644 (file)
@@ -2,6 +2,7 @@ export * from './auth'
 export * from './confirm'
 export * from './routing'
 export * from './server'
+export * from './notification'
 export * from './theme'
 
 export * from './core.module'
diff --git a/client/src/app/core/notification/index.ts b/client/src/app/core/notification/index.ts
new file mode 100644 (file)
index 0000000..3e8d9ea
--- /dev/null
@@ -0,0 +1,2 @@
+export * from './notifier.service'
+export * from './user-notification-socket.service'
diff --git a/client/src/app/core/notification/notifier.service.ts b/client/src/app/core/notification/notifier.service.ts
new file mode 100644 (file)
index 0000000..9833c65
--- /dev/null
@@ -0,0 +1,41 @@
+import { Injectable } from '@angular/core'
+import { MessageService } from 'primeng/api'
+import { I18n } from '@ngx-translate/i18n-polyfill'
+
+@Injectable()
+export class Notifier {
+  readonly TIMEOUT = 5000
+
+  constructor (
+    private i18n: I18n,
+    private messageService: MessageService) {
+  }
+
+  info (text: string, title?: string, timeout?: number) {
+    if (!title) title = this.i18n('Info')
+
+    return this.notify('info', text, title, timeout)
+  }
+
+  error (text: string, title?: string, timeout?: number) {
+    if (!title) title = this.i18n('Error')
+
+    return this.notify('error', text, title, timeout)
+  }
+
+  success (text: string, title?: string, timeout?: number) {
+    if (!title) title = this.i18n('Success')
+
+    return this.notify('success', text, title, timeout)
+  }
+
+  private notify (severity: 'success' | 'info' | 'warn' | 'error', text: string, title: string, timeout?: number) {
+    this.messageService.add({
+      severity,
+      summary: title,
+      detail: text,
+      closable: true,
+      life: timeout || this.TIMEOUT
+    })
+  }
+}
diff --git a/client/src/app/core/notification/user-notification-socket.service.ts b/client/src/app/core/notification/user-notification-socket.service.ts
new file mode 100644 (file)
index 0000000..f367d9a
--- /dev/null
@@ -0,0 +1,41 @@
+import { Injectable } from '@angular/core'
+import { environment } from '../../../environments/environment'
+import { UserNotification as UserNotificationServer } from '../../../../../shared'
+import { Subject } from 'rxjs'
+import * as io from 'socket.io-client'
+import { AuthService } from '../auth'
+
+export type NotificationEvent = 'new' | 'read' | 'read-all'
+
+@Injectable()
+export class UserNotificationSocket {
+  private notificationSubject = new Subject<{ type: NotificationEvent, notification?: UserNotificationServer }>()
+
+  private socket: SocketIOClient.Socket
+
+  constructor (
+    private auth: AuthService
+  ) {}
+
+  dispatch (type: NotificationEvent, notification?: UserNotificationServer) {
+    this.notificationSubject.next({ type, notification })
+  }
+
+  getMyNotificationsSocket () {
+    const socket = this.getSocket()
+
+    socket.on('new-notification', (n: UserNotificationServer) => this.dispatch('new', n))
+
+    return this.notificationSubject.asObservable()
+  }
+
+  private getSocket () {
+    if (this.socket) return this.socket
+
+    this.socket = io(environment.apiUrl + '/user-notifications', {
+      query: { accessToken: this.auth.getAccessToken() }
+    })
+
+    return this.socket
+  }
+}
index 40ff8f5059491177fd9695e01ec22c675aed81cc..7b1c37ee8929976022d60ff1592d4ef9fcf28e58 100644 (file)
@@ -1,11 +1,5 @@
 import { Injectable } from '@angular/core'
-import {
-  ActivatedRouteSnapshot,
-  CanActivateChild,
-  RouterStateSnapshot,
-  CanActivate,
-  Router
-} from '@angular/router'
+import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router'
 
 import { AuthService } from '../auth/auth.service'
 
@@ -20,8 +14,6 @@ export class LoginGuard implements CanActivate, CanActivateChild {
   canActivate (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
     if (this.auth.isLoggedIn() === true) return true
 
-    this.auth.redirectUrl = state.url
-
     this.router.navigate([ '/login' ])
     return false
   }
index 1881be117e4a9d20bc4a9e87ee25241474a26468..e1db4097b87ff35934529538a2c86487ae22e9a8 100644 (file)
@@ -1,5 +1,5 @@
 import { Injectable } from '@angular/core'
-import { Router } from '@angular/router'
+import { NavigationEnd, Router } from '@angular/router'
 import { ServerService } from '../server'
 
 @Injectable()
@@ -8,6 +8,9 @@ export class RedirectService {
   static INIT_DEFAULT_ROUTE = '/videos/trending'
   static DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE
 
+  private previousUrl: string
+  private currentUrl: string
+
   constructor (
     private router: Router,
     private serverService: ServerService
@@ -18,6 +21,7 @@ export class RedirectService {
       RedirectService.DEFAULT_ROUTE = config.instance.defaultClientRoute
     }
 
+    // Load default route
     this.serverService.configLoaded
         .subscribe(() => {
           const defaultRouteConfig = this.serverService.getConfig().instance.defaultClientRoute
@@ -26,6 +30,21 @@ export class RedirectService {
             RedirectService.DEFAULT_ROUTE = defaultRouteConfig
           }
         })
+
+    // Track previous url
+    this.currentUrl = this.router.url
+    router.events.subscribe(event => {
+      if (event instanceof NavigationEnd) {
+        this.previousUrl = this.currentUrl
+        this.currentUrl = event.url
+      }
+    })
+  }
+
+  redirectToPreviousRoute () {
+    if (this.previousUrl) return this.router.navigateByUrl(this.previousUrl)
+
+    return this.redirectToHomepage()
   }
 
   redirectToHomepage (skipLocationChange = false) {
index 65d0299770fa763832064029eb385a95aad354fd..50c3d8c19e59f9c7b412ad2d694230045d3124e1 100644 (file)
@@ -7,7 +7,7 @@ import {
   Router
 } from '@angular/router'
 
-import { AuthService } from '../auth'
+import { AuthService } from '../auth/auth.service'
 
 @Injectable()
 export class UserRightGuard implements CanActivate, CanActivateChild {
index da8bd26db2be69931ab2c25fa92a280b8fbd62fb..4ae72427b9ecf8c7b727ad38c12cdd55e00a1171 100644 (file)
@@ -13,6 +13,7 @@ import { sortBy } from '@app/shared/misc/utils'
 
 @Injectable()
 export class ServerService {
+  private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server/'
   private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/'
   private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
   private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
@@ -37,6 +38,12 @@ export class ServerService {
         css: ''
       }
     },
+    email: {
+      enabled: false
+    },
+    contactForm: {
+      enabled: false
+    },
     serverVersion: 'Unknown',
     signup: {
       allowed: false,
@@ -80,6 +87,11 @@ export class ServerService {
           enabled: false
         }
       }
+    },
+    trending: {
+      videos: {
+        intervalDays: 0
+      }
     }
   }
   private videoCategories: Array<VideoConstant<number>> = []
@@ -141,10 +153,6 @@ export class ServerService {
     return this.videoPrivacies
   }
 
-  getAbout () {
-    return this.http.get<About>(ServerService.BASE_CONFIG_URL + '/about')
-  }
-
   private loadVideoAttributeEnum (
     attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
     hashToPopulate: VideoConstant<string | number>[],
index c23e0c55d9ef1d213f0f4d8ece69015953fdd49d..46a87c79c09e30a3fcbf94baacd971dcd4000170 100644 (file)
@@ -5,6 +5,6 @@
 <span (click)="doSearch()" class="icon icon-search"></span>
 
 <a class="upload-button" routerLink="/videos/upload">
-  <span class="icon icon-upload"></span>
+  <my-global-icon iconName="upload"></my-global-icon>
   <span i18n class="upload-button-label">Upload</span>
 </a>
index 2f98206650173f0856e859da3552ca5b9d937e99..cea415d9b229001eaeeb91da515d2179caa21a2a 100644 (file)
@@ -6,6 +6,7 @@
   padding-left: 10px;
   margin-right: 15px;
   padding-right: 40px; // For the search icon
+  font-size: 14px;
 
   &::placeholder {
     color: var(--inputPlaceholderColor);
@@ -40,6 +41,7 @@
 .upload-button {
   @include peertube-button-link;
   @include orange-button;
+  @include button-with-icon(22px, 3px, -1px);
 
   margin-right: 25px;
 
     margin-right: 0;
   }
 
-  .icon.icon-upload {
-    @include icon(22px);
-
-    background-image: url('../../assets/images/header/upload-white.svg');
-    height: 24px;
-    vertical-align: middle;
-    margin-right: 6px;
-  }
-
   @media screen and (max-width: 600px) {
     margin-right: 10px;
     padding: 0 10px;
index 93dbed525e22ac7cd3b0f340dfbb0afc852ac235..4efe3fb222600fa02ce820f7a0b39a0e81b916be 100644 (file)
 <ng-template #forgotPasswordModal>
   <div class="modal-header">
     <h4 i18n class="modal-title">Forgot your password</h4>
-    <span class="close" aria-hidden="true" (click)="hideForgotPasswordModal()"></span>
+
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideForgotPasswordModal()"></my-global-icon>
   </div>
 
   <div class="modal-body">
-    <div class="form-group">
+
+    <div *ngIf="isEmailDisabled()" class="alert alert-danger" i18n>
+      We are sorry, you cannot recover you password because your instance administrator did not configure the PeerTube email system.
+    </div>
+
+    <div class="form-group" [hidden]="isEmailDisabled()">
       <label i18n for="forgot-password-email">Email</label>
       <input
         type="email" id="forgot-password-email" i18n-placeholder placeholder="Email address" required
index 7553e64564220907cf323ed7c88f1279893d28c5..fc2442c0e9bb4cddae72e66d487a0d66bec530b5 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
-import { RedirectService, ServerService } from '@app/core'
+import { Notifier, RedirectService, ServerService } from '@app/core'
 import { UserService } from '@app/shared'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../core'
 import { FormReactive } from '../shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -19,7 +18,6 @@ import { Router } from '@angular/router'
 export class LoginComponent extends FormReactive implements OnInit {
   @ViewChild('emailInput') input: ElementRef
   @ViewChild('forgotPasswordModal') forgotPasswordModal: ElementRef
-  @ViewChild('forgotPasswordEmailInput') forgotPasswordEmailInput: ElementRef
 
   error: string = null
   forgotPasswordEmail = ''
@@ -35,7 +33,7 @@ export class LoginComponent extends FormReactive implements OnInit {
     private userService: UserService,
     private serverService: ServerService,
     private redirectService: RedirectService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private i18n: I18n
   ) {
     super()
@@ -45,6 +43,10 @@ export class LoginComponent extends FormReactive implements OnInit {
     return this.serverService.getConfig().signup.allowed === true
   }
 
+  isEmailDisabled () {
+    return this.serverService.getConfig().email.enabled === false
+  }
+
   ngOnInit () {
     this.buildForm({
       username: this.loginValidatorsService.LOGIN_USERNAME,
@@ -61,7 +63,7 @@ export class LoginComponent extends FormReactive implements OnInit {
 
     this.authService.login(username, password)
       .subscribe(
-        () => this.redirect(),
+        () => this.redirectService.redirectToPreviousRoute(),
 
         err => {
           if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.')
@@ -71,15 +73,6 @@ export class LoginComponent extends FormReactive implements OnInit {
       )
   }
 
-  redirect () {
-    const redirect = this.authService.redirectUrl
-    if (redirect) {
-      this.router.navigate([ redirect ])
-    } else {
-      this.redirectService.redirectToHomepage()
-    }
-  }
-
   askResetPassword () {
     this.userService.askResetPassword(this.forgotPasswordEmail)
       .subscribe(
@@ -88,18 +81,14 @@ export class LoginComponent extends FormReactive implements OnInit {
             'An email with the reset password instructions will be sent to {{email}}.',
             { email: this.forgotPasswordEmail }
           )
-          this.notificationsService.success(this.i18n('Success'), message)
+          this.notifier.success(message)
           this.hideForgotPasswordModal()
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
-  onForgotPasswordModalShown () {
-    this.forgotPasswordEmailInput.nativeElement.focus()
-  }
-
   openForgotPasswordModal () {
     this.openedForgotPasswordModal = this.modalService.open(this.forgotPasswordModal)
   }
diff --git a/client/src/app/menu/avatar-notification.component.html b/client/src/app/menu/avatar-notification.component.html
new file mode 100644 (file)
index 0000000..4ef3f0e
--- /dev/null
@@ -0,0 +1,23 @@
+<div
+  [ngbPopover]="popContent" autoClose="outside" placement="bottom-left" container="body" popoverClass="popover-notifications"
+  i18n-title title="View your notifications" class="notification-avatar" #popover="ngbPopover"
+>
+  <div *ngIf="unreadNotifications > 0" class="unread-notifications">{{ unreadNotifications }}</div>
+
+  <img [src]="user.accountAvatarUrl" alt="Avatar" />
+</div>
+
+<ng-template #popContent>
+  <div class="notifications-header">
+    <div i18n>Notifications</div>
+
+    <a
+      i18n-title title="Update your notification preferences" class="glyphicon glyphicon-cog"
+      routerLink="/my-account/settings" fragment="notifications"
+    ></a>
+  </div>
+
+  <my-user-notifications [ignoreLoadingBar]="true" [infiniteScroll]="false" itemsPerPage="10"></my-user-notifications>
+
+  <a class="all-notifications" routerLink="/my-account/notifications" i18n>See all your notifications</a>
+</ng-template>
diff --git a/client/src/app/menu/avatar-notification.component.scss b/client/src/app/menu/avatar-notification.component.scss
new file mode 100644 (file)
index 0000000..e785db7
--- /dev/null
@@ -0,0 +1,91 @@
+@import '_variables';
+@import '_mixins';
+
+/deep/ {
+  .popover-notifications.popover {
+    max-width: none;
+
+    .popover-body {
+      padding: 0;
+      font-size: 14px;
+      font-family: $main-fonts;
+      overflow-y: auto;
+      max-height: 500px;
+      width: 400px;
+      box-shadow: 0 6px 14px rgba(0, 0, 0, 0.30);
+
+      .notifications-header {
+        display: flex;
+        justify-content: space-between;
+
+        background-color: rgba(0, 0, 0, 0.10);
+        align-items: center;
+        padding: 0 10px;
+        font-size: 16px;
+        height: 50px;
+
+        a {
+          @include disable-default-a-behaviour;
+
+          color: rgba(20, 20, 20, 0.5);
+
+          &:hover {
+            color: rgba(20, 20, 20, 0.8);
+          }
+        }
+      }
+
+      .all-notifications {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        font-weight: $font-semibold;
+        color: var(--mainForegroundColor);
+        padding: 7px 0;
+      }
+    }
+  }
+}
+
+.notification-avatar {
+  cursor: pointer;
+  position: relative;
+
+  img,
+  .unread-notifications {
+    margin-left: 20px;
+  }
+
+  img {
+    @include avatar(34px);
+
+    margin-right: 10px;
+  }
+
+  .unread-notifications {
+    position: absolute;
+    top: -5px;
+    left: -5px;
+
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    background-color: var(--mainColor);
+    color: var(#fff);
+    font-size: 10px;
+    font-weight: $font-semibold;
+
+    border-radius: 15px;
+    width: 15px;
+    height: 15px;
+  }
+}
+
+@media screen and (max-width: $mobile-view) {
+  /deep/ {
+    .popover-notifications.popover .popover-body {
+      width: 400px;
+    }
+  }
+}
diff --git a/client/src/app/menu/avatar-notification.component.ts b/client/src/app/menu/avatar-notification.component.ts
new file mode 100644 (file)
index 0000000..f1af080
--- /dev/null
@@ -0,0 +1,65 @@
+import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'
+import { User } from '../shared/users/user.model'
+import { UserNotificationService } from '@app/shared/users/user-notification.service'
+import { Subscription } from 'rxjs'
+import { Notifier, UserNotificationSocket } from '@app/core'
+import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
+import { NavigationEnd, Router } from '@angular/router'
+import { filter } from 'rxjs/operators'
+
+@Component({
+  selector: 'my-avatar-notification',
+  templateUrl: './avatar-notification.component.html',
+  styleUrls: [ './avatar-notification.component.scss' ]
+})
+export class AvatarNotificationComponent implements OnInit, OnDestroy {
+  @ViewChild('popover') popover: NgbPopover
+  @Input() user: User
+
+  unreadNotifications = 0
+
+  private notificationSub: Subscription
+  private routeSub: Subscription
+
+  constructor (
+    private userNotificationService: UserNotificationService,
+    private userNotificationSocket: UserNotificationSocket,
+    private notifier: Notifier,
+    private router: Router
+  ) {}
+
+  ngOnInit () {
+    this.userNotificationService.countUnreadNotifications()
+      .subscribe(
+        result => {
+          this.unreadNotifications = Math.min(result, 99) // Limit number to 99
+          this.subscribeToNotifications()
+        },
+
+        err => this.notifier.error(err.message)
+      )
+
+    this.routeSub = this.router.events
+                        .pipe(filter(event => event instanceof NavigationEnd))
+                        .subscribe(() => this.closePopover())
+  }
+
+  ngOnDestroy () {
+    if (this.notificationSub) this.notificationSub.unsubscribe()
+    if (this.routeSub) this.routeSub.unsubscribe()
+  }
+
+  closePopover () {
+    this.popover.close()
+  }
+
+  private subscribeToNotifications () {
+    this.notificationSub = this.userNotificationSocket.getMyNotificationsSocket()
+                               .subscribe(data => {
+                                 if (data.type === 'new') return this.unreadNotifications++
+                                 if (data.type === 'read') return this.unreadNotifications--
+                                 if (data.type === 'read-all') return this.unreadNotifications = 0
+                               })
+  }
+
+}
index 421271c12982e4a352397c7767afc40e9d46389d..39dbde750c714c636a84c53a02363c93ccd13665 100644 (file)
@@ -1 +1,3 @@
+export * from './language-chooser.component'
+export * from './avatar-notification.component'
 export * from './menu.component'
index c37bf28262acba3714f7080e9c598f7a04203789..a62b33dda19cfc6ecdb4d3f70957f81ef18c9686 100644 (file)
@@ -1,9 +1,14 @@
 <ng-template #modal let-hide="close">
   <div class="modal-header">
     <h4 i18n class="modal-title">Change the language</h4>
-    <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
+
+  <a i18n class="help-to-translate" target="_blank" rel="noreferrer noopener" href="https://github.com/Chocobozzz/PeerTube/blob/develop/support/doc/translation.md">
+    Help to translate PeerTube!
+  </a>
+
   <div class="modal-body">
     <a *ngFor="let lang of languages" [href]="buildLanguageLink(lang)">{{ lang.label }}</a>
   </div>
index 944e86f461d3ee0e350d74f359b22f56dd39ae7e..72deb3952e431e09ff994c694ac2bb4eff9d380a 100644 (file)
@@ -1,6 +1,11 @@
 @import '_variables';
 @import '_mixins';
 
+.help-to-translate {
+  @include peertube-button-link;
+  @include orange-button;
+}
+
 .modal-body {
   text-align: center;
 
@@ -9,4 +14,4 @@
     font-size: 16px;
     margin: 15px;
   }
-}
\ No newline at end of file
+}
index e04bdf3d644bdbcb1958b2540f2868b797f6599f..aa5bfa9c9b42e8e0310dca68452a260034c04491 100644 (file)
@@ -2,9 +2,7 @@
   <menu>
     <div class="top-menu">
       <div *ngIf="isLoggedIn" class="logged-in-block">
-        <a routerLink="/my-account/settings">
-          <img [src]="user.accountAvatarUrl" alt="Avatar" />
-        </a>
+        <my-avatar-notification [user]="user"></my-avatar-notification>
 
         <div class="logged-in-info">
           <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a>
@@ -97,4 +95,4 @@
   </menu>
 </div>
 
-<my-language-chooser #languageChooserModal></my-language-chooser>
\ No newline at end of file
+<my-language-chooser #languageChooserModal></my-language-chooser>
index b271ebfd28b6b0b36c1176fb731a8b38ed23f0b8..f30b8941391b76446aa31b7fe8902e1ccb0e92f4 100644 (file)
@@ -16,7 +16,7 @@ menu {
   height: 100%;
   white-space: nowrap;
   text-overflow: ellipsis;
-  overflow: hidden;
+  overflow: auto;
   color: var(--menuForegroundColor);
   display: flex;
   flex-direction: column;
@@ -39,13 +39,6 @@ menu {
     justify-content: center;
     margin-bottom: 35px;
 
-    img {
-      @include avatar(34px);
-
-      margin-left: 20px;
-      margin-right: 10px;
-    }
-
     .logged-in-info {
       flex-grow: 1;
 
@@ -250,7 +243,7 @@ menu {
   }
 }
 
-@media screen and (max-width: 400px) {
+@media screen and (max-width: $mobile-view) {
   .menu-wrapper {
     width: 100% !important;
   }
index af1298de613d225d00bf50212e57ec3fd0fdc3ae..07b93ee7383ca28a29cb31d3345d403c3ab601d0 100644 (file)
@@ -1,8 +1,7 @@
 import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
-import { UserService, UserValidatorsService } from '@app/shared'
-import { NotificationsService } from 'angular2-notifications'
-import { FormReactive } from '../shared'
+import { UserService, UserValidatorsService, FormReactive } from '@app/shared'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { ResetPasswordValidatorsService } from '@app/shared/forms/form-validators/reset-password-validators.service'
@@ -22,7 +21,7 @@ export class ResetPasswordComponent extends FormReactive implements OnInit {
     private resetPasswordValidatorsService: ResetPasswordValidatorsService,
     private userValidatorsService: UserValidatorsService,
     private userService: UserService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private router: Router,
     private route: ActivatedRoute,
     private i18n: I18n
@@ -40,7 +39,7 @@ export class ResetPasswordComponent extends FormReactive implements OnInit {
     this.verificationString = this.route.snapshot.queryParams['verificationString']
 
     if (!this.userId || !this.verificationString) {
-      this.notificationsService.error(this.i18n('Error'), this.i18n('Unable to find user id or verification string.'))
+      this.notifier.error(this.i18n('Unable to find user id or verification string.'))
       this.router.navigate([ '/' ])
     }
   }
@@ -49,11 +48,11 @@ export class ResetPasswordComponent extends FormReactive implements OnInit {
     this.userService.resetPassword(this.userId, this.verificationString, this.form.value.password)
       .subscribe(
         () => {
-          this.notificationsService.success(this.i18n('Success'), this.i18n('Your password has been successfully reset!'))
+          this.notifier.success(this.i18n('Your password has been successfully reset!'))
           this.router.navigate([ '/login' ])
         },
 
-        err => this.notificationsService.error('Error', err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
index 8d7f84ac1ba5758b5294ddd2a995c45d5fe7fc0d..3fdc6df35c9d8cdab0e7aba2c5ddf4c9802d68b2 100644 (file)
@@ -1,10 +1,6 @@
 import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
-import { ActivatedRoute } from '@angular/router'
-import { RedirectService, ServerService } from '@app/core'
-import { NotificationsService } from 'angular2-notifications'
-import { SearchService } from '@app/search/search.service'
+import { ServerService } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { MetaService } from '@ngx-meta/core'
 import { AdvancedSearch } from '@app/search/advanced-search.model'
 import { VideoConstant } from '../../../../shared'
 
index 3a87ea1dee5bccef0c5a258f0d3ad2ef1bd690d8..82a5f0f26bbd62e779c58ca278512b70f17d0a2d 100644 (file)
@@ -48,7 +48,7 @@
     </div>
 
     <div *ngIf="isVideo(result)" class="entry video">
-      <my-video-thumbnail [video]="result"></my-video-thumbnail>
+      <my-video-thumbnail [video]="result" [nsfw]="isVideoBlur(result)"></my-video-thumbnail>
 
       <div class="video-info">
         <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', result.uuid]" [attr.title]="result.name">{{ result.name }}</a>
index 3e074621b8545a403f9c7a419812d0d529afc2af..6de13d27624b9d3da851192d4ddc97c27ed53543 100644 (file)
           text-overflow: ellipsis;
           white-space: nowrap;
           font-size: 14px;
-          color: #585858;
+          color: $grey-foreground-color;
 
           &:hover {
-            color: #303030;
+            color: $grey-foreground-hover-color;
           }
         }
       }
index 3d17e6d96fdc8425f7171d516ddefb3b4876e483..c4a4b1fdebf7a472916c81bc22f1c86e10e8cd27 100644 (file)
@@ -1,7 +1,6 @@
 import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
-import { AuthService } from '@app/core'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier, ServerService } from '@app/core'
 import { forkJoin, Subscription } from 'rxjs'
 import { SearchService } from '@app/search/search.service'
 import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
@@ -40,9 +39,10 @@ export class SearchComponent implements OnInit, OnDestroy {
     private route: ActivatedRoute,
     private router: Router,
     private metaService: MetaService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private searchService: SearchService,
-    private authService: AuthService
+    private authService: AuthService,
+    private serverService: ServerService
   ) { }
 
   ngOnInit () {
@@ -68,7 +68,7 @@ export class SearchComponent implements OnInit, OnDestroy {
         this.search()
       },
 
-      err => this.notificationsService.error('Error', err.text)
+      err => this.notifier.error(err.text)
     )
   }
 
@@ -76,6 +76,10 @@ export class SearchComponent implements OnInit, OnDestroy {
     if (this.subActivatedRoute) this.subActivatedRoute.unsubscribe()
   }
 
+  isVideoBlur (video: Video) {
+    return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig())
+  }
+
   isVideoChannel (d: VideoChannel | Video): d is VideoChannel {
     return d instanceof VideoChannel
   }
@@ -112,9 +116,7 @@ export class SearchComponent implements OnInit, OnDestroy {
           this.firstSearch = false
         },
 
-        error => {
-          this.notificationsService.error(this.i18n('Error'), error.message)
-        }
+        err => this.notifier.error(err.message)
       )
 
   }
index 811afb4497a38dcf006522a29d5c086551cd38bf..adecec1fce53405ac73d23d6952fe2242995afb0 100644 (file)
@@ -16,7 +16,7 @@ export abstract class Actor implements ActorServer {
 
   avatarUrl: string
 
-  static GET_ACTOR_AVATAR_URL (actor: { avatar: Avatar }) {
+  static GET_ACTOR_AVATAR_URL (actor: { avatar?: { path: string } }) {
     const absoluteAPIUrl = getAbsoluteAPIUrl()
 
     if (actor && actor.avatar) return absoluteAPIUrl + actor.avatar.path
index 90651f2170b316367b691763bce8f9dfe73f3252..114b1d71f68ab2953c9ccfeac092ee3b7186e2c6 100644 (file)
@@ -3,7 +3,7 @@
     class="action-button" [ngClass]="{ small: buttonSize === 'small', grey: theme === 'grey', orange: theme === 'orange' }"
     ngbDropdownToggle role="button"
   >
-    <span *ngIf="!label" class="icon icon-action"></span>
+    <my-global-icon *ngIf="!label" class="more-icon" iconName="more"></my-global-icon>
     <span *ngIf="label" class="dropdown-toggle">{{ label }}</span>
   </div>
 
index a4fcceeee25f5203f1b2f2b6a23563a6c89e7aa7..985b2ca8844da898537e0e4a69b544295c71abbe 100644 (file)
   }
 
   &:hover, &:active, &:focus {
-    background-color: $grey-color;
+    background-color: $grey-background-color;
   }
 
-  .icon-action {
-    @include icon(21px);
-
-    background-image: url('../../../assets/images/video/more.svg');
-    top: -1px;
+  .more-icon {
+    width: 21px;
   }
 
   &.small {
index 87a8daccfefabba66d9b8f40c19d6bcb541bb3e4..b6df671029e3f21948b465e4da8189a1e4acc820 100644 (file)
@@ -1,4 +1,4 @@
 <span class="action-button" [ngClass]="className" [title]="getTitle()">
-  <span class="icon" [ngClass]="icon"></span>
+  <my-global-icon [iconName]="icon"></my-global-icon>
   <span class="button-label">{{ label }}</span>
 </span>
index 168102f09e36378f7884fdd3972ac6e450e9026f..04199a2a95c31e3dea21078dc36951405fae7157 100644 (file)
@@ -3,41 +3,18 @@
 
 .action-button {
   @include peertube-button-link;
+  @include button-with-icon(21px, 0, -2px);
 
-  font-size: 15px;
   font-weight: $font-semibold;
-  color: #585858;
-  background-color: #E5E5E5;
+  color: $grey-foreground-color;
+  background-color: $grey-background-color;
 
   &:hover {
-    background-color: #EFEFEF;
+    background-color: $grey-background-hover-color;
   }
 
-  .icon {
-    @include icon(21px);
-
-    position: relative;
-    top: -2px;
-
-    &.icon-edit {
-      background-image: url('../../../assets/images/global/edit-grey.svg');
-    }
-
-    &.icon-delete-grey {
-      background-image: url('../../../assets/images/global/delete-grey.svg');
-    }
-
-    &.icon-im-with-her {
-      background-image: url('../../../assets/images/global/im-with-her.svg');
-    }
-
-    &.icon-tick {
-      background-image: url('../../../assets/images/global/tick.svg');
-    }
-
-    &.icon-cross {
-      background-image: url('../../../assets/images/global/cross.svg');
-    }
+  my-global-icon {
+    @include apply-svg-color($grey-foreground-color);
   }
 }
 
index 1a1162f0901aa87cfb3cb75ced66464d235b3aa6..a91e9c7eb1b140606494d1ffb3a84aee5e40b7e4 100644 (file)
@@ -1,4 +1,5 @@
 import { Component, Input } from '@angular/core'
+import { GlobalIconName } from '@app/shared/icons/global-icon.component'
 
 @Component({
   selector: 'my-button',
@@ -9,7 +10,7 @@ import { Component, Input } from '@angular/core'
 export class ButtonComponent {
   @Input() label = ''
   @Input() className: string = undefined
-  @Input() icon: string = undefined
+  @Input() icon: GlobalIconName = undefined
   @Input() title: string = undefined
 
   getTitle () {
index 6c55d810493538c46fc9578f4e40469ddd13e2bf..4d12a84c06f20801b70ba04c2f80d1c1609c1dfa 100644 (file)
@@ -1,5 +1,5 @@
 <span class="action-button action-button-delete" [title]="getTitle()" role="button">
-  <span class="icon icon-delete-grey"></span>
+  <my-global-icon iconName="delete"></my-global-icon>
 
   <span class="button-label" *ngIf="label">{{ label }}</span>
   <span class="button-label" i18n *ngIf="!label">Delete</span>
index cecb780f384e1292de1f6f5e5a74ad70397b0228..da3addbae2a517a91816e12f0afc5f71b9bbee97 100644 (file)
@@ -1,5 +1,5 @@
 <a class="action-button action-button-edit" [routerLink]="routerLink" i18n-title title="Edit">
-  <span class="icon icon-edit"></span>
+  <my-global-icon iconName="edit"></my-global-icon>
 
   <span class="button-label" *ngIf="label">{{ label }}</span>
   <span i18n class="button-label" *ngIf="!label">Edit</span>
similarity index 87%
rename from client/src/app/core/confirm/confirm.component.html
rename to client/src/app/shared/confirm/confirm.component.html
index 43f0c61907c096fbbd2a76726e5166127424759d..65df1cd4dfab6967cc5d1dbe88b1976c1a47b7a6 100644 (file)
@@ -2,7 +2,8 @@
 
   <div class="modal-header">
     <h4 class="modal-title">{{ title }}</h4>
-    <span class="close" aria-label="Close" role="button" (click)="dismiss()"></span>
+
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="dismiss()"></my-global-icon>
   </div>
 
   <div class="modal-body" >
similarity index 96%
rename from client/src/app/core/confirm/confirm.component.ts
rename to client/src/app/shared/confirm/confirm.component.ts
index 5138b78483e4a204971bc22a243fec3425d8ebe8..63c163da68061c6582ef7d628f470cb959a3ccf5 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'
-import { ConfirmService } from './confirm.service'
+import { ConfirmService } from '@app/core/confirm/confirm.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
index 0bb7d25e6f5c606da996ae66195930b9262f14bc..b9873af2c44aef9cde215933d265ec48ec7631b2 100644 (file)
@@ -1,11 +1,9 @@
 import { FormGroup } from '@angular/forms'
 import { BuildFormArgument, BuildFormDefaultValues, FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 
-export type FormReactiveErrors = { [ id: string ]: string }
+export type FormReactiveErrors = { [ id: string ]: string | FormReactiveErrors }
 export type FormReactiveValidationMessages = {
-  [ id: string ]: {
-    [ name: string ]: string
-  }
+  [ id: string ]: { [ name: string ]: string } | FormReactiveValidationMessages
 }
 
 export abstract class FormReactive {
@@ -13,7 +11,7 @@ export abstract class FormReactive {
   protected formChanged = false
 
   form: FormGroup
-  formErrors: FormReactiveErrors
+  formErrors: any // To avoid casting in template because of string | FormReactiveErrors
   validationMessages: FormReactiveValidationMessages
 
   buildForm (obj: BuildFormArgument, defaultValues: BuildFormDefaultValues = {}) {
@@ -23,29 +21,49 @@ export abstract class FormReactive {
     this.formErrors = formErrors
     this.validationMessages = validationMessages
 
-    this.form.valueChanges.subscribe(() => this.onValueChanged(false))
+    this.form.valueChanges.subscribe(() => this.onValueChanged(this.form, this.formErrors, this.validationMessages, false))
+  }
+
+  protected forceCheck () {
+    return this.onValueChanged(this.form, this.formErrors, this.validationMessages, true)
+  }
+
+  protected check () {
+    return this.onValueChanged(this.form, this.formErrors, this.validationMessages, false)
   }
 
-  protected onValueChanged (forceCheck = false) {
-    for (const field in this.formErrors) {
+  private onValueChanged (
+    form: FormGroup,
+    formErrors: FormReactiveErrors,
+    validationMessages: FormReactiveValidationMessages,
+    forceCheck = false
+  ) {
+    for (const field of Object.keys(formErrors)) {
+      if (formErrors[field] && typeof formErrors[field] === 'object') {
+        this.onValueChanged(
+          form.controls[field] as FormGroup,
+          formErrors[field] as FormReactiveErrors,
+          validationMessages[field] as FormReactiveValidationMessages,
+          forceCheck
+        )
+        continue
+      }
+
       // clear previous error message (if any)
-      this.formErrors[ field ] = ''
-      const control = this.form.get(field)
+      formErrors[ field ] = ''
+      const control = form.get(field)
 
       if (control.dirty) this.formChanged = true
 
       // Don't care if dirty on force check
       const isDirty = control.dirty || forceCheck === true
       if (control && isDirty && !control.valid) {
-        const messages = this.validationMessages[ field ]
+        const messages = validationMessages[ field ]
         for (const key in control.errors) {
-          this.formErrors[ field ] += messages[ key ] + ' '
+          formErrors[ field ] += messages[ key ] + ' '
         }
       }
     }
   }
 
-  protected forceCheck () {
-    return this.onValueChanged(true)
-  }
 }
index 19a8bef25c6f008f4c030e90144ebdab44adc8be..249fdf1198bb418e6de8464ec4a9dfa856b6c111 100644 (file)
@@ -7,10 +7,10 @@ export type BuildFormValidator = {
   MESSAGES: { [ name: string ]: string }
 }
 export type BuildFormArgument = {
-  [ id: string ]: BuildFormValidator
+  [ id: string ]: BuildFormValidator | BuildFormArgument
 }
 export type BuildFormDefaultValues = {
-  [ name: string ]: string | string[]
+  [ name: string ]: string | string[] | BuildFormDefaultValues
 }
 
 @Injectable()
@@ -29,7 +29,16 @@ export class FormValidatorService {
       formErrors[name] = ''
 
       const field = obj[name]
-      if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES
+      if (this.isRecursiveField(field)) {
+        const result = this.buildForm(field as BuildFormArgument, defaultValues[name] as BuildFormDefaultValues)
+        group[name] = result.form
+        formErrors[name] = result.formErrors
+        validationMessages[name] = result.validationMessages
+
+        continue
+      }
+
+      if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string }
 
       const defaultValue = defaultValues[name] || ''
 
@@ -52,13 +61,27 @@ export class FormValidatorService {
       formErrors[name] = ''
 
       const field = obj[name]
-      if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES
+      if (this.isRecursiveField(field)) {
+        this.updateForm(
+          form[name],
+          formErrors[name] as FormReactiveErrors,
+          validationMessages[name] as FormReactiveValidationMessages,
+          obj[name] as BuildFormArgument,
+          defaultValues[name] as BuildFormDefaultValues
+        )
+        continue
+      }
+
+      if (field && field.MESSAGES) validationMessages[name] = field.MESSAGES as { [ name: string ]: string }
 
       const defaultValue = defaultValues[name] || ''
 
-      if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS))
+      if (field && field.VALIDATORS) form.addControl(name, new FormControl(defaultValue, field.VALIDATORS as ValidatorFn[]))
       else form.addControl(name, new FormControl(defaultValue))
     }
   }
 
+  private isRecursiveField (field: any) {
+    return field && typeof field === 'object' && !field.MESSAGES && !field.VALIDATORS
+  }
 }
index 74e385b3d769d868ccaa6d7b23915782109422f1..fdcbedb71c026e17992aa35259488efdcf831caf 100644 (file)
@@ -1,6 +1,7 @@
 export * from './custom-config-validators.service'
 export * from './form-validator.service'
 export * from './host'
+export * from './instance-validators.service'
 export * from './login-validators.service'
 export * from './reset-password-validators.service'
 export * from './user-validators.service'
diff --git a/client/src/app/shared/forms/form-validators/instance-validators.service.ts b/client/src/app/shared/forms/form-validators/instance-validators.service.ts
new file mode 100644 (file)
index 0000000..5bb8528
--- /dev/null
@@ -0,0 +1,48 @@
+import { I18n } from '@ngx-translate/i18n-polyfill'
+import { Validators } from '@angular/forms'
+import { BuildFormValidator } from '@app/shared'
+import { Injectable } from '@angular/core'
+
+@Injectable()
+export class InstanceValidatorsService {
+  readonly FROM_EMAIL: BuildFormValidator
+  readonly FROM_NAME: BuildFormValidator
+  readonly BODY: BuildFormValidator
+
+  constructor (private i18n: I18n) {
+
+    this.FROM_EMAIL = {
+      VALIDATORS: [ Validators.required, Validators.email ],
+      MESSAGES: {
+        'required': this.i18n('Email is required.'),
+        'email': this.i18n('Email must be valid.')
+      }
+    }
+
+    this.FROM_NAME = {
+      VALIDATORS: [
+        Validators.required,
+        Validators.minLength(1),
+        Validators.maxLength(120)
+      ],
+      MESSAGES: {
+        'required': this.i18n('Your name is required.'),
+        'minlength': this.i18n('Your name must be at least 1 character long.'),
+        'maxlength': this.i18n('Your name cannot be more than 120 characters long.')
+      }
+    }
+
+    this.BODY = {
+      VALIDATORS: [
+        Validators.required,
+        Validators.minLength(3),
+        Validators.maxLength(5000)
+      ],
+      MESSAGES: {
+        'required': this.i18n('A message is required.'),
+        'minlength': this.i18n('The message must be at least 3 characters long.'),
+        'maxlength': this.i18n('The message cannot be more than 5000 characters long.')
+      }
+    }
+  }
+}
index d14fa47779b9460d137756c7c0bda4c84e8569d0..6589b25804ed73eda6d7fa10130085fad51d4cd3 100644 (file)
@@ -23,15 +23,15 @@ export class UserValidatorsService {
     this.USER_USERNAME = {
       VALIDATORS: [
         Validators.required,
-        Validators.minLength(3),
-        Validators.maxLength(20),
-        Validators.pattern(/^[a-z0-9._]+$/)
+        Validators.minLength(1),
+        Validators.maxLength(50),
+        Validators.pattern(/^[a-z0-9][a-z0-9._]*$/)
       ],
       MESSAGES: {
         'required': this.i18n('Username is required.'),
-        'minlength': this.i18n('Username must be at least 3 characters long.'),
-        'maxlength': this.i18n('Username cannot be more than 20 characters long.'),
-        'pattern': this.i18n('Username should be only lowercase alphanumeric characters.')
+        'minlength': this.i18n('Username must be at least 1 character long.'),
+        'maxlength': this.i18n('Username cannot be more than 50 characters long.'),
+        'pattern': this.i18n('Username should be lowercase alphanumeric; dots and underscores are allowed.')
       }
     }
 
@@ -88,13 +88,13 @@ export class UserValidatorsService {
     this.USER_DISPLAY_NAME = {
       VALIDATORS: [
         Validators.required,
-        Validators.minLength(3),
-        Validators.maxLength(120)
+        Validators.minLength(1),
+        Validators.maxLength(50)
       ],
       MESSAGES: {
         'required': this.i18n('Display name is required.'),
-        'minlength': this.i18n('Display name must be at least 3 characters long.'),
-        'maxlength': this.i18n('Display name cannot be more than 120 characters long.')
+        'minlength': this.i18n('Display name must be at least 1 character long.'),
+        'maxlength': this.i18n('Display name cannot be more than 50 characters long.')
       }
     }
 
index 6e98066118176661732c11e88674ce68bec5d5d8..fcc966b84add0ff38d4799d8f953ff705a1e50f6 100644 (file)
@@ -10,20 +10,20 @@ export class VideoAbuseValidatorsService {
 
   constructor (private i18n: I18n) {
     this.VIDEO_ABUSE_REASON = {
-      VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
+      VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
       MESSAGES: {
         'required': this.i18n('Report reason is required.'),
         'minlength': this.i18n('Report reason must be at least 2 characters long.'),
-        'maxlength': this.i18n('Report reason cannot be more than 300 characters long.')
+        'maxlength': this.i18n('Report reason cannot be more than 3000 characters long.')
       }
     }
 
     this.VIDEO_ABUSE_MODERATION_COMMENT = {
-      VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(300) ],
+      VALIDATORS: [ Validators.required, Validators.minLength(2), Validators.maxLength(3000) ],
       MESSAGES: {
         'required': this.i18n('Moderation comment is required.'),
         'minlength': this.i18n('Moderation comment must be at least 2 characters long.'),
-        'maxlength': this.i18n('Moderation comment cannot be more than 300 characters long.')
+        'maxlength': this.i18n('Moderation comment cannot be more than 3000 characters long.')
       }
     }
   }
index f62ff65f776b49cbaa4ba33b04dc6434d9d0ed66..1c519c10acb02710a2e1fdbedd244647009df4b0 100644 (file)
@@ -14,28 +14,28 @@ export class VideoChannelValidatorsService {
     this.VIDEO_CHANNEL_NAME = {
       VALIDATORS: [
         Validators.required,
-        Validators.minLength(3),
-        Validators.maxLength(20),
-        Validators.pattern(/^[a-z0-9._]+$/)
+        Validators.minLength(1),
+        Validators.maxLength(50),
+        Validators.pattern(/^[a-z0-9][a-z0-9._]*$/)
       ],
       MESSAGES: {
         'required': this.i18n('Name is required.'),
-        'minlength': this.i18n('Name must be at least 3 characters long.'),
-        'maxlength': this.i18n('Name cannot be more than 20 characters long.'),
-        'pattern': this.i18n('Name should be only lowercase alphanumeric characters.')
+        'minlength': this.i18n('Name must be at least 1 character long.'),
+        'maxlength': this.i18n('Name cannot be more than 50 characters long.'),
+        'pattern': this.i18n('Name should be lowercase alphanumeric; dots and underscores are allowed.')
       }
     }
 
     this.VIDEO_CHANNEL_DISPLAY_NAME = {
       VALIDATORS: [
         Validators.required,
-        Validators.minLength(3),
-        Validators.maxLength(120)
+        Validators.minLength(1),
+        Validators.maxLength(50)
       ],
       MESSAGES: {
         'required': i18n('Display name is required.'),
-        'minlength': i18n('Display name must be at least 3 characters long.'),
-        'maxlength': i18n('Display name cannot be more than 120 characters long.')
+        'minlength': i18n('Display name must be at least 1 character long.'),
+        'maxlength': i18n('Display name cannot be more than 50 characters long.')
       }
     }
 
index b99169ed28bbe209207571fc1505863b8e9822f1..e87aca0d4b402d9fb0c0e8ba2c35a6a97c5cea78 100644 (file)
@@ -1,10 +1,10 @@
 import { debounceTime, distinctUntilChanged } from 'rxjs/operators'
 import { Component, forwardRef, Input, OnInit } from '@angular/core'
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
-import { MarkdownService } from '@app/videos/shared'
 import { Subject } from 'rxjs'
 import truncate from 'lodash-es/truncate'
 import { ScreenService } from '@app/shared/misc/screen.service'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-markdown-textarea',
index fb3006b5328a82eaf964f1bfb2d3144f9ed12a6c..7b8bcf60170b3e529ad37a0cfbd91e6a754af51f 100644 (file)
@@ -1,10 +1,10 @@
 <div class="root">
   <label class="form-group-checkbox">
-    <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="isDisabled" />
+    <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" />
     <span role="checkbox" [attr.aria-checked]="checked"></span>
     <span *ngIf="labelText">{{ labelText }}</span>
     <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span>
   </label>
 
   <my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help>
-</div>
\ No newline at end of file
+</div>
index bbc9904dfe8aa2ace1b58c28c289828a15849dcc..c1a6915e8613c901ef780a237411d1cb46c4604e 100644 (file)
@@ -19,8 +19,7 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor {
   @Input() labelText: string
   @Input() labelHtml: string
   @Input() helpHtml: string
-
-  isDisabled = false
+  @Input() disabled = false
 
   propagateChange = (_: any) => { /* empty */ }
 
@@ -41,6 +40,6 @@ export class PeertubeCheckboxComponent implements ControlValueAccessor {
   }
 
   setDisabledState (isDisabled: boolean) {
-    this.isDisabled = isDisabled
+    this.disabled = isDisabled
   }
 }
index 8d22aa56ce91fa0ebf72fcff548878b16d6d8e65..f60c38e8de8052a4b23d20547fc634906975b161 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@angular/core'
 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 
 @Component({
@@ -30,7 +30,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
   private file: File
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private i18n: I18n
   ) {}
 
@@ -49,7 +49,18 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
       const [ file ] = event.target.files
 
       if (file.size > this.maxFileSize) {
-        this.notificationsService.error(this.i18n('Error'), this.i18n('This file is too large.'))
+        this.notifier.error(this.i18n('This file is too large.'))
+        return
+      }
+
+      const extension = '.' + file.name.split('.').pop()
+      if (this.extensions.includes(extension) === false) {
+        const message = this.i18n(
+          'PeerTube cannot handle this kind of file. Accepted extensions are {{extensions}}.',
+          { extensions: this.allowedExtensionsMessage }
+        )
+        this.notifier.error(message)
+
         return
       }
 
diff --git a/client/src/app/shared/icons/global-icon.component.html b/client/src/app/shared/icons/global-icon.component.html
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/app/shared/icons/global-icon.component.scss b/client/src/app/shared/icons/global-icon.component.scss
new file mode 100644 (file)
index 0000000..6805fb6
--- /dev/null
@@ -0,0 +1,4 @@
+/deep/ svg {
+  width: inherit;
+  height: inherit;
+}
diff --git a/client/src/app/shared/icons/global-icon.component.ts b/client/src/app/shared/icons/global-icon.component.ts
new file mode 100644 (file)
index 0000000..e8ada03
--- /dev/null
@@ -0,0 +1,48 @@
+import { Component, ElementRef, Input, OnInit } from '@angular/core'
+
+const icons = {
+  'add': require('../../../assets/images/global/add.html'),
+  'syndication': require('../../../assets/images/global/syndication.html'),
+  'help': require('../../../assets/images/global/help.html'),
+  'sparkle': require('../../../assets/images/global/sparkle.html'),
+  'alert': require('../../../assets/images/global/alert.html'),
+  'cloud-error': require('../../../assets/images/global/cloud-error.html'),
+  'user-add': require('../../../assets/images/global/user-add.html'),
+  'no': require('../../../assets/images/global/no.html'),
+  'cloud-download': require('../../../assets/images/global/cloud-download.html'),
+  'undo': require('../../../assets/images/global/undo.html'),
+  'circle-tick': require('../../../assets/images/global/circle-tick.html'),
+  'cog': require('../../../assets/images/global/cog.html'),
+  'download': require('../../../assets/images/global/download.html'),
+  'edit': require('../../../assets/images/global/edit.html'),
+  'im-with-her': require('../../../assets/images/global/im-with-her.html'),
+  'delete': require('../../../assets/images/global/delete.html'),
+  'cross': require('../../../assets/images/global/cross.html'),
+  'validate': require('../../../assets/images/global/validate.html'),
+  'tick': require('../../../assets/images/global/tick.html'),
+  'dislike': require('../../../assets/images/video/dislike.html'),
+  'heart': require('../../../assets/images/video/heart.html'),
+  'like': require('../../../assets/images/video/like.html'),
+  'more': require('../../../assets/images/video/more.html'),
+  'share': require('../../../assets/images/video/share.html'),
+  'upload': require('../../../assets/images/video/upload.html')
+}
+
+export type GlobalIconName = keyof typeof icons
+
+@Component({
+  selector: 'my-global-icon',
+  template: '',
+  styleUrls: [ './global-icon.component.scss' ]
+})
+export class GlobalIconComponent implements OnInit {
+  @Input() iconName: GlobalIconName
+
+  constructor (private el: ElementRef) {}
+
+  ngOnInit () {
+    const nativeElement = this.el.nativeElement
+
+    nativeElement.innerHTML = icons[this.iconName]
+  }
+}
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts
new file mode 100644 (file)
index 0000000..61321ec
--- /dev/null
@@ -0,0 +1,36 @@
+import { catchError } from 'rxjs/operators'
+import { HttpClient } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import { environment } from '../../../environments/environment'
+import { RestExtractor, RestService } from '../rest'
+import { About } from '../../../../../shared/models/server'
+
+@Injectable()
+export class InstanceService {
+  private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config'
+  private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server'
+
+  constructor (
+    private authHttp: HttpClient,
+    private restService: RestService,
+    private restExtractor: RestExtractor
+  ) {
+  }
+
+  getAbout () {
+    return this.authHttp.get<About>(InstanceService.BASE_CONFIG_URL + '/about')
+               .pipe(catchError(res => this.restExtractor.handleError(res)))
+  }
+
+  contactAdministrator (fromEmail: string, fromName: string, message: string) {
+    const body = {
+      fromEmail,
+      fromName,
+      body: message
+    }
+
+    return this.authHttp.post(InstanceService.BASE_SERVER_URL + '/contact', body)
+               .pipe(catchError(res => this.restExtractor.handleError(res)))
+
+  }
+}
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.html b/client/src/app/shared/menu/top-menu-dropdown.component.html
new file mode 100644 (file)
index 0000000..d3c8960
--- /dev/null
@@ -0,0 +1,21 @@
+<div class="sub-menu">
+  <ng-container *ngFor="let menuEntry of menuEntries">
+
+    <a *ngIf="menuEntry.routerLink" [routerLink]="menuEntry.routerLink" routerLinkActive="active" class="title-page">{{ menuEntry.label }}</a>
+
+    <div *ngIf="!menuEntry.routerLink" ngbDropdown class="parent-entry" #dropdown="ngbDropdown" (mouseleave)="closeDropdownIfHovered(dropdown)">
+      <span
+        (mouseenter)="openDropdownOnHover(dropdown)" [ngClass]="{ active: !!suffixLabels[menuEntry.label] }" ngbDropdownAnchor
+        (click)="dropdownAnchorClicked(dropdown)" role="button" class="title-page"
+      >
+        <ng-container i18n>{{ menuEntry.label }}</ng-container>
+        <ng-container *ngIf="!!suffixLabels[menuEntry.label]"> - {{ suffixLabels[menuEntry.label] }}</ng-container>
+      </span>
+
+      <div ngbDropdownMenu>
+        <a *ngFor="let menuChild of menuEntry.children" class="dropdown-item" [routerLink]="menuChild.routerLink">{{ menuChild.label }}</a>
+      </div>
+    </div>
+
+  </ng-container>
+</div>
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.scss b/client/src/app/shared/menu/top-menu-dropdown.component.scss
new file mode 100644 (file)
index 0000000..7715953
--- /dev/null
@@ -0,0 +1,18 @@
+.parent-entry {
+  span[role=button] {
+    cursor: pointer;
+  }
+
+  a {
+    display: block;
+  }
+}
+
+/deep/ .dropdown-toggle::after {
+  position: relative;
+  top: 2px;
+}
+
+/deep/ .dropdown-menu {
+  margin-top: 0 !important;
+}
diff --git a/client/src/app/shared/menu/top-menu-dropdown.component.ts b/client/src/app/shared/menu/top-menu-dropdown.component.ts
new file mode 100644 (file)
index 0000000..e859c30
--- /dev/null
@@ -0,0 +1,83 @@
+import { Component, Input, OnDestroy, OnInit } from '@angular/core'
+import { filter, take } from 'rxjs/operators'
+import { NavigationEnd, Router } from '@angular/router'
+import { Subscription } from 'rxjs'
+import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
+
+export type TopMenuDropdownParam = {
+  label: string
+  routerLink?: string
+
+  children?: {
+    label: string
+    routerLink: string
+  }[]
+}
+
+@Component({
+  selector: 'my-top-menu-dropdown',
+  templateUrl: './top-menu-dropdown.component.html',
+  styleUrls: [ './top-menu-dropdown.component.scss' ]
+})
+export class TopMenuDropdownComponent implements OnInit, OnDestroy {
+  @Input() menuEntries: TopMenuDropdownParam[] = []
+
+  suffixLabels: { [ parentLabel: string ]: string }
+
+  private openedOnHover = false
+  private routeSub: Subscription
+
+  constructor (private router: Router) {}
+
+  ngOnInit () {
+    this.updateChildLabels(window.location.pathname)
+
+    this.routeSub = this.router.events
+                        .pipe(filter(event => event instanceof NavigationEnd))
+                        .subscribe(() => this.updateChildLabels(window.location.pathname))
+  }
+
+  ngOnDestroy () {
+    if (this.routeSub) this.routeSub.unsubscribe()
+  }
+
+  openDropdownOnHover (dropdown: NgbDropdown) {
+    this.openedOnHover = true
+    dropdown.open()
+
+    // Menu was closed
+    dropdown.openChange
+            .pipe(take(1))
+            .subscribe(e => this.openedOnHover = false)
+  }
+
+  dropdownAnchorClicked (dropdown: NgbDropdown) {
+    if (this.openedOnHover) {
+      this.openedOnHover = false
+      return
+    }
+
+    return dropdown.toggle()
+  }
+
+  closeDropdownIfHovered (dropdown: NgbDropdown) {
+    if (this.openedOnHover === false) return
+
+    dropdown.close()
+    this.openedOnHover = false
+  }
+
+  private updateChildLabels (path: string) {
+    this.suffixLabels = {}
+
+    for (const entry of this.menuEntries) {
+      if (!entry.children) continue
+
+      for (const child of entry.children) {
+        if (path.startsWith(child.routerLink)) {
+          this.suffixLabels[entry.label] = child.label
+        }
+      }
+    }
+  }
+}
index 28ccb1e26170fc1c78fc04067dbb2455810ef462..444425c9f0a65dc7b32d11fff876eed3965333e0 100644 (file)
   container="body"
   title="Get help"
   i18n-title
+  popoverClass="help-popover"
   [attr.aria-pressed]="isPopoverOpened"
   [ngbPopover]="tooltipTemplate"
   [placement]="tooltipPlacement"
   [autoClose]="true"
   (onHidden)="onPopoverHidden()"
   (onShown)="onPopoverShown()"
-></span>
+>
+  <my-global-icon iconName="help"></my-global-icon>
+</span>
index 5c73a80310de606871e4ac868b83b886f34f93d3..3898f3cda96005f9bfb57c11806fb9f2dfef4083 100644 (file)
@@ -2,29 +2,40 @@
 @import '_mixins';
 
 .help-tooltip-button {
-  @include icon(17px);
-
-  position: relative;
-  top: -2px;
-  background-image: url('../../../assets/images/global/help.svg');
+  cursor: pointer;
   border: none;
-  margin: 5px;
+
+  my-global-icon {
+    width: 17px;
+    position: relative;
+    top: -2px;
+    margin: 5px;
+
+    @include apply-svg-color(var(--mainForegroundColor))
+  }
 }
 
 /deep/ {
-  .popover-body {
-    text-align: left;
-    padding: 10px;
+  .help-popover {
     max-width: 300px;
 
-    font-size: 13px;
-    font-family: $main-fonts;
-    background-color: #fff;
-    color: #000;
-    box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
+    .popover-body {
+      font-family: $main-fonts;
+      text-align: left;
+      padding: 10px;
+      font-size: 13px;
+      background-color: var(--mainBackgroundColor);
+      color: var(--mainForegroundColor);
+      box-shadow: 0 0 6px rgba(0, 0, 0, 0.5);
+
+      p {
+        margin-bottom: 0;
+      }
 
-    ul {
-      padding-left: 20px;
+      ul {
+        padding-left: 20px;
+        margin-bottom: 0;
+      }
     }
   }
 }
index ba0452e778393dd2ffdb5047a42660c5f8403c2d..f3426f70ff4246a2e8aff0ac3ce1167f06a3c454 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, Input, OnChanges, OnInit } from '@angular/core'
-import { MarkdownService } from '@app/videos/shared'
 import { I18n } from '@ngx-translate/i18n-polyfill'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-help',
index 78e8e968250951bec4c61f5c291e2aa37328628f..7cc6055c2ae6c3b355ef7ba0940b77988332232a 100644 (file)
@@ -102,12 +102,18 @@ function objectToFormData (obj: any, form?: FormData, namespace?: string) {
   return fd
 }
 
-function lineFeedToHtml (obj: any, keyToNormalize: string) {
+function objectLineFeedToHtml (obj: any, keyToNormalize: string) {
   return immutableAssign(obj, {
-    [keyToNormalize]: obj[keyToNormalize].replace(/\r?\n|\r/g, '<br />')
+    [keyToNormalize]: lineFeedToHtml(obj[keyToNormalize])
   })
 }
 
+function lineFeedToHtml (text: string) {
+  if (!text) return text
+
+  return text.replace(/\r?\n|\r/g, '<br />')
+}
+
 function removeElementFromArray <T> (arr: T[], elem: T) {
   const index = arr.indexOf(elem)
   if (index !== -1) arr.splice(index, 1)
@@ -131,6 +137,7 @@ function scrollToTop () {
 export {
   sortBy,
   durationToString,
+  lineFeedToHtml,
   objectToUrlEncoded,
   getParameterByName,
   populateAsyncUserVideoChannels,
@@ -138,7 +145,7 @@ export {
   dateToHuman,
   immutableAssign,
   objectToFormData,
-  lineFeedToHtml,
+  objectLineFeedToHtml,
   removeElementFromArray,
   scrollToTop
 }
index fa5cb740472c4f3f358636faeb2ce7a9b81183d5..f38ea543d2efebf1aeed715544a5d8253156b784 100644 (file)
@@ -1,7 +1,8 @@
 <ng-template #modal>
   <div class="modal-header">
     <h4 i18n class="modal-title">Ban</h4>
-    <span class="close" aria-hidden="true" (click)="hideBanUserModal()"></span>
+
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body">
@@ -19,7 +20,7 @@
       </div>
 
       <div class="form-group inputs">
-        <span i18n class="action-button action-button-cancel" (click)="hideBanUserModal()">Cancel</span>
+        <span i18n class="action-button action-button-cancel" (click)="hide()">Cancel</span>
 
         <input
           type="submit" i18n-value value="Ban this user" class="action-button-submit"
@@ -29,4 +30,4 @@
     </form>
   </div>
 
-</ng-template>
\ No newline at end of file
+</ng-template>
index 60bd442dd1354cbe526eabca58fc756f5dd466c0..94276530125cfb836d35c0e72caf4a463e908836 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
@@ -23,7 +23,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
   constructor (
     protected formValidatorService: FormValidatorService,
     private modalService: NgbModal,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private userValidatorsService: UserValidatorsService,
     private i18n: I18n
@@ -42,7 +42,7 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
     this.openedModal = this.modalService.open(this.modal)
   }
 
-  hideBanUserModal () {
+  hide () {
     this.usersToBan = undefined
     this.openedModal.close()
   }
@@ -57,13 +57,13 @@ export class UserBanModalComponent extends FormReactive implements OnInit {
             ? this.i18n('{{num}} users banned.', { num: this.usersToBan.length })
             : this.i18n('User {{username}} banned.', { username: this.usersToBan.username })
 
-          this.notificationsService.success(this.i18n('Success'), message)
+          this.notifier.success(message)
 
           this.userBanned.emit(this.usersToBan)
-          this.hideBanUserModal()
+          this.hide()
         },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
       )
   }
 
index d391246e00ec4affb7f2bcbb406dbada059af4e6..9a2461ebf3f2db695c15b458e37f8f4bb04d287d 100644 (file)
@@ -1,10 +1,9 @@
 import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { DropdownAction } from '@app/shared/buttons/action-dropdown.component'
 import { UserBanModalComponent } from '@app/shared/moderation/user-ban-modal.component'
 import { UserService } from '@app/shared/users'
-import { AuthService, ConfirmService, ServerService } from '@app/core'
+import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
 import { User, UserRight } from '../../../../../shared/models/users'
 import { Account } from '@app/shared/account/account.model'
 import { BlocklistService } from '@app/shared/blocklist'
@@ -30,7 +29,7 @@ export class UserModerationDropdownComponent implements OnChanges {
 
   constructor (
     private authService: AuthService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private serverService: ServerService,
     private userService: UserService,
@@ -48,7 +47,7 @@ export class UserModerationDropdownComponent implements OnChanges {
 
   openBanUserModal (user: User) {
     if (user.username === 'root') {
-      this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot ban root.'))
+      this.notifier.error(this.i18n('You cannot ban root.'))
       return
     }
 
@@ -67,21 +66,18 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.userService.unbanUsers(user)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('User {{username}} unbanned.', { username: user.username })
-            )
+            this.notifier.success(this.i18n('User {{username}} unbanned.', { username: user.username }))
 
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
   async removeUser (user: User) {
     if (user.username === 'root') {
-      this.notificationsService.error(this.i18n('Error'), this.i18n('You cannot delete root.'))
+      this.notifier.error(this.i18n('You cannot delete root.'))
       return
     }
 
@@ -91,29 +87,23 @@ export class UserModerationDropdownComponent implements OnChanges {
 
     this.userService.removeUser(user).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('User {{username}} deleted.', { username: user.username })
-        )
+        this.notifier.success(this.i18n('User {{username}} deleted.', { username: user.username }))
         this.userDeleted.emit()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
   setEmailAsVerified (user: User) {
     this.userService.updateUser(user.id, { emailVerified: true }).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('User {{username}} email set as verified', { username: user.username })
-        )
+        this.notifier.success(this.i18n('User {{username}} email set as verified', { username: user.username }))
 
         this.userChanged.emit()
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
@@ -121,16 +111,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.blockAccountByUser(account)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Account {{nameWithHost}} muted.', { nameWithHost: account.nameWithHost })
-            )
+            this.notifier.success(this.i18n('Account {{nameWithHost}} muted.', { nameWithHost: account.nameWithHost }))
 
             this.account.mutedByUser = true
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -138,16 +125,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.unblockAccountByUser(account)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: account.nameWithHost })
-            )
+            this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted.', { nameWithHost: account.nameWithHost }))
 
             this.account.mutedByUser = false
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -155,16 +139,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.blockServerByUser(host)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Instance {{host}} muted.', { host })
-            )
+            this.notifier.success(this.i18n('Instance {{host}} muted.', { host }))
 
             this.account.mutedServerByUser = true
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -172,16 +153,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.unblockServerByUser(host)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Instance {{host}} unmuted.', { host })
-            )
+            this.notifier.success(this.i18n('Instance {{host}} unmuted.', { host }))
 
             this.account.mutedServerByUser = false
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -189,16 +167,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.blockAccountByInstance(account)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost })
-            )
+            this.notifier.success(this.i18n('Account {{nameWithHost}} muted by the instance.', { nameWithHost: account.nameWithHost }))
 
             this.account.mutedByInstance = true
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -206,16 +181,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.unblockAccountByInstance(account)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Account {{nameWithHost}} unmuted by the instance.', { nameWithHost: account.nameWithHost })
-            )
+            this.notifier.success(this.i18n('Account {{nameWithHost}} unmuted by the instance.', { nameWithHost: account.nameWithHost }))
 
             this.account.mutedByInstance = false
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -223,16 +195,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.blockServerByInstance(host)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Instance {{host}} muted by the instance.', { host })
-            )
+            this.notifier.success(this.i18n('Instance {{host}} muted by the instance.', { host }))
 
             this.account.mutedServerByInstance = true
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -240,16 +209,13 @@ export class UserModerationDropdownComponent implements OnChanges {
     this.blocklistService.unblockServerByInstance(host)
         .subscribe(
           () => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Instance {{host}} unmuted by the instance.', { host })
-            )
+            this.notifier.success(this.i18n('Instance {{host}} unmuted by the instance.', { host }))
 
             this.account.mutedServerByInstance = false
             this.userChanged.emit()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
@@ -277,18 +243,18 @@ export class UserModerationDropdownComponent implements OnChanges {
           },
           {
             label: this.i18n('Ban'),
-            handler: ({ user }: { user: User }) => this.openBanUserModal(user),
-            isDisplayed: ({ user }: { user: User }) => !user.blocked
+            handler: ({ user }) => this.openBanUserModal(user),
+            isDisplayed: ({ user }) => !user.blocked
           },
           {
             label: this.i18n('Unban'),
-            handler: ({ user }: { user: User }) => this.unbanUser(user),
-            isDisplayed: ({ user }: { user: User }) => user.blocked
+            handler: ({ user }) => this.unbanUser(user),
+            isDisplayed: ({ user }) => user.blocked
           },
           {
             label: this.i18n('Set Email as Verified'),
-            handler: ({ user }: { user: User }) => this.setEmailAsVerified(user),
-            isDisplayed: ({ user }: { user: User }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false
+            handler: ({ user }) => this.setEmailAsVerified(user),
+            isDisplayed: ({ user }) => this.requiresEmailVerification && !user.blocked && user.emailVerified === false
           }
         ])
       }
@@ -299,23 +265,23 @@ export class UserModerationDropdownComponent implements OnChanges {
         this.userActions.push([
           {
             label: this.i18n('Mute this account'),
-            isDisplayed: ({ account }: { account: Account }) => account.mutedByUser === false,
-            handler: ({ account }: { account: Account }) => this.blockAccountByUser(account)
+            isDisplayed: ({ account }) => account.mutedByUser === false,
+            handler: ({ account }) => this.blockAccountByUser(account)
           },
           {
             label: this.i18n('Unmute this account'),
-            isDisplayed: ({ account }: { account: Account }) => account.mutedByUser === true,
-            handler: ({ account }: { account: Account }) => this.unblockAccountByUser(account)
+            isDisplayed: ({ account }) => account.mutedByUser === true,
+            handler: ({ account }) => this.unblockAccountByUser(account)
           },
           {
             label: this.i18n('Mute the instance'),
-            isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === false,
-            handler: ({ account }: { account: Account }) => this.blockServerByUser(account.host)
+            isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
+            handler: ({ account }) => this.blockServerByUser(account.host)
           },
           {
             label: this.i18n('Unmute the instance'),
-            isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === true,
-            handler: ({ account }: { account: Account }) => this.unblockServerByUser(account.host)
+            isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
+            handler: ({ account }) => this.unblockServerByUser(account.host)
           }
         ])
 
@@ -326,13 +292,13 @@ export class UserModerationDropdownComponent implements OnChanges {
           instanceActions = instanceActions.concat([
             {
               label: this.i18n('Mute this account by your instance'),
-              isDisplayed: ({ account }: { account: Account }) => account.mutedByInstance === false,
-              handler: ({ account }: { account: Account }) => this.blockAccountByInstance(account)
+              isDisplayed: ({ account }) => account.mutedByInstance === false,
+              handler: ({ account }) => this.blockAccountByInstance(account)
             },
             {
               label: this.i18n('Unmute this account by your instance'),
-              isDisplayed: ({ account }: { account: Account }) => account.mutedByInstance === true,
-              handler: ({ account }: { account: Account }) => this.unblockAccountByInstance(account)
+              isDisplayed: ({ account }) => account.mutedByInstance === true,
+              handler: ({ account }) => this.unblockAccountByInstance(account)
             }
           ])
         }
@@ -342,13 +308,13 @@ export class UserModerationDropdownComponent implements OnChanges {
           instanceActions = instanceActions.concat([
             {
               label: this.i18n('Mute the instance by your instance'),
-              isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === false,
-              handler: ({ account }: { account: Account }) => this.blockServerByInstance(account.host)
+              isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === false,
+              handler: ({ account }) => this.blockServerByInstance(account.host)
             },
             {
               label: this.i18n('Unmute the instance by your instance'),
-              isDisplayed: ({ account }: { account: Account }) => !account.userId && account.mutedServerByInstance === true,
-              handler: ({ account }: { account: Account }) => this.unblockServerByInstance(account.host)
+              isDisplayed: ({ account }) => !account.userId && account.mutedServerByInstance === true,
+              handler: ({ account }) => this.unblockServerByInstance(account.host)
             }
           ])
         }
diff --git a/client/src/app/shared/renderer/html-renderer.service.ts b/client/src/app/shared/renderer/html-renderer.service.ts
new file mode 100644 (file)
index 0000000..d49df9b
--- /dev/null
@@ -0,0 +1,35 @@
+import { Injectable } from '@angular/core'
+import { LinkifierService } from '@app/shared/renderer/linkifier.service'
+import * as sanitizeHtml from 'sanitize-html'
+
+@Injectable()
+export class HtmlRendererService {
+
+  constructor (private linkifier: LinkifierService) {
+
+  }
+
+  toSafeHtml (text: string) {
+    // Convert possible markdown to html
+    const html = this.linkifier.linkify(text)
+
+    return sanitizeHtml(html, {
+      allowedTags: [ 'a', 'p', 'span', 'br' ],
+      allowedSchemes: [ 'http', 'https' ],
+      allowedAttributes: {
+        'a': [ 'href', 'class', 'target' ]
+      },
+      transformTags: {
+        a: (tagName, attribs) => {
+          return {
+            tagName,
+            attribs: Object.assign(attribs, {
+              target: '_blank',
+              rel: 'noopener noreferrer'
+            })
+          }
+        }
+      }
+    })
+  }
+}
diff --git a/client/src/app/shared/renderer/index.ts b/client/src/app/shared/renderer/index.ts
new file mode 100644 (file)
index 0000000..39202b3
--- /dev/null
@@ -0,0 +1,3 @@
+export * from './html-renderer.service'
+export * from './linkifier.service'
+export * from './markdown.service'
index 0b8ecc318bd7c866dfaa9d547f39c9bd93f8b573..85160d44559c83ff9d539d55654c8424d5c160de 100644 (file)
@@ -3,3 +3,14 @@ export interface ComponentPagination {
   itemsPerPage: number
   totalItems?: number
 }
+
+export function hasMoreItems (componentPagination: ComponentPagination) {
+  // No results
+  if (componentPagination.totalItems === 0) return false
+
+  // Not loaded yet
+  if (!componentPagination.totalItems) return true
+
+  const maxPage = componentPagination.totalItems / componentPagination.itemsPerPage
+  return maxPage > componentPagination.currentPage
+}
index f149569ef5028b2a5c27f6e92dc0bf432db957ba..e6518dd1d75ba22ed89e8892f8c7f028b7eef507 100644 (file)
@@ -80,6 +80,7 @@ export class RestExtractor {
       errorMessage = errorMessage ? errorMessage : 'Unknown error.'
       console.error(`Backend returned code ${err.status}, errorMessage is: ${errorMessage}`)
     } else {
+      console.error(err)
       errorMessage = err
     }
 
index a2fa27b72ac4209ecb41ad3be09702930da4e5ae..6f8625c7e926f83f8a5eaaf38d60cbb5afa889c7 100644 (file)
@@ -6,7 +6,6 @@ import { RouterModule } from '@angular/router'
 import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
 import { HelpComponent } from '@app/shared/misc/help.component'
 import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
-import { MarkdownService } from '@app/videos/shared'
 
 import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
 import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
@@ -34,6 +33,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import {
   CustomConfigValidatorsService,
+  InstanceValidatorsService,
   LoginValidatorsService,
   ReactiveFileComponent,
   ResetPasswordValidatorsService,
@@ -61,6 +61,14 @@ import { OverviewService } from '@app/shared/overview'
 import { UserBanModalComponent } from '@app/shared/moderation'
 import { UserModerationDropdownComponent } from '@app/shared/moderation/user-moderation-dropdown.component'
 import { BlocklistService } from '@app/shared/blocklist'
+import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.component'
+import { UserHistoryService } from '@app/shared/users/user-history.service'
+import { UserNotificationService } from '@app/shared/users/user-notification.service'
+import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component'
+import { InstanceService } from '@app/shared/instance/instance.service'
+import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/shared/renderer'
+import { ConfirmComponent } from '@app/shared/confirm/confirm.component'
+import { GlobalIconComponent } from '@app/shared/icons/global-icon.component'
 
 @NgModule({
   imports: [
@@ -102,7 +110,11 @@ import { BlocklistService } from '@app/shared/blocklist'
     RemoteSubscribeComponent,
     InstanceFeaturesTableComponent,
     UserBanModalComponent,
-    UserModerationDropdownComponent
+    UserModerationDropdownComponent,
+    TopMenuDropdownComponent,
+    UserNotificationsComponent,
+    ConfirmComponent,
+    GlobalIconComponent
   ],
 
   exports: [
@@ -141,6 +153,10 @@ import { BlocklistService } from '@app/shared/blocklist'
     InstanceFeaturesTableComponent,
     UserBanModalComponent,
     UserModerationDropdownComponent,
+    TopMenuDropdownComponent,
+    UserNotificationsComponent,
+    ConfirmComponent,
+    GlobalIconComponent,
 
     NumberFormatterPipe,
     ObjectLengthPipe,
@@ -157,7 +173,6 @@ import { BlocklistService } from '@app/shared/blocklist'
     UserService,
     VideoService,
     AccountService,
-    MarkdownService,
     VideoChannelService,
     VideoCaptionService,
     VideoImportService,
@@ -177,11 +192,20 @@ import { BlocklistService } from '@app/shared/blocklist'
     OverviewService,
     VideoChangeOwnershipValidatorsService,
     VideoAcceptOwnershipValidatorsService,
+    InstanceValidatorsService,
     BlocklistService,
+    UserHistoryService,
+    InstanceService,
+
+    MarkdownService,
+    LinkifierService,
+    HtmlRendererService,
 
     I18nPrimengCalendarService,
     ScreenService,
 
+    UserNotificationService,
+
     I18n
   ]
 })
index 7a81108cde0e36e2b22ebcb3980180e7ec06a6a0..ba2a45df136897c9dbe95e9dad32be4437cdd643 100644 (file)
@@ -29,7 +29,7 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
   }
 
   onValidKey () {
-    this.onValueChanged()
+    this.check()
     if (!this.form.valid) return
 
     this.formValidated()
@@ -37,7 +37,24 @@ export class RemoteSubscribeComponent extends FormReactive implements OnInit {
 
   formValidated () {
     const address = this.form.value['text']
-    const [ , hostname ] = address.split('@')
-    window.open(`https://${hostname}/authorize_interaction?acct=${this.account}`)
+    const [ username, hostname ] = address.split('@')
+
+    fetch(`https://${hostname}/.well-known/webfinger?resource=acct:${username}@${hostname}`)
+      .then(response => response.json())
+      .then(data => new Promise((resolve, reject) => {
+        if (data && Array.isArray(data.links)) {
+          const link: {
+            template: string
+          } = data.links.find((link: any) =>
+            link && typeof link.template === 'string' && link.rel === 'http://ostatus.org/schema/1.0/subscribe')
+
+          if (link && link.template.includes('{uri}')) {
+            resolve(link.template.replace('{uri}', `acct:${this.account}`))
+          }
+        }
+        reject()
+      }))
+      .then(window.open)
+      .catch(() => window.open(`https://${hostname}/authorize_interaction?acct=${this.account}`))
   }
 }
index 315ea5037d8d13cb3ab1329a90eecea27d0aaf6c..8f1754c7f31ececd86134531f0d391e31b1edf96 100644 (file)
@@ -1,9 +1,8 @@
 import { Component, Input, OnInit } from '@angular/core'
 import { Router } from '@angular/router'
-import { AuthService } from '@app/core'
+import { AuthService, Notifier } from '@app/core'
 import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
 import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
-import { NotificationsService } from 'angular2-notifications'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { VideoService } from '@app/shared/video/video.service'
 import { FeedFormat } from '../../../../../shared/models/feeds'
@@ -23,7 +22,7 @@ export class SubscribeButtonComponent implements OnInit {
   constructor (
     private authService: AuthService,
     private router: Router,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userSubscriptionService: UserSubscriptionService,
     private i18n: I18n,
     private videoService: VideoService
@@ -43,18 +42,17 @@ export class SubscribeButtonComponent implements OnInit {
         .subscribe(
           res => this.subscribed = res[this.uri],
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
     }
   }
 
   subscribe () {
     if (this.isUserLoggedIn()) {
-      this.localSubscribe()
-    } else {
-      this.authService.redirectUrl = this.router.url
-      this.gotoLogin()
+      return this.localSubscribe()
     }
+
+    return this.gotoLogin()
   }
 
   localSubscribe () {
@@ -63,13 +61,13 @@ export class SubscribeButtonComponent implements OnInit {
         () => {
           this.subscribed = true
 
-          this.notificationsService.success(
-            this.i18n('Subscribed'),
-            this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName })
+          this.notifier.success(
+            this.i18n('Subscribed to {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }),
+            this.i18n('Subscribed')
           )
         },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
       )
   }
 
@@ -85,13 +83,13 @@ export class SubscribeButtonComponent implements OnInit {
           () => {
             this.subscribed = false
 
-            this.notificationsService.success(
-              this.i18n('Unsubscribed'),
-              this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName })
+            this.notifier.success(
+              this.i18n('Unsubscribed from {{nameWithHost}}', { nameWithHost: this.videoChannel.displayName }),
+              this.i18n('Unsubscribed')
             )
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
index 7b5a67bc78513926957a988459da0da1a01df486..ebd715fb1ea7cbe9e59b57a67dc867fd6a367cf3 100644 (file)
@@ -1,2 +1,3 @@
 export * from './user.model'
 export * from './user.service'
+export * from './user-notifications.component'
diff --git a/client/src/app/shared/users/user-history.service.ts b/client/src/app/shared/users/user-history.service.ts
new file mode 100644 (file)
index 0000000..9ed25bf
--- /dev/null
@@ -0,0 +1,45 @@
+import { HttpClient, HttpParams } from '@angular/common/http'
+import { Injectable } from '@angular/core'
+import { environment } from '../../../environments/environment'
+import { RestExtractor } from '../rest/rest-extractor.service'
+import { RestService } from '../rest/rest.service'
+import { Video } from '../video/video.model'
+import { catchError, map, switchMap } from 'rxjs/operators'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { VideoService } from '@app/shared/video/video.service'
+import { ResultList } from '../../../../../shared'
+
+@Injectable()
+export class UserHistoryService {
+  static BASE_USER_VIDEOS_HISTORY_URL = environment.apiUrl + '/api/v1/users/me/history/videos'
+
+  constructor (
+    private authHttp: HttpClient,
+    private restExtractor: RestExtractor,
+    private restService: RestService,
+    private videoService: VideoService
+  ) {}
+
+  getUserVideosHistory (historyPagination: ComponentPagination) {
+    const pagination = this.restService.componentPaginationToRestPagination(historyPagination)
+
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, pagination)
+
+    return this.authHttp
+               .get<ResultList<Video>>(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL, { params })
+               .pipe(
+                 switchMap(res => this.videoService.extractVideos(res)),
+                 catchError(err => this.restExtractor.handleError(err))
+               )
+  }
+
+  deleteUserVideosHistory () {
+    return this.authHttp
+               .post(UserHistoryService.BASE_USER_VIDEOS_HISTORY_URL + '/remove', {})
+               .pipe(
+                 map(() => this.restExtractor.extractDataBool()),
+                 catchError(err => this.restExtractor.handleError(err))
+               )
+  }
+}
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts
new file mode 100644 (file)
index 0000000..125d212
--- /dev/null
@@ -0,0 +1,155 @@
+import { UserNotification as UserNotificationServer, UserNotificationType, VideoInfo, ActorInfo } from '../../../../../shared'
+import { Actor } from '@app/shared/actor/actor.model'
+
+export class UserNotification implements UserNotificationServer {
+  id: number
+  type: UserNotificationType
+  read: boolean
+
+  video?: VideoInfo & {
+    channel: ActorInfo & { avatarUrl?: string }
+  }
+
+  videoImport?: {
+    id: number
+    video?: VideoInfo
+    torrentName?: string
+    magnetUri?: string
+    targetUrl?: string
+  }
+
+  comment?: {
+    id: number
+    threadId: number
+    account: ActorInfo & { avatarUrl?: string }
+    video: VideoInfo
+  }
+
+  videoAbuse?: {
+    id: number
+    video: VideoInfo
+  }
+
+  videoBlacklist?: {
+    id: number
+    video: VideoInfo
+  }
+
+  account?: ActorInfo & { avatarUrl?: string }
+
+  actorFollow?: {
+    id: number
+    follower: ActorInfo & { avatarUrl?: string }
+    following: {
+      type: 'account' | 'channel'
+      name: string
+      displayName: string
+    }
+  }
+
+  createdAt: string
+  updatedAt: string
+
+  // Additional fields
+  videoUrl?: string
+  commentUrl?: any[]
+  videoAbuseUrl?: string
+  accountUrl?: string
+  videoImportIdentifier?: string
+  videoImportUrl?: string
+
+  constructor (hash: UserNotificationServer) {
+    this.id = hash.id
+    this.type = hash.type
+    this.read = hash.read
+
+    this.video = hash.video
+    if (this.video) this.setAvatarUrl(this.video.channel)
+
+    this.videoImport = hash.videoImport
+
+    this.comment = hash.comment
+    if (this.comment) this.setAvatarUrl(this.comment.account)
+
+    this.videoAbuse = hash.videoAbuse
+
+    this.videoBlacklist = hash.videoBlacklist
+
+    this.account = hash.account
+    if (this.account) this.setAvatarUrl(this.account)
+
+    this.actorFollow = hash.actorFollow
+    if (this.actorFollow) this.setAvatarUrl(this.actorFollow.follower)
+
+    this.createdAt = hash.createdAt
+    this.updatedAt = hash.updatedAt
+
+    switch (this.type) {
+      case UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION:
+        this.videoUrl = this.buildVideoUrl(this.video)
+        break
+
+      case UserNotificationType.UNBLACKLIST_ON_MY_VIDEO:
+        this.videoUrl = this.buildVideoUrl(this.video)
+        break
+
+      case UserNotificationType.NEW_COMMENT_ON_MY_VIDEO:
+      case UserNotificationType.COMMENT_MENTION:
+        this.accountUrl = this.buildAccountUrl(this.comment.account)
+        this.commentUrl = [ this.buildVideoUrl(this.comment.video), { threadId: this.comment.threadId } ]
+        break
+
+      case UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS:
+        this.videoAbuseUrl = '/admin/moderation/video-abuses/list'
+        this.videoUrl = this.buildVideoUrl(this.videoAbuse.video)
+        break
+
+      case UserNotificationType.BLACKLIST_ON_MY_VIDEO:
+        this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video)
+        break
+
+      case UserNotificationType.MY_VIDEO_PUBLISHED:
+        this.videoUrl = this.buildVideoUrl(this.video)
+        break
+
+      case UserNotificationType.MY_VIDEO_IMPORT_SUCCESS:
+        this.videoImportUrl = this.buildVideoImportUrl()
+        this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport)
+        this.videoUrl = this.buildVideoUrl(this.videoImport.video)
+        break
+
+      case UserNotificationType.MY_VIDEO_IMPORT_ERROR:
+        this.videoImportUrl = this.buildVideoImportUrl()
+        this.videoImportIdentifier = this.buildVideoImportIdentifier(this.videoImport)
+        break
+
+      case UserNotificationType.NEW_USER_REGISTRATION:
+        this.accountUrl = this.buildAccountUrl(this.account)
+        break
+
+      case UserNotificationType.NEW_FOLLOW:
+        this.accountUrl = this.buildAccountUrl(this.actorFollow.follower)
+        break
+    }
+  }
+
+  private buildVideoUrl (video: { uuid: string }) {
+    return '/videos/watch/' + video.uuid
+  }
+
+  private buildAccountUrl (account: { name: string, host: string }) {
+    return '/accounts/' + Actor.CREATE_BY_STRING(account.name, account.host)
+  }
+
+  private buildVideoImportUrl () {
+    return '/my-account/video-imports'
+  }
+
+  private buildVideoImportIdentifier (videoImport: { targetUrl?: string, magnetUri?: string, torrentName?: string }) {
+    return videoImport.targetUrl || videoImport.magnetUri || videoImport.torrentName
+  }
+
+  private setAvatarUrl (actor: { avatarUrl?: string, avatar?: { path: string } }) {
+    actor.avatarUrl = Actor.GET_ACTOR_AVATAR_URL(actor)
+  }
+}
diff --git a/client/src/app/shared/users/user-notification.service.ts b/client/src/app/shared/users/user-notification.service.ts
new file mode 100644 (file)
index 0000000..f8a3095
--- /dev/null
@@ -0,0 +1,86 @@
+import { Injectable } from '@angular/core'
+import { HttpClient, HttpParams } from '@angular/common/http'
+import { RestExtractor, RestService } from '../rest'
+import { catchError, map, tap } from 'rxjs/operators'
+import { environment } from '../../../environments/environment'
+import { ResultList, UserNotification as UserNotificationServer, UserNotificationSetting } from '../../../../../shared'
+import { UserNotification } from './user-notification.model'
+import { AuthService } from '../../core'
+import { ComponentPagination } from '../rest/component-pagination.model'
+import { User } from '..'
+import { UserNotificationSocket } from '@app/core/notification/user-notification-socket.service'
+
+@Injectable()
+export class UserNotificationService {
+  static BASE_NOTIFICATIONS_URL = environment.apiUrl + '/api/v1/users/me/notifications'
+  static BASE_NOTIFICATION_SETTINGS = environment.apiUrl + '/api/v1/users/me/notification-settings'
+
+  constructor (
+    private auth: AuthService,
+    private authHttp: HttpClient,
+    private restExtractor: RestExtractor,
+    private restService: RestService,
+    private userNotificationSocket: UserNotificationSocket
+  ) {}
+
+  listMyNotifications (pagination: ComponentPagination, unread?: boolean, ignoreLoadingBar = false) {
+    let params = new HttpParams()
+    params = this.restService.addRestGetParams(params, this.restService.componentPaginationToRestPagination(pagination))
+
+    if (unread) params = params.append('unread', `${unread}`)
+
+    const headers = ignoreLoadingBar ? { ignoreLoadingBar: '' } : undefined
+
+    return this.authHttp.get<ResultList<UserNotification>>(UserNotificationService.BASE_NOTIFICATIONS_URL, { params, headers })
+               .pipe(
+                 map(res => this.restExtractor.convertResultListDateToHuman(res)),
+                 map(res => this.restExtractor.applyToResultListData(res, this.formatNotification.bind(this))),
+                 catchError(err => this.restExtractor.handleError(err))
+               )
+  }
+
+  countUnreadNotifications () {
+    return this.listMyNotifications({ currentPage: 1, itemsPerPage: 0 }, true)
+      .pipe(map(n => n.total))
+  }
+
+  markAsRead (notification: UserNotification) {
+    const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read'
+
+    const body = { ids: [ notification.id ] }
+    const headers = { ignoreLoadingBar: '' }
+
+    return this.authHttp.post(url, body, { headers })
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 tap(() => this.userNotificationSocket.dispatch('read')),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  markAllAsRead () {
+    const url = UserNotificationService.BASE_NOTIFICATIONS_URL + '/read-all'
+    const headers = { ignoreLoadingBar: '' }
+
+    return this.authHttp.post(url, {}, { headers })
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 tap(() => this.userNotificationSocket.dispatch('read-all')),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  updateNotificationSettings (user: User, settings: UserNotificationSetting) {
+    const url = UserNotificationService.BASE_NOTIFICATION_SETTINGS
+
+    return this.authHttp.put(url, settings)
+               .pipe(
+                 map(this.restExtractor.extractDataBool),
+                 catchError(res => this.restExtractor.handleError(res))
+               )
+  }
+
+  private formatNotification (notification: UserNotificationServer) {
+    return new UserNotification(notification)
+  }
+}
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html
new file mode 100644 (file)
index 0000000..0d69e0f
--- /dev/null
@@ -0,0 +1,101 @@
+<div *ngIf="componentPagination.totalItems === 0" class="no-notification" i18n>You don't have notifications.</div>
+
+<div class="notifications" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()">
+  <div *ngFor="let notification of notifications" class="notification" [ngClass]="{ unread: !notification.read }" (click)="markAsRead(notification)">
+
+    <ng-container [ngSwitch]="notification.type">
+      <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION">
+        <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.video.channel.avatarUrl" />
+
+        <div class="message">
+          {{ notification.video.channel.displayName }} published a <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">new video</a>
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.UNBLACKLIST_ON_MY_VIDEO">
+        <my-global-icon iconName="undo"></my-global-icon>
+
+        <div class="message">
+          Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been unblacklisted
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.BLACKLIST_ON_MY_VIDEO">
+        <my-global-icon iconName="no"></my-global-icon>
+
+        <div class="message">
+          Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been blacklisted
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS">
+        <my-global-icon iconName="alert"></my-global-icon>
+
+        <div class="message">
+          <a (click)="markAsRead(notification)" [routerLink]="notification.videoAbuseUrl">A new video abuse</a> has been created on video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoAbuse.video.name }}</a>
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_COMMENT_ON_MY_VIDEO">
+        <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
+
+        <div class="message">
+          <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> commented your video <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">{{ notification.comment.video.name }}</a>
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_PUBLISHED">
+        <my-global-icon iconName="sparkle"></my-global-icon>
+
+        <div class="message">
+          Your video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been published
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_SUCCESS">
+        <my-global-icon iconName="cloud-download"></my-global-icon>
+
+        <div class="message">
+          <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">Your video import</a> {{ notification.videoImportIdentifier }} succeeded
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.MY_VIDEO_IMPORT_ERROR">
+        <my-global-icon iconName="cloud-error"></my-global-icon>
+
+        <div class="message">
+          <a (click)="markAsRead(notification)" [routerLink]="notification.videoImportUrl">Your video import</a> {{ notification.videoImportIdentifier }} failed
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_USER_REGISTRATION">
+        <my-global-icon iconName="user-add"></my-global-icon>
+
+        <div class="message">
+          User <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.account.name }} registered</a> on your instance
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.NEW_FOLLOW">
+        <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.actorFollow.follower.avatarUrl" />
+
+        <div class="message">
+          <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.actorFollow.follower.displayName }}</a> is following
+
+          <ng-container *ngIf="notification.actorFollow.following.type === 'channel'">your channel {{ notification.actorFollow.following.displayName }}</ng-container>
+          <ng-container *ngIf="notification.actorFollow.following.type === 'account'">your account</ng-container>
+        </div>
+      </ng-container>
+
+      <ng-container i18n *ngSwitchCase="UserNotificationType.COMMENT_MENTION">
+        <img alt="" aria-labelledby="avatar" class="avatar" [src]="notification.comment.account.avatarUrl" />
+
+        <div class="message">
+          <a (click)="markAsRead(notification)" [routerLink]="notification.accountUrl">{{ notification.comment.account.displayName }}</a> mentioned you on <a (click)="markAsRead(notification)" [routerLink]="notification.commentUrl">video {{ notification.comment.video.name }}</a>
+        </div>
+      </ng-container>
+    </ng-container>
+
+    <div class="from-date">{{ notification.createdAt | myFromNow }}</div>
+  </div>
+</div>
diff --git a/client/src/app/shared/users/user-notifications.component.scss b/client/src/app/shared/users/user-notifications.component.scss
new file mode 100644 (file)
index 0000000..315d504
--- /dev/null
@@ -0,0 +1,51 @@
+@import '_variables';
+@import '_mixins';
+
+.no-notification {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 20px 0;
+}
+
+.notification {
+  display: flex;
+  align-items: center;
+  font-size: inherit;
+  padding: 15px 5px 15px 10px;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.10);
+
+  &.unread {
+    background-color: rgba(0, 0, 0, 0.05);
+  }
+
+  my-global-icon {
+    width: 24px;
+    margin-right: 11px;
+    margin-left: 3px;
+
+    @include apply-svg-color(#333);
+  }
+
+  .avatar {
+    @include avatar(30px);
+
+    margin-right: 10px;
+  }
+
+  .message {
+    flex-grow: 1;
+
+    a {
+      font-weight: $font-semibold;
+    }
+  }
+
+  .from-date {
+    font-size: 0.85em;
+    color: $grey-foreground-color;
+    padding-left: 5px;
+    min-width: 70px;
+    text-align: right;
+  }
+}
diff --git a/client/src/app/shared/users/user-notifications.component.ts b/client/src/app/shared/users/user-notifications.component.ts
new file mode 100644 (file)
index 0000000..b5f9fd3
--- /dev/null
@@ -0,0 +1,87 @@
+import { Component, Input, OnInit } from '@angular/core'
+import { UserNotificationService } from '@app/shared/users/user-notification.service'
+import { UserNotificationType } from '../../../../../shared'
+import { ComponentPagination, hasMoreItems } from '@app/shared/rest/component-pagination.model'
+import { Notifier } from '@app/core'
+import { UserNotification } from '@app/shared/users/user-notification.model'
+
+@Component({
+  selector: 'my-user-notifications',
+  templateUrl: 'user-notifications.component.html',
+  styleUrls: [ 'user-notifications.component.scss' ]
+})
+export class UserNotificationsComponent implements OnInit {
+  @Input() ignoreLoadingBar = false
+  @Input() infiniteScroll = true
+  @Input() itemsPerPage = 20
+
+  notifications: UserNotification[] = []
+
+  // So we can access it in the template
+  UserNotificationType = UserNotificationType
+
+  componentPagination: ComponentPagination
+
+  constructor (
+    private userNotificationService: UserNotificationService,
+    private notifier: Notifier
+  ) { }
+
+  ngOnInit () {
+    this.componentPagination = {
+      currentPage: 1,
+      itemsPerPage: this.itemsPerPage, // Reset items per page, because of the @Input() variable
+      totalItems: null
+    }
+
+    this.loadMoreNotifications()
+  }
+
+  loadMoreNotifications () {
+    this.userNotificationService.listMyNotifications(this.componentPagination, undefined, this.ignoreLoadingBar)
+        .subscribe(
+          result => {
+            this.notifications = this.notifications.concat(result.data)
+            this.componentPagination.totalItems = result.total
+          },
+
+          err => this.notifier.error(err.message)
+        )
+  }
+
+  onNearOfBottom () {
+    if (this.infiniteScroll === false) return
+
+    this.componentPagination.currentPage++
+
+    if (hasMoreItems(this.componentPagination)) {
+      this.loadMoreNotifications()
+    }
+  }
+
+  markAsRead (notification: UserNotification) {
+    if (notification.read) return
+
+    this.userNotificationService.markAsRead(notification)
+        .subscribe(
+          () => {
+            notification.read = true
+          },
+
+          err => this.notifier.error(err.message)
+        )
+  }
+
+  markAllAsRead () {
+    this.userNotificationService.markAllAsRead()
+        .subscribe(
+          () => {
+            for (const notification of this.notifications) {
+              notification.read = true
+            }
+          },
+
+          err => this.notifier.error(err.message)
+        )
+  }
+}
index 9819829fd1c6e91827726c5d087d1c123ab0c0ba..c15f1de8c5b24c1818abfcd9798bead76bf04d62 100644 (file)
@@ -1,33 +1,8 @@
-import {
-  Account as AccountServerModel,
-  hasUserRight,
-  User as UserServerModel,
-  UserRight,
-  UserRole,
-  VideoChannel
-} from '../../../../../shared'
+import { hasUserRight, User as UserServerModel, UserNotificationSetting, UserRight, UserRole, VideoChannel } from '../../../../../shared'
 import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
 import { Account } from '@app/shared/account/account.model'
 import { Avatar } from '../../../../../shared/models/avatars/avatar.model'
 
-export type UserConstructorHash = {
-  id: number,
-  username: string,
-  email: string,
-  role: UserRole,
-  emailVerified?: boolean,
-  videoQuota?: number,
-  videoQuotaDaily?: number,
-  nsfwPolicy?: NSFWPolicyType,
-  webTorrentEnabled?: boolean,
-  autoPlayVideo?: boolean,
-  createdAt?: Date,
-  account?: AccountServerModel,
-  videoChannels?: VideoChannel[]
-
-  blocked?: boolean
-  blockedReason?: string
-}
 export class User implements UserServerModel {
   id: number
   username: string
@@ -35,8 +10,11 @@ export class User implements UserServerModel {
   emailVerified: boolean
   role: UserRole
   nsfwPolicy: NSFWPolicyType
+
   webTorrentEnabled: boolean
   autoPlayVideo: boolean
+  videosHistoryEnabled: boolean
+
   videoQuota: number
   videoQuotaDaily: number
   account: Account
@@ -46,7 +24,9 @@ export class User implements UserServerModel {
   blocked: boolean
   blockedReason?: string
 
-  constructor (hash: UserConstructorHash) {
+  notificationSettings?: UserNotificationSetting
+
+  constructor (hash: Partial<UserServerModel>) {
     this.id = hash.id
     this.username = hash.username
     this.email = hash.email
@@ -57,11 +37,14 @@ export class User implements UserServerModel {
     this.videoQuotaDaily = hash.videoQuotaDaily
     this.nsfwPolicy = hash.nsfwPolicy
     this.webTorrentEnabled = hash.webTorrentEnabled
+    this.videosHistoryEnabled = hash.videosHistoryEnabled
     this.autoPlayVideo = hash.autoPlayVideo
     this.createdAt = hash.createdAt
     this.blocked = hash.blocked
     this.blockedReason = hash.blockedReason
 
+    this.notificationSettings = hash.notificationSettings
+
     if (hash.account !== undefined) {
       this.account = new Account(hash.account)
     }
index 61b7e1b9880dfbbdeacd1e3b6f229229eaf6af7c..b0b59ea0c7ee8edbf4e2a13ca97b90429deb9815 100644 (file)
@@ -32,9 +32,7 @@ export class VideoAbuseService {
 
   reportVideo (id: number, reason: string) {
     const url = VideoAbuseService.BASE_VIDEO_ABUSE_URL + id + '/abuse'
-    const body = {
-      reason
-    }
+    const body = { reason }
 
     return this.authHttp.post(url, body)
                .pipe(
index 7d39fd4f24fd771c657a71be030b6c9e32fa3a01..94e46d7c28a6aae601ee2af2e24f9114664ccded 100644 (file)
@@ -36,8 +36,11 @@ export class VideoBlacklistService {
                )
   }
 
-  blacklistVideo (videoId: number, reason?: string) {
-    const body = reason ? { reason } : {}
+  blacklistVideo (videoId: number, reason: string, unfederate: boolean) {
+    const body = {
+      unfederate,
+      reason
+    }
 
     return this.authHttp.post(VideoBlacklistService.BASE_VIDEOS_URL + videoId + '/blacklist', body)
                .pipe(
index 29492351b0f620df88cd10b477e7a0ee11ca49e9..1f97bc38937600de09db188725eb50914b7f3412 100644 (file)
@@ -1,8 +1,11 @@
 <div [ngClass]="{ 'margin-content': marginContent }">
   <div class="videos-header">
     <div *ngIf="titlePage" class="title-page title-page-single">
-      {{ titlePage }}
+      <div placement="bottom" [ngbTooltip]="titleTooltip" container="body">
+        {{ titlePage }}
+      </div>
     </div>
+
     <my-feed [syndicationItems]="syndicationItems"></my-feed>
 
     <div class="moderation-block" *ngIf="displayModerationBlock">
index 9fb3fd4d65abba4f4c0da4cc3c119b725d7f8751..292ede698eb2c056932ef556eebfed856dd2a9fb 100644 (file)
@@ -19,8 +19,8 @@
 
   my-feed {
     display: inline-block;
-    position: relative;
     top: 1px;
+    min-width: 60px;
   }
 
   .moderation-block {
index 2d32dd6ad590f0cac525a3c9f8988917630ec704..b0633be4abfc85dc570205ba103c5284e45f34b9 100644 (file)
@@ -3,7 +3,6 @@ import { ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { Location } from '@angular/common'
 import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
-import { NotificationsService } from 'angular2-notifications'
 import { fromEvent, Observable, Subscription } from 'rxjs'
 import { AuthService } from '../../core/auth'
 import { ComponentPagination } from '../rest/component-pagination.model'
@@ -13,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 import { ScreenService } from '@app/shared/misc/screen.service'
 import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
 import { Syndication } from '@app/shared/video/syndication.model'
+import { Notifier } from '@app/core'
 
 export abstract class AbstractVideoList implements OnInit, OnDestroy {
   private static LINES_PER_PAGE = 4
@@ -39,11 +39,12 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
   ownerDisplayType: OwnerDisplayType = 'account'
   firstLoadedPage: number
   displayModerationBlock = false
+  titleTooltip: string
 
   protected baseVideoWidth = 215
   protected baseVideoHeight = 205
 
-  protected abstract notificationsService: NotificationsService
+  protected abstract notifier: Notifier
   protected abstract authService: AuthService
   protected abstract router: Router
   protected abstract route: ActivatedRoute
@@ -157,7 +158,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
       },
       error => {
         this.loadingPage[page] = false
-        this.notificationsService.error(this.i18n('Error'), error.message)
+        this.notifier.error(error.message)
       }
     )
   }
index 16116ba88aa15b29046a79806c731c5dbb6aa1a5..f7624ec010e854eb0851253ce36bd18fedddb688 100644 (file)
@@ -1,10 +1,11 @@
 <div class="video-feed">
-  <span
+  <my-global-icon
     *ngIf="syndicationItems.length !== 0" [ngbPopover]="feedsList" [autoClose]="true" placement="bottom"
-    class="icon icon-syndication" role="button"
-  ></span>
+    class="icon-syndication" role="button" iconName="syndication"
+  >
+  </my-global-icon>
 
   <ng-template #feedsList>
     <a *ngFor="let item of syndicationItems" [href]="item.url" target="_blank" rel="noopener noreferrer">{{ item.label }}</a>
   </ng-template>
-</div>
\ No newline at end of file
+</div>
index 385764be0aaf0cddd3ffaba37307120f3f163a8b..ed1dc17d348701c157043e4d09a071330793cb19 100644 (file)
@@ -1,3 +1,4 @@
+@import '_variables';
 @import '_mixins';
 
 .video-feed {
@@ -6,14 +7,12 @@
     display: block;
   }
 
-  .icon {
-    @include icon(12px);
+  my-global-icon {
+    cursor: pointer;
+    width: 12px;
+    position: relative;
+    top: -2px;
 
-    &.icon-syndication {
-      position: relative;
-      top: -2px;
-      background-color: var(--mainForegroundColor);
-      mask-image: url('../../../assets/images/global/syndication.svg');
-    }
+    @include apply-svg-color(var(--mainForegroundColor))
   }
-}
\ No newline at end of file
+}
index 895879adc5311ce210c734dbc8af78046f29081f..f44bdf9a93c3aa04da27efab85f462bc5aa73add 100644 (file)
       text-overflow: ellipsis;
       white-space: nowrap;
       font-size: 13px;
-      color: #585858;
+      color: $grey-foreground-color;
 
       &:hover {
-        color: #303030;
+        color: $grey-foreground-hover-color;
       }
     }
   }
index b92c96450b1b4a60d99c8ca2bb97932e6f6c3ed7..6ea83d13b4726d3ed8403e83840c112e8c8c1efa 100644 (file)
@@ -53,7 +53,7 @@ export class Video implements VideoServerModel {
     displayName: string
     url: string
     host: string
-    avatar: Avatar
+    avatar?: Avatar
   }
 
   channel: {
@@ -63,7 +63,7 @@ export class Video implements VideoServerModel {
     displayName: string
     url: string
     host: string
-    avatar: Avatar
+    avatar?: Avatar
   }
 
   userHistory?: {
index 0207a166eadb07bf1fb4aedd30d559b4029ac75b..07d24b38132a56c0b706312fb25034da2b7d46a6 100644 (file)
@@ -64,7 +64,7 @@
     </form>
 
     <div>
-      <label for="email" i18n>Features found on this instance</label>
+      <label i18n>Features found on this instance</label>
       <my-instance-features-table></my-instance-features-table>
     </div>
   </div>
index 3341d4e0943a6f4d65cceffc146898060a3344fc..13941ec79542e978de2e9bb05a63ea0b2b3aeec8 100644 (file)
@@ -1,8 +1,7 @@
 import { Component, OnInit } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier, RedirectService, ServerService } from '@app/core'
 import { UserCreate } from '../../../../shared'
 import { FormReactive, UserService, UserValidatorsService } from '../shared'
-import { AuthService, RedirectService, ServerService } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 
@@ -20,7 +19,7 @@ export class SignupComponent extends FormReactive implements OnInit {
     protected formValidatorService: FormValidatorService,
     private authService: AuthService,
     private userValidatorsService: UserValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private userService: UserService,
     private serverService: ServerService,
     private redirectService: RedirectService,
@@ -64,10 +63,7 @@ export class SignupComponent extends FormReactive implements OnInit {
         this.authService.login(userCreate.username, userCreate.password)
             .subscribe(
               () => {
-                this.notificationsService.success(
-                  this.i18n('Success'),
-                  this.i18n('You are now logged in as {{username}}!', { username: userCreate.username })
-                )
+                this.notifier.success(this.i18n('You are now logged in as {{username}}!', { username: userCreate.username }))
 
                 this.redirectService.redirectToHomepage()
               },
index 30aefdbfc89be07a137fcfe9cfd552f5d075660e..19043eee6e89fc749572a77ffc8a0ee3f12766e7 100644 (file)
@@ -3,7 +3,7 @@
 
     <div class="modal-header">
       <h4 i18n class="modal-title">Add caption</h4>
-      <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
+      <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
     </div>
 
     <div class="modal-body">
index 33c766d87ca09852e69d138244dab4fe4cf74085..092c0e86224b038ca033c93aa0f6482c2262c887 100644 (file)
             ></my-peertube-checkbox>
 
             <my-peertube-checkbox
+              *ngIf="waitTranscodingEnabled"
               inputName="waitTranscoding" formControlName="waitTranscoding"
               i18n-labelText labelText="Wait transcoding before publishing the video"
               i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
 
           <div class="captions-header">
             <a (click)="openAddCaptionModal()" class="create-caption">
-              <span class="icon icon-add"></span>
+              <my-global-icon iconName="add"></my-global-icon>
               <ng-container i18n>Add another caption</ng-container>
             </a>
           </div>
index 25db8e8edba0f8cc249510034687f619690127e3..bb775cb0af67642979d720bf93d15339cfadcfcd 100644 (file)
@@ -23,10 +23,6 @@ my-peertube-checkbox {
     display: block;
   }
 
-  input, select {
-    font-size: 15px
-  }
-
   .label-tags + span {
     font-size: 15px;
   }
@@ -42,7 +38,7 @@ my-peertube-checkbox {
     text-align: right;
 
     .create-caption {
-      @include create-button('../../../../assets/images/global/add.svg');
+      @include create-button;
     }
   }
 
@@ -100,13 +96,14 @@ my-peertube-checkbox {
     display: inline-block;
     margin-right: 25px;
 
-    color: #585858;
+    color: $grey-foreground-color;
     font-size: 15px;
   }
 
   .submit-button {
     @include peertube-button;
     @include orange-button;
+    @include button-with-icon(20px, 1px);
 
     display: inline-block;
 
@@ -119,16 +116,6 @@ my-peertube-checkbox {
       color: inherit;
       font-weight: $font-semibold;
     }
-
-    .icon.icon-validate {
-      @include icon(20px);
-
-      cursor: inherit;
-      position: relative;
-      top: -1px;
-      margin-right: 4px;
-      background-image: url('../../../../assets/images/global/validate.svg');
-    }
   }
 }
 
@@ -176,10 +163,10 @@ p-calendar {
   }
 
   tag {
-    background-color: var(--inputColor) !important;
+    background-color: $grey-background-color !important;
+    color: #000 !important;
     border-radius: 3px !important;
     font-size: 15px !important;
-    color: var(--mainForegroundColor) !important;
     height: 30px !important;
     line-height: 30px !important;
     margin: 0 5px 0 0 !important;
@@ -202,7 +189,10 @@ p-calendar {
       top: -1px;
       height: auto !important;
       vertical-align: middle !important;
-      fill: #585858 !important;
+
+      path  {
+        fill: $grey-foreground-color !important;
+      }
     }
 
     &:hover {
index a56733e5752bf48631868e16e0a1de2ab3265301..85e01590108957db131dc9cc747e9f75e77299ec 100644 (file)
@@ -2,7 +2,7 @@ import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'
 import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'
 import { ActivatedRoute, Router } from '@angular/router'
 import { FormReactiveValidationMessages, VideoValidatorsService } from '@app/shared'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { ServerService } from '../../../core/server'
 import { VideoEdit } from '../../../shared/video/video-edit.model'
 import { map } from 'rxjs/operators'
@@ -27,6 +27,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
   @Input() userVideoChannels: { id: number, label: string, support: string }[] = []
   @Input() schedulePublicationPossible = true
   @Input() videoCaptions: VideoCaptionEdit[] = []
+  @Input() waitTranscodingEnabled = true
 
   @ViewChild('videoCaptionAddModal') videoCaptionAddModal: VideoCaptionAddModalComponent
 
@@ -58,7 +59,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
     private videoCaptionService: VideoCaptionService,
     private route: ActivatedRoute,
     private router: Router,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private serverService: ServerService,
     private i18nPrimengCalendarService: I18nPrimengCalendarService
   ) {
index 11a81ad66f74dfccfca572cd490a8f4b331eb830..28eb143c9a4ec763b51a5bc5d82b624e5d1c98f7 100644 (file)
@@ -1,6 +1,6 @@
 <div *ngIf="!hasImportedVideo" class="upload-video-container">
-  <div class="import-video-torrent">
-    <div class="icon icon-upload"></div>
+  <div class="first-step-block">
+    <my-global-icon class="upload-icon" iconName="upload"></my-global-icon>
 
     <div class="button-file">
       <span i18n>Select the torrent to import</span>
@@ -66,7 +66,7 @@
        (click)="updateSecondStep()"
        [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }"
     >
-      <span class="icon icon-validate"></span>
+      <my-global-icon iconName="validate"></my-global-icon>
       <input type="button" i18n-value value="Update" />
     </div>
   </div>
index 00626cd7b3ec601f9fb93c61bf252a59dbb83182..6d59ed834290a945b814649e29d7661093008c4a 100644 (file)
@@ -1,45 +1,7 @@
 @import 'variables';
 @import 'mixins';
 
-$width-size: 190px;
-
-.peertube-select-container {
-  @include peertube-select-container($width-size);
-}
-
-.alert.alert-danger {
-  text-align: center;
-
-  & > div {
-    font-weight: $font-semibold;
-  }
-}
-
-.import-video-torrent {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-
-  .icon.icon-upload {
-    @include icon(90px);
-    margin-bottom: 25px;
-    cursor: default;
-
-    background-image: url('../../../../assets/images/video/upload.svg');
-  }
-
-  .button-file {
-    @include peertube-button-file(auto);
-
-    min-width: 190px;
-  }
-
-  .button-file-extension {
-    display: block;
-    font-size: 12px;
-    margin-top: 5px;
-  }
-
+.first-step-block {
   .torrent-or-magnet {
     margin: 10px 0;
   }
@@ -47,19 +9,6 @@ $width-size: 190px;
   .form-group-magnet-uri {
     margin-bottom: 40px;
   }
-
-  input[type=text] {
-    @include peertube-input-text($width-size);
-    display: block;
-  }
-
-  input[type=button] {
-    @include peertube-button;
-    @include orange-button;
-
-    width: $width-size;
-    margin-top: 30px;
-  }
 }
 
 
index 13776ae36748161b90ff726aa5edb157c2cfafc2..307806bb98eb8f0ae6d9c96ab196b3177d8df05e 100644 (file)
@@ -1,8 +1,7 @@
 import { Component, ElementRef, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
 import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos'
-import { AuthService, ServerService } from '../../../core'
+import { AuthService, Notifier, ServerService } from '../../../core'
 import { VideoService } from '../../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { LoadingBarService } from '@ngx-loading-bar/core'
@@ -19,7 +18,8 @@ import { scrollToTop } from '@app/shared/misc/utils'
   templateUrl: './video-import-torrent.component.html',
   styleUrls: [
     '../shared/video-edit.component.scss',
-    './video-import-torrent.component.scss'
+    './video-import-torrent.component.scss',
+    './video-send.scss'
   ]
 })
 export class VideoImportTorrentComponent extends VideoSend implements OnInit, CanComponentDeactivate {
@@ -41,7 +41,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
   constructor (
     protected formValidatorService: FormValidatorService,
     protected loadingBar: LoadingBarService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected serverService: ServerService,
     protected videoService: VideoService,
@@ -107,7 +107,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
         this.loadingBar.complete()
         this.isImportingVideo = false
         this.firstStepError.emit()
-        this.notificationsService.error(this.i18n('Error'), err.message)
+        this.notifier.error(err.message)
       }
     )
   }
@@ -126,7 +126,7 @@ export class VideoImportTorrentComponent extends VideoSend implements OnInit, Ca
         .subscribe(
           () => {
             this.isUpdatingVideo = false
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Video to import updated.'))
+            this.notifier.success(this.i18n('Video to import updated.'))
 
             this.router.navigate([ '/my-account', 'video-imports' ])
           },
index 533446672fd302bd82422f0cc295fc0752f8e211..3550c3585de936a0fd9fad448019aa74fc59de0f 100644 (file)
@@ -1,6 +1,6 @@
 <div *ngIf="!hasImportedVideo" class="upload-video-container">
-  <div class="import-video-url">
-    <div class="icon icon-upload"></div>
+  <div class="first-step-block">
+    <my-global-icon class="upload-icon" iconName="upload"></my-global-icon>
 
     <div class="form-group">
       <label i18n for="targetUrl">URL</label>
@@ -59,7 +59,7 @@
        (click)="updateSecondStep()"
        [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }"
     >
-      <span class="icon icon-validate"></span>
+      <my-global-icon iconName="validate"></my-global-icon>
       <input type="button" i18n-value value="Update" />
     </div>
   </div>
index 9cdface7519dcf09e6730db2f1b3fc8a4ed97761..257c6e5dbb3fcd9ced5931881986998e2720f72a 100644 (file)
@@ -1,8 +1,7 @@
 import { Component, EventEmitter, OnInit, Output } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
 import { VideoPrivacy, VideoUpdate } from '../../../../../../shared/models/videos'
-import { AuthService, ServerService } from '../../../core'
+import { AuthService, Notifier, ServerService } from '../../../core'
 import { VideoService } from '../../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { LoadingBarService } from '@ngx-loading-bar/core'
@@ -19,7 +18,7 @@ import { scrollToTop } from '@app/shared/misc/utils'
   templateUrl: './video-import-url.component.html',
   styleUrls: [
     '../shared/video-edit.component.scss',
-    './video-import-url.component.scss'
+    './video-send.scss'
   ]
 })
 export class VideoImportUrlComponent extends VideoSend implements OnInit, CanComponentDeactivate {
@@ -40,7 +39,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom
   constructor (
     protected formValidatorService: FormValidatorService,
     protected loadingBar: LoadingBarService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected serverService: ServerService,
     protected videoService: VideoService,
@@ -99,7 +98,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom
         this.loadingBar.complete()
         this.isImportingVideo = false
         this.firstStepError.emit()
-        this.notificationsService.error(this.i18n('Error'), err.message)
+        this.notifier.error(err.message)
       }
     )
   }
@@ -118,7 +117,7 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom
         .subscribe(
           () => {
             this.isUpdatingVideo = false
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Video to import updated.'))
+            this.notifier.success(this.i18n('Video to import updated.'))
 
             this.router.navigate([ '/my-account', 'video-imports' ])
           },
similarity index 57%
rename from client/src/app/videos/+video-edit/video-add-components/video-import-url.component.scss
rename to client/src/app/videos/+video-edit/video-add-components/video-send.scss
index e907edc7035fc926b77fcf80f02ca58228e34084..8769dd3020e3c835338a2c95ed3ca050a70082af 100644 (file)
@@ -3,10 +3,6 @@
 
 $width-size: 190px;
 
-.peertube-select-container {
-  @include peertube-select-container($width-size);
-}
-
 .alert.alert-danger {
   text-align: center;
 
@@ -15,17 +11,20 @@ $width-size: 190px;
   }
 }
 
-.import-video-url {
+.first-step-block {
   display: flex;
   flex-direction: column;
   align-items: center;
 
-  .icon.icon-upload {
-    @include icon(90px);
+  .upload-icon {
+    width: 90px;
     margin-bottom: 25px;
-    cursor: default;
 
-    background-image: url('../../../../assets/images/video/upload.svg');
+    @include apply-svg-color(#C6C6C6);
+  }
+
+  .peertube-select-container {
+    @include peertube-select-container($width-size);
   }
 
   input[type=text] {
@@ -40,6 +39,16 @@ $width-size: 190px;
     width: $width-size;
     margin-top: 30px;
   }
-}
 
+  .button-file {
+    @include peertube-button-file(auto);
 
+    min-width: 190px;
+  }
+
+  .button-file-extension {
+    display: block;
+    font-size: 12px;
+    margin-top: 5px;
+  }
+}
index 71d2544d812f4cad2a6cd162b5143f34399939bf..580c123a029a59cc10a857545a441b9c60cdc28c 100644 (file)
@@ -1,10 +1,9 @@
 import { EventEmitter, OnInit } from '@angular/core'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier, ServerService } from '@app/core'
 import { catchError, switchMap, tap } from 'rxjs/operators'
 import { FormReactive } from '@app/shared'
 import { VideoConstant, VideoPrivacy } from '../../../../../../shared'
-import { AuthService, ServerService } from '@app/core'
 import { VideoService } from '@app/shared/video/video.service'
 import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
 import { VideoCaptionService } from '@app/shared/video-caption'
@@ -25,7 +24,7 @@ export abstract class VideoSend extends FormReactive implements OnInit {
   protected abstract readonly DEFAULT_VIDEO_PRIVACY: VideoPrivacy
 
   protected loadingBar: LoadingBarService
-  protected notificationsService: NotificationsService
+  protected notifier: Notifier
   protected authService: AuthService
   protected serverService: ServerService
   protected videoService: VideoService
index a09f54dfcce48ad277e4a1fee4e12ebf8ccaadd8..b252cd60adb01af61219fff805bed85f1573616b 100644 (file)
@@ -1,12 +1,12 @@
 <div *ngIf="!isUploadingVideo" class="upload-video-container">
-  <div class="upload-video">
-    <div class="icon icon-upload"></div>
+  <div class="first-step-block">
+    <my-global-icon class="upload-icon" iconName="upload"></my-global-icon>
 
     <div class="button-file">
       <span i18n>Select the file to upload</span>
       <input #videofileInput type="file" name="videofile" id="videofile" [accept]="videoExtensions" (change)="fileChange()" />
     </div>
-    <span class="button-file-extension">(.mp4, .webm, .ogv)</span>
+    <span class="button-file-extension">({{ videoExtensions }})</span>
 
     <div class="form-group form-group-channel">
       <label i18n for="first-step-channel">Channel</label>
   {{ error }}
 </div>
 
+<div *ngIf="videoUploaded && !error" class="alert alert-info" i18n>
+  Congratulations! Your video is now available in your private library.
+</div>
+
 <!-- Hidden because we want to load the component -->
 <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form">
   <my-video-edit
     [form]="form" [formErrors]="formErrors" [videoCaptions]="videoCaptions"
     [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
+    [waitTranscodingEnabled]="waitTranscodingEnabled"
   ></my-video-edit>
 
   <div class="submit-container">
@@ -56,7 +61,7 @@
        (click)="updateSecondStep()"
        [ngClass]="{ disabled: isPublishingButtonDisabled() }"
     >
-      <span class="icon icon-validate"></span>
+      <my-global-icon iconName="validate"></my-global-icon>
       <input [disabled]="isPublishingButtonDisabled()" type="button" i18n-value value="Publish" />
     </div>
   </div>
index cf1725ef9ee22a37affb1311a8a8f9984bfee8b2..8adf8f169c596033ee9deea1b2087893c0c9f946 100644 (file)
@@ -1,47 +1,9 @@
 @import 'variables';
 @import 'mixins';
 
-.peertube-select-container {
-  @include peertube-select-container(190px);
-}
-
-.alert.alert-danger {
-  text-align: center;
-
-  & > div {
-    font-weight: $font-semibold;
-  }
-}
-
-.upload-video {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-
-  .form-group-channel {
-    margin-bottom: 20px;
-    margin-top: 35px;
-  }
-
-  .icon.icon-upload {
-    @include icon(90px);
-    margin-bottom: 25px;
-    cursor: default;
-
-    background-image: url('../../../../assets/images/video/upload.svg');
-  }
-
-  .button-file {
-    @include peertube-button-file(auto);
-
-    min-width: 190px;
-  }
-
-  .button-file-extension {
-    display: block;
-    font-size: 12px;
-    margin-top: 5px;
-  }
+.first-step-block .form-group-channel {
+  margin-bottom: 20px;
+  margin-top: 35px;
 }
 
 .upload-progress-cancel {
@@ -54,9 +16,7 @@
 
     /deep/ .ui-progressbar {
       font-size: 15px !important;
-      color: #fff !important;
       height: 30px !important;
-      line-height: 30px !important;
       border-radius: 3px !important;
       background-color: rgba(11, 204, 41, 0.16) !important;
 
@@ -68,6 +28,8 @@
         text-align: left;
         padding-left: 18px;
         margin-top: 0 !important;
+        color: #fff !important;
+        line-height: 30px !important;
       }
     }
 
index 3fcb71ac3440f58e3a6b16bf30a63ee547bd26a5..e4d54b65478b1c4910f818e85d861decf49a84f7 100644 (file)
@@ -2,11 +2,10 @@ import { HttpEventType, HttpResponse } from '@angular/common/http'
 import { Component, ElementRef, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'
 import { Router } from '@angular/router'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { NotificationsService } from 'angular2-notifications'
 import { BytesPipe } from 'ngx-pipes'
 import { Subscription } from 'rxjs'
 import { VideoPrivacy } from '../../../../../../shared/models/videos'
-import { AuthService, ServerService } from '../../../core'
+import { AuthService, Notifier, ServerService } from '../../../core'
 import { VideoEdit } from '../../../shared/video/video-edit.model'
 import { VideoService } from '../../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -21,7 +20,8 @@ import { scrollToTop } from '@app/shared/misc/utils'
   templateUrl: './video-upload.component.html',
   styleUrls: [
     '../shared/video-edit.component.scss',
-    './video-upload.component.scss'
+    './video-upload.component.scss',
+    './video-send.scss'
   ]
 })
 export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy, CanComponentDeactivate {
@@ -44,6 +44,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     id: 0,
     uuid: ''
   }
+  waitTranscodingEnabled = true
 
   error: string
 
@@ -52,7 +53,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
   constructor (
     protected formValidatorService: FormValidatorService,
     protected loadingBar: LoadingBarService,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected serverService: ServerService,
     protected videoService: VideoService,
@@ -109,7 +110,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
       this.isUploadingVideo = false
       this.videoUploadPercents = 0
       this.videoUploadObservable = null
-      this.notificationsService.info(this.i18n('Info'), this.i18n('Upload cancelled'))
+      this.notifier.info(this.i18n('Upload cancelled'))
     }
   }
 
@@ -117,12 +118,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     const videofile = this.videofileInput.nativeElement.files[0]
     if (!videofile) return
 
-    // Cannot upload videos > 8GB for now
-    if (videofile.size > 8 * 1024 * 1024 * 1024) {
-      this.notificationsService.error(this.i18n('Error'), this.i18n('We are sorry but PeerTube cannot handle videos > 8GB'))
-      return
-    }
-
+    // Check global user quota
     const bytePipes = new BytesPipe()
     const videoQuota = this.authService.getUser().videoQuota
     if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) {
@@ -134,10 +130,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
           videoQuota: bytePipes.transform(videoQuota, 0)
         }
       )
-      this.notificationsService.error(this.i18n('Error'), msg)
+      this.notifier.error(msg)
       return
     }
 
+    // Check daily user quota
     const videoQuotaDaily = this.authService.getUser().videoQuotaDaily
     if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) {
       const msg = this.i18n(
@@ -148,10 +145,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
           quotaDaily: bytePipes.transform(videoQuotaDaily, 0)
         }
       )
-      this.notificationsService.error(this.i18n('Error'), msg)
+      this.notifier.error(msg)
       return
     }
 
+    // Build name field
     const nameWithoutExtension = videofile.name.replace(/\.[^/.]+$/, '')
     let name: string
 
@@ -159,6 +157,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
     if (nameWithoutExtension.length < 3) name = videofile.name
     else name = nameWithoutExtension
 
+    // Force user to wait transcoding for unsupported video types in web browsers
+    if (!videofile.name.endsWith('.mp4') && !videofile.name.endsWith('.webm') && !videofile.name.endsWith('.ogv')) {
+      this.waitTranscodingEnabled = false
+    }
+
     const privacy = this.firstStepPrivacyId.toString()
     const nsfw = false
     const waitTranscoding = true
@@ -206,7 +209,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
         this.videoUploadPercents = 0
         this.videoUploadObservable = null
         this.firstStepError.emit()
-        this.notificationsService.error(this.i18n('Error'), err.message)
+        this.notifier.error(err.message)
       }
     )
   }
@@ -235,7 +238,7 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy
             this.isUpdatingVideo = false
             this.isUploadingVideo = false
 
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Video published.'))
+            this.notifier.success(this.i18n('Video published.'))
             this.router.navigate([ '/videos/watch', video.uuid ])
           },
 
index 57a9d0ca7abd4d2d541f490a74b4ff44f50d9c84..01fdfcb66742e3a9eb89b85a9fb0f8d0636f676e 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, ViewChild } from '@angular/core'
+import { Component, HostListener, ViewChild } from '@angular/core'
 import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.service'
 import { VideoImportUrlComponent } from '@app/videos/+video-edit/video-add-components/video-import-url.component'
 import { VideoUploadComponent } from '@app/videos/+video-edit/video-add-components/video-upload.component'
@@ -32,7 +32,17 @@ export class VideoAddComponent implements CanComponentDeactivate {
     this.secondStepType = undefined
   }
 
-  canDeactivate () {
+  @HostListener('window:beforeunload', [ '$event' ])
+  onUnload (event: any) {
+    const { text, canDeactivate } = this.canDeactivate()
+
+    if (canDeactivate) return
+
+    event.returnValue = text
+    return text
+  }
+
+  canDeactivate (): { canDeactivate: boolean, text?: string} {
     if (this.secondStepType === 'upload') return this.videoUpload.canDeactivate()
     if (this.secondStepType === 'import-url') return this.videoImportUrl.canDeactivate()
     if (this.secondStepType === 'import-torrent') return this.videoImportTorrent.canDeactivate()
index 9242c30a02cef81af675a092660fec3bf2d004b1..4992bb3693e9689900ac8afffc833eac962b03a4 100644 (file)
@@ -8,12 +8,12 @@
     <my-video-edit
       [form]="form" [formErrors]="formErrors" [schedulePublicationPossible]="schedulePublicationPossible"
       [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" [userVideoChannels]="userVideoChannels"
-      [videoCaptions]="videoCaptions"
+      [videoCaptions]="videoCaptions" [waitTranscodingEnabled]="waitTranscodingEnabled"
     ></my-video-edit>
 
     <div class="submit-container">
       <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid || isUpdatingVideo === true }">
-        <span class="icon icon-validate"></span>
+        <my-global-icon iconName="validate"></my-global-icon>
         <input type="button" i18n-value value="Update" />
       </div>
     </div>
index 3a0f3a39aab093e0c39435746082333394c99c62..9e849014ed5fd688dfd9c22fb7b7a0f94de893dc 100644 (file)
@@ -1,8 +1,8 @@
 import { map, switchMap } from 'rxjs/operators'
-import { Component, OnInit } from '@angular/core'
+import { Component, HostListener, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { LoadingBarService } from '@ngx-loading-bar/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { VideoConstant, VideoPrivacy } from '../../../../../shared/models/videos'
 import { ServerService } from '../../core'
 import { FormReactive } from '../../shared'
@@ -12,6 +12,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { VideoCaptionService } from '@app/shared/video-caption'
 import { VideoCaptionEdit } from '@app/shared/video-caption/video-caption-edit.model'
+import { VideoDetails } from '@app/shared/video/video-details.model'
 
 @Component({
   selector: 'my-videos-update',
@@ -26,6 +27,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
   userVideoChannels: { id: number, label: string, support: string }[] = []
   schedulePublicationPossible = false
   videoCaptions: VideoCaptionEdit[] = []
+  waitTranscodingEnabled = true
 
   private updateDone = false
 
@@ -33,7 +35,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
     protected formValidatorService: FormValidatorService,
     private route: ActivatedRoute,
     private router: Router,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private serverService: ServerService,
     private videoService: VideoService,
     private loadingBar: LoadingBarService,
@@ -65,25 +67,42 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
 
           this.videoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies)
 
+          const videoFiles = (video as VideoDetails).files
+          if (videoFiles.length > 1) { // Already transcoded
+            this.waitTranscodingEnabled = false
+          }
+
           // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout
           setTimeout(() => this.hydrateFormFromVideo())
         },
 
         err => {
           console.error(err)
-          this.notificationsService.error(this.i18n('Error'), err.message)
+          this.notifier.error(err.message)
         }
       )
   }
 
-  canDeactivate () {
+  @HostListener('window:beforeunload', [ '$event' ])
+  onUnload (event: any) {
+    const { text, canDeactivate } = this.canDeactivate()
+
+    if (canDeactivate) return
+
+    event.returnValue = text
+    return text
+  }
+
+  canDeactivate (): { canDeactivate: boolean, text?: string } {
     if (this.updateDone === true) return { canDeactivate: true }
 
+    const text = this.i18n('You have unsaved changes! If you leave, your changes will be lost.')
+
     for (const caption of this.videoCaptions) {
-      if (caption.action) return { canDeactivate: false }
+      if (caption.action) return { canDeactivate: false, text }
     }
 
-    return { canDeactivate: this.formChanged === false }
+    return { canDeactivate: this.formChanged === false, text }
   }
 
   checkForm () {
@@ -114,14 +133,14 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
             this.updateDone = true
             this.isUpdatingVideo = false
             this.loadingBar.complete()
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.'))
+            this.notifier.success(this.i18n('Video updated.'))
             this.router.navigate([ '/videos/watch', this.video.uuid ])
           },
 
           err => {
             this.loadingBar.complete()
             this.isUpdatingVideo = false
-            this.notificationsService.error(this.i18n('Error'), err.message)
+            this.notifier.error(err.message)
             console.error(err)
           }
         )
index 6db0eb55ddaa5277258dd841cf2cca3dab918d47..fd85c28f2cb77c1af01ed456fa3ac8149b3e7eee 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
 import { Router } from '@angular/router'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { Observable } from 'rxjs'
 import { VideoCommentCreate } from '../../../../../../shared/models/videos/video-comment.model'
 import { FormReactive } from '../../../shared'
@@ -36,7 +36,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
   constructor (
     protected formValidatorService: FormValidatorService,
     private videoCommentValidatorsService: VideoCommentValidatorsService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private videoCommentService: VideoCommentService,
     private authService: AuthService,
     private modalService: NgbModal,
@@ -70,7 +70,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
   }
 
   onValidKey () {
-    this.onValueChanged()
+    this.check()
     if (!this.form.valid) return
 
     this.formValidated()
@@ -115,7 +115,7 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
       err => {
         this.addingComment = false
 
-        this.notificationsService.error(this.i18n('Error'), err.text)
+        this.notifier.error(err.text)
       }
     )
   }
@@ -135,7 +135,6 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
 
   gotoLogin () {
     this.hideVisitorModal()
-    this.authService.redirectUrl = this.router.url
     this.router.navigate([ '/login' ])
   }
 
index 84da5727e6d534c8949efca2b1b9b4dcd2505774..731ecbf8f97736fa341b57cbb09c241bdeebe0a9 100644 (file)
@@ -41,7 +41,7 @@
       }
 
       .comment-date {
-        color: #585858;
+        color: $grey-foreground-color;
         margin-left: 10px;
       }
     }
@@ -69,7 +69,7 @@
 
       .comment-action-reply,
       .comment-action-delete {
-        color: #585858;
+        color: $grey-foreground-color;
         cursor: pointer;
         margin-right: 10px;
 
   .root-comment {
     font-size: 14px;
   }
-}
\ No newline at end of file
+}
index 00f0460a138f06b6a879eff651c0b27c4e3525a4..aba7f9d1c2cb9f87144b5be7d6193a9add8407f2 100644 (file)
@@ -1,11 +1,10 @@
 import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
-import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
-import * as sanitizeHtml from 'sanitize-html'
 import { UserRight } from '../../../../../../shared/models/users'
 import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model'
 import { AuthService } from '../../../core/auth'
 import { Video } from '../../../shared/video/video.model'
 import { VideoComment } from './video-comment.model'
+import { HtmlRendererService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-comment',
@@ -29,7 +28,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   newParentComments: VideoComment[] = []
 
   constructor (
-    private linkifierService: LinkifierService,
+    private htmlRenderer: HtmlRendererService,
     private authService: AuthService
   ) {}
 
@@ -87,27 +86,7 @@ export class VideoCommentComponent implements OnInit, OnChanges {
   }
 
   private init () {
-    // Convert possible markdown to html
-    const html = this.linkifierService.linkify(this.comment.text)
-
-    this.sanitizedCommentHTML = sanitizeHtml(html, {
-      allowedTags: [ 'a', 'p', 'span', 'br' ],
-      allowedSchemes: [ 'http', 'https' ],
-      allowedAttributes: {
-        'a': [ 'href', 'class', 'target' ]
-      },
-      transformTags: {
-        a: (tagName, attribs) => {
-          return {
-            tagName,
-            attribs: Object.assign(attribs, {
-              target: '_blank',
-              rel: 'noopener noreferrer'
-            })
-          }
-        }
-      }
-    })
+    this.sanitizedCommentHTML = this.htmlRenderer.toSafeHtml(this.comment.text)
 
     this.newParentComments = this.parentComments.concat([ this.comment ])
   }
index 921447d5be264e3369273e9d6cd03b94464ef3c7..b8e5878c5b9c8391dedd419b70984e5eb5d222b6 100644 (file)
@@ -1,7 +1,7 @@
 import { catchError, map } from 'rxjs/operators'
 import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
-import { lineFeedToHtml } from '@app/shared/misc/utils'
+import { objectLineFeedToHtml } from '@app/shared/misc/utils'
 import { Observable } from 'rxjs'
 import { ResultList, FeedFormat } from '../../../../../../shared/models'
 import {
@@ -28,7 +28,7 @@ export class VideoCommentService {
 
   addCommentThread (videoId: number | string, comment: VideoCommentCreate) {
     const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comment-threads'
-    const normalizedComment = lineFeedToHtml(comment, 'text')
+    const normalizedComment = objectLineFeedToHtml(comment, 'text')
 
     return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment)
                .pipe(
@@ -39,7 +39,7 @@ export class VideoCommentService {
 
   addCommentReply (videoId: number | string, inReplyToCommentId: number, comment: VideoCommentCreate) {
     const url = VideoCommentService.BASE_VIDEO_URL + videoId + '/comments/' + inReplyToCommentId
-    const normalizedComment = lineFeedToHtml(comment, 'text')
+    const normalizedComment = objectLineFeedToHtml(comment, 'text')
 
     return this.authHttp.post<{ comment: VideoCommentServerModel }>(url, normalizedComment)
                .pipe(
index 8850eccd80213a4bb1e98e8324075e1b5be20ec5..2616820d2462df10f02c5379ae1f6b95e067d619 100644 (file)
@@ -1,11 +1,10 @@
-import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ElementRef } from '@angular/core'
+import { Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'
 import { ActivatedRoute } from '@angular/router'
-import { ConfirmService } from '@app/core'
-import { NotificationsService } from 'angular2-notifications'
+import { ConfirmService, Notifier } from '@app/core'
 import { Subscription } from 'rxjs'
 import { VideoCommentThreadTree } from '../../../../../../shared/models/videos/video-comment.model'
 import { AuthService } from '../../../core/auth'
-import { ComponentPagination } from '../../../shared/rest/component-pagination.model'
+import { ComponentPagination, hasMoreItems } from '../../../shared/rest/component-pagination.model'
 import { User } from '../../../shared/users'
 import { VideoSortField } from '../../../shared/video/sort-field.type'
 import { VideoDetails } from '../../../shared/video/video-details.model'
@@ -42,7 +41,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
 
   constructor (
     private authService: AuthService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private confirmService: ConfirmService,
     private videoCommentService: VideoCommentService,
     private activatedRoute: ActivatedRoute,
@@ -84,15 +83,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
             this.highlightedThread = new VideoComment(res.comment)
 
             // Scroll to the highlighted thread
-            setTimeout(() => {
-              // -60 because of the fixed header
-              const scrollY = this.commentHighlightBlock.nativeElement.offsetTop - 60
-              window.scroll(0, scrollY)
-            }, 500)
+            setTimeout(() => this.commentHighlightBlock.nativeElement.scrollIntoView(), 0)
           }
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -104,7 +99,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
           this.componentPagination.totalItems = res.totalComments
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -155,7 +150,7 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
           if (this.highlightedThread.id === commentToDelete.id) this.highlightedThread = undefined
         },
 
-        err => this.notificationsService.error(this.i18n('Error'), err.message)
+        err => this.notifier.error(err.message)
       )
   }
 
@@ -166,22 +161,11 @@ export class VideoCommentsComponent implements OnInit, OnChanges, OnDestroy {
   onNearOfBottom () {
     this.componentPagination.currentPage++
 
-    if (this.hasMoreComments()) {
+    if (hasMoreItems(this.componentPagination)) {
       this.loadMoreComments()
     }
   }
 
-  private hasMoreComments () {
-    // No results
-    if (this.componentPagination.totalItems === 0) return false
-
-    // Not loaded yet
-    if (!this.componentPagination.totalItems) return true
-
-    const maxPage = this.componentPagination.totalItems / this.componentPagination.itemsPerPage
-    return maxPage > this.componentPagination.currentPage
-  }
-
   private deleteLocalCommentThread (parentComment: VideoCommentThreadTree, commentToDelete: VideoComment) {
     for (const commentChild of parentComment.children) {
       if (commentChild.comment.id === commentToDelete.id) {
index c436501b4279d2fca22f9d3af12e1770c2068e07..1a87bdcd4235d2f0b06f858e5178ad45ad53b3be 100644 (file)
@@ -1,7 +1,7 @@
 <ng-template #modal>
   <div class="modal-header">
     <h4 i18n class="modal-title">Blacklist video</h4>
-    <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body">
         </div>
       </div>
 
+      <div class="form-group" *ngIf="video.isLocal">
+        <my-peertube-checkbox
+          inputName="unfederate" formControlName="unfederate"
+          i18n-labelText labelText="Unfederate the video (ask for its deletion from the remote instances)"
+        ></my-peertube-checkbox>
+      </div>
+
       <div class="form-group inputs">
         <span i18n class="action-button action-button-cancel" (click)="hide()">
           Cancel
index 2c123ebed40af1839c10a914340a8b2dd2aab2ec..50a7cadd1acc1cc020d04486e6b0257a90070e69 100644 (file)
@@ -1,12 +1,11 @@
 import { Component, Input, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier, RedirectService } from '@app/core'
 import { FormReactive, VideoBlacklistService, VideoBlacklistValidatorsService } from '../../../shared/index'
 import { VideoDetails } from '../../../shared/video/video-details.model'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
-import { RedirectService } from '@app/core'
 
 @Component({
   selector: 'my-video-blacklist',
@@ -27,7 +26,7 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
     private modalService: NgbModal,
     private videoBlacklistValidatorsService: VideoBlacklistValidatorsService,
     private videoBlacklistService: VideoBlacklistService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private redirectService: RedirectService,
     private i18n: I18n
   ) {
@@ -35,9 +34,12 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
   }
 
   ngOnInit () {
+    const defaultValues = { unfederate: 'true' }
+
     this.buildForm({
-      reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON
-    })
+      reason: this.videoBlacklistValidatorsService.VIDEO_BLACKLIST_REASON,
+      unfederate: null
+    }, defaultValues)
   }
 
   show () {
@@ -51,16 +53,17 @@ export class VideoBlacklistComponent extends FormReactive implements OnInit {
 
   blacklist () {
     const reason = this.form.value[ 'reason' ] || undefined
+    const unfederate = this.video.isLocal ? this.form.value[ 'unfederate' ] : undefined
 
-    this.videoBlacklistService.blacklistVideo(this.video.id, reason)
+    this.videoBlacklistService.blacklistVideo(this.video.id, reason, unfederate)
         .subscribe(
           () => {
-            this.notificationsService.success(this.i18n('Success'), this.i18n('Video blacklisted.'))
+            this.notifier.success(this.i18n('Video blacklisted.'))
             this.hide()
             this.redirectService.redirectToHomepage()
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 }
index f46f92a17aeff4254ebaa80bbe34b0aa90e3a256..2bb5d6d378f308c23e72d5dadb96cdc6e99cdd24 100644 (file)
@@ -1,7 +1,7 @@
 <ng-template #modal let-hide="close">
   <div class="modal-header">
     <h4 i18n class="modal-title">Download video</h4>
-    <span class="close" aria-hidden="true" (click)="hide()"></span>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body">
index b1b2c06237714527b792d32d27115e27e274b875..8343857717d4301a23ce2800f3c4f694ec1a1144 100644 (file)
@@ -2,7 +2,7 @@ import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core'
 import { VideoDetails } from '../../../shared/video/video-details.model'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-video-download',
@@ -18,7 +18,7 @@ export class VideoDownloadComponent implements OnInit {
   resolutionId: number | string = -1
 
   constructor (
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private modalService: NgbModal,
     private i18n: I18n
   ) { }
@@ -63,6 +63,6 @@ export class VideoDownloadComponent implements OnInit {
   }
 
   activateCopiedMessage () {
-    this.notificationsService.success(this.i18n('Success'), this.i18n('Copied'))
+    this.notifier.success(this.i18n('Copied'))
   }
 }
index 8d9a492766ef9b2a26d81349aa08db7338ed9dce..b9434da26ce461f7d8dcc76d6d2c4bb3aedbd7e8 100644 (file)
@@ -1,11 +1,16 @@
 <ng-template #modal>
   <div class="modal-header">
     <h4 i18n class="modal-title">Report video</h4>
-    <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body">
 
+    <div i18n class="information">
+      Your report will be sent to moderators of {{ currentHost }}.
+      <ng-container *ngIf="isRemoteVideo()"> It will be forwarded to origin instance {{ originHost }} too.</ng-container>
+    </div>
+
     <form novalidate [formGroup]="form" (ngSubmit)="report()">
       <div class="form-group">
         <textarea i18n-placeholder placeholder="Reason..." formControlName="reason" [ngClass]="{ 'input-error': formErrors['reason'] }">
index afcdb9a16f31ceb4db00a0714dd1c7059538b839..4713660a226c801f1e22f687bd3d0c3dc008a3b8 100644 (file)
@@ -1,6 +1,10 @@
 @import 'variables';
 @import 'mixins';
 
+.information {
+  margin-bottom: 20px;
+}
+
 textarea {
   @include peertube-textarea(100%, 100px);
 }
index 297afb19f45a227f838a97a596855f753c0bf118..911f3b4479e888b0757e324bcf17b7376fa9af51 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, Input, OnInit, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { FormReactive, VideoAbuseService } from '../../../shared/index'
 import { VideoDetails } from '../../../shared/video/video-details.model'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -27,12 +27,24 @@ export class VideoReportComponent extends FormReactive implements OnInit {
     private modalService: NgbModal,
     private videoAbuseValidatorsService: VideoAbuseValidatorsService,
     private videoAbuseService: VideoAbuseService,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private i18n: I18n
   ) {
     super()
   }
 
+  get currentHost () {
+    return window.location.host
+  }
+
+  get originHost () {
+    if (this.isRemoteVideo()) {
+      return this.video.account.host
+    }
+
+    return ''
+  }
+
   ngOnInit () {
     this.buildForm({
       reason: this.videoAbuseValidatorsService.VIDEO_ABUSE_REASON
@@ -54,11 +66,15 @@ export class VideoReportComponent extends FormReactive implements OnInit {
     this.videoAbuseService.reportVideo(this.video.id, reason)
                           .subscribe(
                             () => {
-                              this.notificationsService.success(this.i18n('Success'), this.i18n('Video reported.'))
+                              this.notifier.success(this.i18n('Video reported.'))
                               this.hide()
                             },
 
-                            err => this.notificationsService.error(this.i18n('Error'), err.message)
+                            err => this.notifier.error(err.message)
                            )
   }
+
+  isRemoteVideo () {
+    return !this.video.isLocal
+  }
 }
index 301f67f2d0b20439f919ce95a9141419ac42bf76..9f3c37fe86d383e414574151d33e0007296f26f0 100644 (file)
@@ -1,7 +1,7 @@
 <ng-template #modal let-hide="close">
   <div class="modal-header">
     <h4 i18n class="modal-title">Share</h4>
-    <span class="close" aria-hidden="true" (click)="hide()"></span>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body">
index 17e2b31e1bb8e4b3c974fc064ee4e22162aa39f9..c6205e355ca53076f14e7eb454a3af96901b8ad5 100644 (file)
@@ -1,5 +1,5 @@
 import { Component, ElementRef, Input, ViewChild } from '@angular/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier } from '@app/core'
 import { VideoDetails } from '../../../shared/video/video-details.model'
 import { buildVideoEmbed, buildVideoLink } from '../../../../assets/player/utils'
 import { I18n } from '@ngx-translate/i18n-polyfill'
@@ -23,7 +23,7 @@ export class VideoShareComponent {
 
   constructor (
     private modalService: NgbModal,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private i18n: I18n
   ) { }
 
@@ -49,7 +49,7 @@ export class VideoShareComponent {
   }
 
   activateCopiedMessage () {
-    this.notificationsService.success(this.i18n('Success'), this.i18n('Copied'))
+    this.notifier.success(this.i18n('Copied'))
   }
 
   getStartCheckboxLabel () {
index 00c30470967ea05eca7dc547659878335660c9ca..2a05224a8531bce0f94b64ae0244f00430e9f01e 100644 (file)
@@ -1,7 +1,7 @@
 <ng-template #modal let-hide="close">
   <div class="modal-header">
     <h4 i18n class="modal-title">Support</h4>
-    <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
+    <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
   </div>
 
   <div class="modal-body" [innerHTML]="videoHTMLSupport"></div>
index 1540021209b5404e413991315259b08e1a8461e8..deb8fbc6759f5cd3521e93840e166b29ba577fbf 100644 (file)
@@ -1,8 +1,7 @@
 import { Component, Input, ViewChild } from '@angular/core'
-import { MarkdownService } from '@app/videos/shared'
-
 import { VideoDetails } from '../../../shared/video/video-details.model'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-support',
index 770785d023da9f19d98ed6f9bc5a0bc4063c3d76..709eb91a87cf99fc708080a5ea4e6846b0214893 100644 (file)
                   <div
                     *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
                     class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'"
+                    i18n-title title="Like this video"
                   >
-                    <span class="icon icon-like" i18n-title title="Like this video" ></span>
+                    <my-global-icon iconName="like"></my-global-icon>
                   </div>
 
                   <div
                     *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
                     class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'"
+                    i18n-title title="Dislike this video"
                   >
-                    <span class="icon icon-dislike" i18n-title title="Dislike this video"></span>
+                    <my-global-icon iconName="dislike"></my-global-icon>
                   </div>
 
                   <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support">
-                    <span class="icon icon-support"></span>
+                    <my-global-icon iconName="heart"></my-global-icon>
                     <span class="icon-text" i18n>Support</span>
                   </div>
 
                   <div (click)="showShareModal()" class="action-button action-button-share" role="button">
-                    <span class="icon icon-share"></span>
+                    <my-global-icon iconName="share"></my-global-icon>
                     <span class="icon-text" i18n>Share</span>
                   </div>
 
                   <div class="action-more" ngbDropdown placement="top" role="button">
                     <div class="action-button" ngbDropdownToggle role="button">
-                      <span class="icon icon-more"></span>
+                      <my-global-icon class="more-icon" iconName="more"></my-global-icon>
                     </div>
 
                     <div ngbDropdownMenu>
                       <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)">
-                        <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container>
+                        <my-global-icon iconName="download"></my-global-icon> <ng-container i18n>Download</ng-container>
                       </a>
 
                       <a *ngIf="isUserLoggedIn()" class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)">
-                        <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
+                        <my-global-icon iconName="alert"></my-global-icon> <ng-container i18n>Report</ng-container>
                       </a>
 
                       <a *ngIf="isVideoUpdatable()" class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
-                        <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
+                        <my-global-icon iconName="edit"></my-global-icon> <ng-container i18n>Update</ng-container>
                       </a>
 
                       <a *ngIf="isVideoBlacklistable()" class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="showBlacklistModal($event)">
-                        <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
+                        <my-global-icon iconName="no"></my-global-icon> <ng-container i18n>Blacklist</ng-container>
                       </a>
 
                       <a *ngIf="isVideoUnblacklistable()" class="dropdown-item" i18n-title title="Unblacklist this video" href="#" (click)="unblacklistVideo($event)">
-                        <span class="icon icon-unblacklist"></span> <ng-container i18n>Unblacklist</ng-container>
+                        <my-global-icon iconName="undo"></my-global-icon> <ng-container i18n>Unblacklist</ng-container>
                       </a>
 
                       <a *ngIf="isVideoRemovable()" class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
-                        <span class="icon icon-delete"></span> <ng-container i18n>Delete</ng-container>
+                        <my-global-icon iconName="delete"></my-global-icon> <ng-container i18n>Delete</ng-container>
                       </a>
                     </div>
                   </div>
index 2586a2204bdd92c29ea523a92c0b67e65dac6ddd..b03ed197d3fe932318d1c8addf6c12c051f369ef 100644 (file)
@@ -183,6 +183,8 @@ $other-videos-width: 260px;
           .action-button {
             @include peertube-button;
             @include grey-button;
+            @include button-with-icon(21px, 0, -1px);
+            @include apply-svg-color($grey-foreground-color);
 
             font-size: 15px;
             font-weight: $font-semibold;
@@ -194,53 +196,25 @@ $other-videos-width: 260px;
               display: none;
             }
 
-            .icon {
-              @include icon(21px);
-
-              position: relative;
-              top: -2px;
-
-              &.icon-like {
-                background-image: url('../../../assets/images/video/like-grey.svg');
-              }
-
-              &.icon-dislike {
-                background-image: url('../../../assets/images/video/dislike-grey.svg');
-              }
-
-              &.icon-support {
-                background-image: url('../../../assets/images/video/heart.svg');
-              }
-
-              &.icon-share {
-                background-image: url('../../../assets/images/video/share.svg');
-              }
-
-              &.icon-more {
-                background-image: url('../../../assets/images/video/more.svg');
-                top: -1px;
-              }
-            }
-
-            .icon-text {
-              margin-left: 3px;
-            }
-
             &.action-button-like.activated {
               background-color: $green;
 
-              .icon-like {
-                background-image: url('../../../assets/images/video/like-white.svg');
+              my-global-icon {
+                @include apply-svg-color(#fff);
               }
             }
 
             &.action-button-dislike.activated {
               background-color: $red;
 
-              .icon-dislike {
-                background-image: url('../../../assets/images/video/dislike-white.svg');
+              my-global-icon {
+                @include apply-svg-color(#fff);
               }
             }
+
+            .icon-text {
+              margin-left: 3px;
+            }
           }
 
           .action-more {
@@ -249,36 +223,12 @@ $other-videos-width: 260px;
             .dropdown-menu .dropdown-item {
               padding: 6px 24px;
 
-              .icon {
-                @include icon(24px);
+              my-global-icon {
+                width: 24px;
 
                 margin-right: 10px;
                 position: relative;
-                top: -1px;
-
-                &.icon-download {
-                  background-image: url('../../../assets/images/video/download-black.svg');
-                }
-
-                &.icon-edit {
-                  background-image: url('../../../assets/images/global/edit-black.svg');
-                }
-
-                &.icon-alert {
-                  background-image: url('../../../assets/images/video/alert.svg');
-                }
-
-                &.icon-blacklist {
-                  background-image: url('../../../assets/images/video/blacklist.svg');
-                }
-
-                &.icon-unblacklist {
-                  background-image: url('../../../assets/images/global/undo.svg');
-                }
-
-                &.icon-delete {
-                  background-image: url('../../../assets/images/global/delete-black.svg');
-                }
+                top: -2px;
               }
             }
           }
@@ -320,7 +270,7 @@ $other-videos-width: 260px;
       .video-info-description-more {
         cursor: pointer;
         font-weight: $font-semibold;
-        color: #585858;
+        color: $grey-foreground-color;
         font-size: 14px;
 
         .glyphicon {
@@ -339,7 +289,7 @@ $other-videos-width: 260px;
         min-width: 91px;
         padding-right: 5px;
         display: inline-block;
-        color: #585858;
+        color: $grey-foreground-color;
         font-weight: $font-bold;
       }
 
index 09ee96bdc66ef52100b90a7f0ae3bf91da5aef56..ee504bc58c8219320c9e8c656703e1c8d33b5ab3 100644 (file)
@@ -5,7 +5,7 @@ import { RedirectService } from '@app/core/routing/redirect.service'
 import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage'
 import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { MetaService } from '@ngx-meta/core'
-import { NotificationsService } from 'angular2-notifications'
+import { Notifier, ServerService } from '@app/core'
 import { forkJoin, Subscription } from 'rxjs'
 // FIXME: something weird with our path definition in tsconfig and typings
 // @ts-ignore
@@ -13,24 +13,23 @@ import videojs from 'video.js'
 import 'videojs-hotkeys'
 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
 import * as WebTorrent from 'webtorrent'
-import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoRateType, VideoState } from '../../../../../shared'
+import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared'
 import '../../../assets/player/peertube-videojs-plugin'
 import { AuthService, ConfirmService } from '../../core'
 import { RestExtractor, VideoBlacklistService } from '../../shared'
 import { VideoDetails } from '../../shared/video/video-details.model'
 import { VideoService } from '../../shared/video/video.service'
-import { MarkdownService } from '../shared'
 import { VideoDownloadComponent } from './modal/video-download.component'
 import { VideoReportComponent } from './modal/video-report.component'
 import { VideoShareComponent } from './modal/video-share.component'
 import { VideoBlacklistComponent } from './modal/video-blacklist.component'
 import { SubscribeButtonComponent } from '@app/shared/user-subscription/subscribe-button.component'
 import { addContextMenu, getVideojsOptions, loadLocaleInVideoJS } from '../../../assets/player/peertube-player'
-import { ServerService } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { environment } from '../../../environments/environment'
 import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
 import { VideoCaptionService } from '@app/shared/video-caption'
+import { MarkdownService } from '@app/shared/renderer'
 
 @Component({
   selector: 'my-video-watch',
@@ -77,7 +76,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     private authService: AuthService,
     private serverService: ServerService,
     private restExtractor: RestExtractor,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private markdownService: MarkdownService,
     private zone: NgZone,
     private redirectService: RedirectService,
@@ -118,7 +117,9 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         )
         .subscribe(([ video, captionsResult ]) => {
           const startTime = this.route.snapshot.queryParams.start
-          this.onVideoFetched(video, captionsResult.data, startTime)
+          const subtitle = this.route.snapshot.queryParams.subtitle
+
+          this.onVideoFetched(video, captionsResult.data, { startTime, subtitle })
               .catch(err => this.handleError(err))
         })
     })
@@ -203,7 +204,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
           error => {
             this.descriptionLoading = false
-            this.notificationsService.error(this.i18n('Error'), error.message)
+            this.notifier.error(error.message)
           }
         )
   }
@@ -245,16 +246,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
     this.videoBlacklistService.removeVideoFromBlacklist(this.video.id).subscribe(
       () => {
-        this.notificationsService.success(
-          this.i18n('Success'),
-          this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name })
-        )
+        this.notifier.success(this.i18n('Video {{name}} removed from the blacklist.', { name: this.video.name }))
 
         this.video.blacklisted = false
         this.video.blacklistedReason = null
       },
 
-      err => this.notificationsService.error(this.i18n('Error'), err.message)
+      err => this.notifier.error(err.message)
     )
   }
 
@@ -292,17 +290,14 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
     this.videoService.removeVideo(this.video.id)
         .subscribe(
-          status => {
-            this.notificationsService.success(
-              this.i18n('Success'),
-              this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name })
-            )
+          () => {
+            this.notifier.success(this.i18n('Video {{videoName}} deleted.', { videoName: this.video.name }))
 
             // Go back to the video-list.
             this.redirectService.redirectToHomepage()
           },
 
-          error => this.notificationsService.error(this.i18n('Error'), error.message)
+          error => this.notifier.error(error.message)
         )
   }
 
@@ -352,7 +347,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       return
     }
 
-    this.notificationsService.error(this.i18n('Error'), errorMessage)
+    this.notifier.error(errorMessage)
   }
 
   private checkUserRating () {
@@ -367,11 +362,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
             }
           },
 
-          err => this.notificationsService.error(this.i18n('Error'), err.message)
+          err => this.notifier.error(err.message)
         )
   }
 
-  private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], startTimeFromUrl: number) {
+  private async onVideoFetched (video: VideoDetails, videoCaptions: VideoCaption[], urlOptions: { startTime: number, subtitle: string }) {
     this.video = video
 
     // Re init attributes
@@ -379,8 +374,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.completeDescriptionShown = false
     this.remoteServerDown = false
 
-    let startTime = startTimeFromUrl || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
-    // Don't start the video if we are at the end
+    let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
+    // If we are at the end of the video, reset the timer
     if (this.video.duration - startTime <= 1) startTime = 0
 
     if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
@@ -419,10 +414,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       peertubeLink: false,
       poster: this.video.previewUrl,
       startTime,
+      subtitle: urlOptions.subtitle,
       theaterMode: true,
       language: this.localeId,
 
-      userWatching: this.user ? {
+      userWatching: this.user && this.user.videosHistoryEnabled === true ? {
         url: this.videoService.getUserWatchingVideoUrl(this.video.uuid),
         authorizationHeader: this.authService.getRequestHeaderValue()
       } : undefined
@@ -472,7 +468,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
               this.userRating = nextRating
             },
 
-            (err: { message: string }) => this.notificationsService.error(this.i18n('Error'), err.message)
+            (err: { message: string }) => this.notifier.error(err.message)
           )
   }
 
index 54a12c1262fe15e3168e3f1187a58da11276e435..2f448db780c9000f899187d28cb39577b1590914 100644 (file)
@@ -1,9 +1,7 @@
 import { NgModule } from '@angular/core'
-import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
 import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
 import { ClipboardModule } from 'ngx-clipboard'
 import { SharedModule } from '../../shared'
-import { MarkdownService } from '../shared'
 import { VideoCommentAddComponent } from './comment/video-comment-add.component'
 import { VideoCommentComponent } from './comment/video-comment.component'
 import { VideoCommentService } from './comment/video-comment.service'
@@ -46,8 +44,6 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio
   ],
 
   providers: [
-    MarkdownService,
-    LinkifierService,
     VideoCommentService
   ]
 })
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts
deleted file mode 100644 (file)
index 7a66944..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export * from './markdown.service'
index 9d000cf2ed6a003faa39a7bef17bd3d5f966886f..c0be4b88564a84066947ba33a5f502030fc7bd71 100644 (file)
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { immutableAssign } from '@app/shared/misc/utils'
 import { Location } from '@angular/common'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../../core/auth'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 import { VideoSortField } from '../../shared/video/sort-field.type'
@@ -11,6 +10,7 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { ScreenService } from '@app/shared/misc/screen.service'
 import { UserRight } from '../../../../../shared/models/users'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-videos-local',
@@ -26,7 +26,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On
   constructor (
     protected router: Router,
     protected route: ActivatedRoute,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected location: Location,
     protected i18n: I18n,
index 2c6054721315d3d66fb3776156187dd1997e3dd8..7ff52b259f04f2c6a754f3a2f72b23e6b7f0a7f3 100644 (file)
@@ -1,6 +1,5 @@
 import { Component, OnInit } from '@angular/core'
-import { AuthService } from '@app/core'
-import { NotificationsService } from 'angular2-notifications'
+import { AuthService, Notifier } from '@app/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { VideosOverview } from '@app/shared/overview/videos-overview.model'
 import { OverviewService } from '@app/shared/overview'
@@ -21,7 +20,7 @@ export class VideoOverviewComponent implements OnInit {
 
   constructor (
     private i18n: I18n,
-    private notificationsService: NotificationsService,
+    private notifier: Notifier,
     private authService: AuthService,
     private overviewService: OverviewService
   ) { }
@@ -43,10 +42,7 @@ export class VideoOverviewComponent implements OnInit {
             ) this.notResults = true
           },
 
-          err => {
-            console.log(err)
-            this.notificationsService.error('Error', err.text)
-          }
+          err => this.notifier.error(err.message)
         )
   }
 
index ac1fcfff379dc12faef537fddc28a3265c07a43c..f99c8abb648b820ac9c756ba7559b54a000a8559 100644 (file)
@@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { Location } from '@angular/common'
 import { immutableAssign } from '@app/shared/misc/utils'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../../core/auth'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 import { VideoSortField } from '../../shared/video/sort-field.type'
 import { VideoService } from '../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { ScreenService } from '@app/shared/misc/screen.service'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-videos-recently-added',
@@ -24,7 +24,7 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On
     protected router: Router,
     protected route: ActivatedRoute,
     protected location: Location,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected i18n: I18n,
     protected screenService: ScreenService,
index 8f3d3842b494a31c74e6de679ee8c3159b5fb918..6fd74e67a8e35f0e0bd930a6174429e68bf39553 100644 (file)
@@ -2,13 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { Location } from '@angular/common'
 import { immutableAssign } from '@app/shared/misc/utils'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../../core/auth'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 import { VideoSortField } from '../../shared/video/sort-field.type'
 import { VideoService } from '../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { ScreenService } from '@app/shared/misc/screen.service'
+import { Notifier, ServerService } from '@app/core'
 
 @Component({
   selector: 'my-videos-trending',
@@ -23,22 +23,37 @@ export class VideoTrendingComponent extends AbstractVideoList implements OnInit,
   constructor (
     protected router: Router,
     protected route: ActivatedRoute,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected location: Location,
     protected screenService: ScreenService,
+    private serverService: ServerService,
     protected i18n: I18n,
     private videoService: VideoService
   ) {
     super()
-
-    this.titlePage = i18n('Trending')
   }
 
   ngOnInit () {
     super.ngOnInit()
 
     this.generateSyndicationList()
+
+    this.serverService.configLoaded.subscribe(
+      () => {
+        const trendingDays = this.serverService.getConfig().trending.videos.intervalDays
+
+        if (trendingDays === 1) {
+          this.titlePage = this.i18n('Trending for the last 24 hours')
+          this.titleTooltip = this.i18n('Trending videos are those totalizing the greatest number of views during the last 24 hours.')
+        } else {
+          this.titlePage = this.i18n('Trending for the last {{days}} days', { days: trendingDays })
+          this.titleTooltip = this.i18n(
+            'Trending videos are those totalizing the greatest number of views during the last {{days}} days.',
+            { days: trendingDays }
+          )
+        }
+      })
   }
 
   ngOnDestroy () {
index 6e8959c54a34ed41591c6961903a3a7b6ea52357..bee828e126a23e6bc24aa01237119a839ef698f5 100644 (file)
@@ -2,7 +2,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'
 import { ActivatedRoute, Router } from '@angular/router'
 import { immutableAssign } from '@app/shared/misc/utils'
 import { Location } from '@angular/common'
-import { NotificationsService } from 'angular2-notifications'
 import { AuthService } from '../../core/auth'
 import { AbstractVideoList } from '../../shared/video/abstract-video-list'
 import { VideoSortField } from '../../shared/video/sort-field.type'
@@ -10,6 +9,7 @@ import { VideoService } from '../../shared/video/video.service'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { ScreenService } from '@app/shared/misc/screen.service'
 import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
+import { Notifier } from '@app/core'
 
 @Component({
   selector: 'my-videos-user-subscriptions',
@@ -25,7 +25,7 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement
   constructor (
     protected router: Router,
     protected route: ActivatedRoute,
-    protected notificationsService: NotificationsService,
+    protected notifier: Notifier,
     protected authService: AuthService,
     protected location: Location,
     protected i18n: I18n,
similarity index 72%
rename from client/src/assets/images/global/add.svg
rename to client/src/assets/images/global/add.html
index 42b269c433f04340bd2701c5f4287f42fbf622ca..bfb0a52bccf57821bdf54c1ceed6e34605f95e04 100644 (file)
@@ -1,8 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-92.000000, -115.000000)">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-92.000000, -115.000000)">
             <g id="2" transform="translate(92.000000, 115.000000)">
                 <circle id="Oval-1" stroke="#ffffff" stroke-width="2" cx="12" cy="12" r="10"></circle>
                 <rect id="Rectangle-1" fill="#ffffff" x="11" y="7" width="2" height="10" rx="1"></rect>
similarity index 71%
rename from client/src/assets/images/video/alert.svg
rename to client/src/assets/images/global/alert.html
index 5b43534add5a6e55e98270a38d453ae876dd3be7..7c8c020743859d55aecd2cd3b9eb145674b498d5 100644 (file)
@@ -1,11 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>alert</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-48.000000, -467.000000)">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-48.000000, -467.000000)">
             <g id="161" transform="translate(48.000000, 467.000000)">
                 <path d="M12.8715755,3.50973876 L12,1.96027114 L11.1284245,3.50973876 L2.12842446,19.5097388 L1.29015252,21 L3,21 L21,21 L22.7098475,21 L21.8715755,19.5097388 L12.8715755,3.50973876 Z" id="Triangle-2" stroke="#000000" stroke-width="2" stroke-linejoin="round"></path>
                 <path d="M12,17.75 C12.6903559,17.75 13.25,17.1903559 13.25,16.5 C13.25,15.8096441 12.6903559,15.25 12,15.25 C11.3096441,15.25 10.75,15.8096441 10.75,16.5 C10.75,17.1903559 11.3096441,17.75 12,17.75 Z" id="Oval-8" fill="#000000"></path>
diff --git a/client/src/assets/images/global/circle-tick.html b/client/src/assets/images/global/circle-tick.html
new file mode 100644 (file)
index 0000000..2327de6
--- /dev/null
@@ -0,0 +1,12 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g transform="translate(-400.000000, -1134.000000)" stroke="#000000" stroke-width="2">
+      <g id="Extras" transform="translate(48.000000, 1046.000000)">
+        <g id="yes" transform="translate(352.000000, 88.000000)">
+          <circle id="Oval-1" cx="12" cy="12" r="10"/>
+          <polyline id="Path-288" stroke-linecap="round" stroke-linejoin="round" points="8.5 12.5 10.5 14.5 15.5 9.5"/>
+        </g>
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/client/src/assets/images/global/cloud-download.html b/client/src/assets/images/global/cloud-download.html
new file mode 100644 (file)
index 0000000..b2634fd
--- /dev/null
@@ -0,0 +1,11 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-356.000000, -775.000000)" stroke="#000000" stroke-width="2">
+            <g id="308" transform="translate(356.000000, 775.000000)">
+                <path d="M8,17 L5,17 L5,17 C2.790861,17 1,15.209139 1,13 C1,10.790861 2.790861,9 5,9 C5.35840468,9 5.70579988,9.04713713 6.03632437,9.13555013 C6.01233106,8.92702603 6,8.71495305 6,8.5 C6,5.46243388 8.46243388,3 11.5,3 C14.0673313,3 16.2238156,4.7590449 16.8299648,7.1376465 C17.2052921,7.04765874 17.5970804,7 18,7 C20.7614237,7 23,9.23857625 23,12 C23,14.7614237 20.7614237,17 18,17 L16,17" id="Combined-Shape" stroke-linejoin="round"></path>
+                <path d="M12,13 L12,21" id="Path-58"></path>
+                <polyline id="Path-59" stroke-linejoin="round" points="15 20 12 23 9 20"></polyline>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/global/cloud-error.html b/client/src/assets/images/global/cloud-error.html
new file mode 100644 (file)
index 0000000..1a34838
--- /dev/null
@@ -0,0 +1,11 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g transform="translate(-400.000000, -775.000000)" stroke="#000000" stroke-width="2">
+            <g id="309" transform="translate(400.000000, 775.000000)">
+                <path d="M7,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L17,18" id="Combined-Shape"></path>
+                <path d="M9,21 L15,15" id="Path-238"></path>
+                <path d="M9,21 L15,15" id="Path-238" transform="translate(12.000000, 18.000000) scale(-1, 1) translate(-12.000000, -18.000000) "></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/global/cog.html b/client/src/assets/images/global/cog.html
new file mode 100644 (file)
index 0000000..b74a180
--- /dev/null
@@ -0,0 +1,9 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linejoin="round">
+    <g transform="translate(-796.000000, -159.000000)" stroke="#000000" stroke-width="2">
+      <g id="38" transform="translate(796.000000, 159.000000)">
+        <path d="M7.20852293,4.3800958 C8.05442158,3.84706631 8.99528987,3.45099725 10,3.22301642 L10,1.99980749 C10,1.44762906 10.4433532,1 11.0093689,1 L12.9906311,1 C13.5480902,1 14,1.44371665 14,1.99980749 L14,3.22301642 C15.0047101,3.45099725 15.9455784,3.84706631 16.7914771,4.3800958 L17.6569904,3.5145825 C18.0474395,3.12413339 18.6774591,3.12110988 19.0776926,3.52134344 L20.4786566,4.92230738 C20.8728396,5.31649045 20.8786331,5.94979402 20.4854175,6.34300963 L19.6199042,7.20852293 C20.1529337,8.05442158 20.5490027,8.99528987 20.7769836,10 L22.0001925,10 C22.5523709,10 23,10.4433532 23,11.0093689 L23,12.9906311 C23,13.5480902 22.5562834,14 22.0001925,14 L20.7769836,14 C20.5490027,15.0047101 20.1529337,15.9455784 19.6199042,16.7914771 L20.4854175,17.6569904 C20.8758666,18.0474395 20.8788901,18.6774591 20.4786566,19.0776926 L19.0776926,20.4786566 C18.6835095,20.8728396 18.050206,20.8786331 17.6569904,20.4854175 L16.7914771,19.6199042 C15.9455784,20.1529337 15.0047101,20.5490027 14,20.7769836 L14,22.0001925 C14,22.5523709 13.5566468,23 12.9906311,23 L11.0093689,23 C10.4519098,23 10,22.5562834 10,22.0001925 L10,20.7769836 C8.99528987,20.5490027 8.05442158,20.1529337 7.20852293,19.6199042 L6.34300963,20.4854175 C5.95256051,20.8758666 5.32254093,20.8788901 4.92230738,20.4786566 L3.52134344,19.0776926 C3.12716036,18.6835095 3.12136689,18.050206 3.5145825,17.6569904 L4.3800958,16.7914771 C3.84706631,15.9455784 3.45099725,15.0047101 3.22301642,14 L1.99980749,14 C1.44762906,14 1,13.5566468 1,12.9906311 L1,11.0093689 C1,10.4519098 1.44371665,10 1.99980749,10 L3.22301642,10 C3.45099725,8.99528987 3.84706631,8.05442158 4.3800958,7.20852293 L3.5145825,6.34300963 C3.12413339,5.95256051 3.12110988,5.32254093 3.52134344,4.92230738 L4.92230738,3.52134344 C5.31649045,3.12716036 5.94979402,3.12136689 6.34300963,3.5145825 L7.20852293,4.3800958 Z M12,16 C14.209139,16 16,14.209139 16,12 C16,9.790861 14.209139,8 12,8 C9.790861,8 8,9.790861 8,12 C8,14.209139 9.790861,16 12,16 Z" id="Combined-Shape"/>
+      </g>
+    </g>
+  </g>
+</svg>
similarity index 63%
rename from client/src/assets/images/global/cross.svg
rename to client/src/assets/images/global/cross.html
index d47a759960ba977bb4c6da4560502bec13aa60b7..9625784877d67aa8d4ec850158bddf59c6204dc5 100644 (file)
@@ -1,8 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-312.000000, -115.000000)" stroke="#585858" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-312.000000, -115.000000)" stroke="#000000" stroke-width="2">
             <g id="7" transform="translate(312.000000, 115.000000)">
                 <path d="M19,5 L5,19" id="Path-14"></path>
                 <path d="M19,5 L5,19" id="Path-14" transform="translate(12.000000, 12.000000) scale(-1, 1) translate(-12.000000, -12.000000) "></path>
diff --git a/client/src/assets/images/global/delete-black.svg b/client/src/assets/images/global/delete-black.svg
deleted file mode 100644 (file)
index 04ddc23..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
-            <g id="25" transform="translate(224.000000, 159.000000)">
-                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#000" stroke-width="2"></path>
-                <rect id="Rectangle-424" fill="#000" x="2" y="4" width="20" height="2" rx="1"></rect>
-                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#000"></path>
-                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#000" stroke-width="2" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/global/delete-white.svg b/client/src/assets/images/global/delete-white.svg
deleted file mode 100644 (file)
index 9c52de5..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
-            <g id="25" transform="translate(224.000000, 159.000000)">
-                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#ffffff" stroke-width="2"></path>
-                <rect id="Rectangle-424" fill="#ffffff" x="2" y="4" width="20" height="2" rx="1"></rect>
-                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#ffffff"></path>
-                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#ffffff" stroke-width="2" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
similarity index 71%
rename from client/src/assets/images/global/delete-grey.svg
rename to client/src/assets/images/global/delete.html
index 67e9e2ce72deaa33b4670a8eef00590ad4a64cc2..a0d9a0cac4aab5ca60da6d50fe51d3e911b3cfe2 100644 (file)
@@ -1,13 +1,11 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-224.000000, -159.000000)">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-224.000000, -159.000000)">
             <g id="25" transform="translate(224.000000, 159.000000)">
-                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#585858" stroke-width="2"></path>
-                <rect id="Rectangle-424" fill="#585858" x="2" y="4" width="20" height="2" rx="1"></rect>
-                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#585858"></path>
-                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#585858" stroke-width="2" stroke-linejoin="round"></path>
+                <path d="M5,7 L5,20.0081158 C5,21.1082031 5.89706013,22 7.00585866,22 L16.9941413,22 C18.1019465,22 19,21.1066027 19,20.0081158 L19,7" id="Path-296" stroke="#000000" stroke-width="2"></path>
+                <rect id="Rectangle-424" fill="#000000" x="2" y="4" width="20" height="2" rx="1"></rect>
+                <path d="M9,10.9970301 C9,10.4463856 9.44386482,10 10,10 C10.5522847,10 11,10.4530363 11,10.9970301 L11,17.0029699 C11,17.5536144 10.5561352,18 10,18 C9.44771525,18 9,17.5469637 9,17.0029699 L9,10.9970301 Z M13,10.9970301 C13,10.4463856 13.4438648,10 14,10 C14.5522847,10 15,10.4530363 15,10.9970301 L15,17.0029699 C15,17.5536144 14.5561352,18 14,18 C13.4477153,18 13,17.5469637 13,17.0029699 L13,10.9970301 Z" id="Combined-Shape" fill="#000000"></path>
+                <path d="M9,5 L9,2.99895656 C9,2.44724809 9.45097518,2 9.99077797,2 L14.009222,2 C14.5564136,2 15,2.44266033 15,2.99895656 L15,5" id="Path-33" stroke="#000000" stroke-width="2" stroke-linejoin="round"></path>
             </g>
         </g>
     </g>
similarity index 64%
rename from client/src/assets/images/video/download-black.svg
rename to client/src/assets/images/global/download.html
index 501836746eb5313172f243e992a629e239a5d219..259506f31c065ba589d72477e43eb1fe7c2323f7 100644 (file)
@@ -1,11 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>download</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#000000" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-180.000000, -291.000000)" stroke="#000000" stroke-width="2">
             <g id="84" transform="translate(180.000000, 291.000000)">
                 <path d="M12,3 L12,15" id="Path-58"></path>
                 <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
diff --git a/client/src/assets/images/global/edit-black.svg b/client/src/assets/images/global/edit-black.svg
deleted file mode 100644 (file)
index 0176b0f..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>edit</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-48.000000, -203.000000)" stroke="#000000" stroke-width="2">
-            <g id="41" transform="translate(48.000000, 203.000000)">
-                <path d="M3,21.0000003 L3,17 L15.8898356,4.11016442 C17.0598483,2.9401517 18.9638992,2.94723715 20.1306896,4.11402752 L19.9181432,3.90148112 C21.0902894,5.07362738 21.0882407,6.97202708 19.9174652,8.1377941 L7,21.0000003 L3,21.0000003 Z" id="Path-74" stroke-linecap="round" stroke-linejoin="round"></path>
-                <path d="M14.5,5.5 L18.5,9.5" id="Path-75"></path>
-            </g>
-        </g>
-    </g>
-</svg>
similarity index 62%
rename from client/src/assets/images/global/edit-grey.svg
rename to client/src/assets/images/global/edit.html
index 23ece68f1b3f0a023309c95eb0773749ab491c0e..f04183c2de0ed1d0f695e8e16bc728ed56361304 100644 (file)
@@ -1,11 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>edit</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-48.000000, -203.000000)" stroke="#585858" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-48.000000, -203.000000)" stroke="#000000" stroke-width="2">
             <g id="41" transform="translate(48.000000, 203.000000)">
                 <path d="M3,21.0000003 L3,17 L15.8898356,4.11016442 C17.0598483,2.9401517 18.9638992,2.94723715 20.1306896,4.11402752 L19.9181432,3.90148112 C21.0902894,5.07362738 21.0882407,6.97202708 19.9174652,8.1377941 L7,21.0000003 L3,21.0000003 Z" id="Path-74" stroke-linecap="round" stroke-linejoin="round"></path>
                 <path d="M14.5,5.5 L18.5,9.5" id="Path-75"></path>
similarity index 76%
rename from client/src/assets/images/global/help.svg
rename to client/src/assets/images/global/help.html
index 48252febea2737bd2de82a2fc212730ba86f1344..80cd403217e22e81ac124c31624427ed885c4a7b 100644 (file)
@@ -1,12 +1,10 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-400.000000, -247.000000)">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-400.000000, -247.000000)">
             <g id="69" transform="translate(400.000000, 247.000000)">
-                <circle id="Oval-7" stroke="#333333" stroke-width="2" cx="12" cy="12" r="10"></circle>
-                <path d="M12.016,14.544 C12.384,14.544 12.64,14.256 12.704,13.904 L12.768,13.168 C14.544,12.864 16,11.952 16,9.936 L16,9.904 C16,7.904 14.48,6.656 12.24,6.656 C10.768,6.656 9.696,7.184 8.848,7.984 C8.624,8.176 8.528,8.432 8.528,8.672 C8.528,9.152 8.928,9.552 9.424,9.552 C9.648,9.552 9.856,9.456 10.016,9.328 C10.656,8.752 11.344,8.448 12.192,8.448 C13.344,8.448 14.032,9.072 14.032,9.968 L14.032,10 C14.032,11.008 13.2,11.584 11.696,11.728 C11.264,11.776 11.008,12.096 11.072,12.528 L11.232,13.904 C11.28,14.272 11.552,14.544 11.92,14.544 L12.016,14.544 Z M10.784,16.816 L10.784,16.976 C10.784,17.6 11.264,18.08 11.92,18.08 C12.576,18.08 13.056,17.6 13.056,16.976 L13.056,16.816 C13.056,16.192 12.576,15.712 11.92,15.712 C11.264,15.712 10.784,16.192 10.784,16.816 Z" id="?" fill="#333333"></path>
+                <circle id="Oval-7" stroke="#000000" stroke-width="2" cx="12" cy="12" r="10"></circle>
+                <path d="M12.016,14.544 C12.384,14.544 12.64,14.256 12.704,13.904 L12.768,13.168 C14.544,12.864 16,11.952 16,9.936 L16,9.904 C16,7.904 14.48,6.656 12.24,6.656 C10.768,6.656 9.696,7.184 8.848,7.984 C8.624,8.176 8.528,8.432 8.528,8.672 C8.528,9.152 8.928,9.552 9.424,9.552 C9.648,9.552 9.856,9.456 10.016,9.328 C10.656,8.752 11.344,8.448 12.192,8.448 C13.344,8.448 14.032,9.072 14.032,9.968 L14.032,10 C14.032,11.008 13.2,11.584 11.696,11.728 C11.264,11.776 11.008,12.096 11.072,12.528 L11.232,13.904 C11.28,14.272 11.552,14.544 11.92,14.544 L12.016,14.544 Z M10.784,16.816 L10.784,16.976 C10.784,17.6 11.264,18.08 11.92,18.08 C12.576,18.08 13.056,17.6 13.056,16.976 L13.056,16.816 C13.056,16.192 12.576,15.712 11.92,15.712 C11.264,15.712 10.784,16.192 10.784,16.816 Z" id="?" fill="#000000"></path>
             </g>
         </g>
     </g>
-</svg>
\ No newline at end of file
+</svg>
similarity index 70%
rename from client/src/assets/images/global/im-with-her.svg
rename to client/src/assets/images/global/im-with-her.html
index 31d4754fdd5d94747844e61c8c6fce6cafaed3be..de2c62e966677dc3c6e221846bf5e39d596337de 100644 (file)
@@ -1,15 +1,10 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>im-with-her</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-708.000000, -467.000000)">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-708.000000, -467.000000)">
             <g id="176" transform="translate(708.000000, 467.000000)">
-                <path d="M8,9 L8,3.99339768 C8,3.44494629 7.55641359,3 7.00922203,3 L2.99077797,3 C2.45097518,3 2,3.44475929 2,3.99339768 L2,20.0066023 C2,20.5550537 2.44358641,21 2.99077797,21 L7.00922203,21 C7.54902482,21 8,20.5552407 8,20.0066023 L8,15 L14,15 L14,20.0066023 C14,20.5550537 14.4435864,21 14.990778,21 L19.009222,21 C19.5490248,21 20,20.5564587 20,20.0093228 L20,15.0006104 L23,12 L20,8.99267578 L20,4.00303919 C20,3.45042467 19.5564136,3 19.009222,3 L14.990778,3 C14.4509752,3 14,3.44475929 14,3.99339768 L14,9 L8,9 Z" id="Combined-Shape" fill="#333333" opacity="0.5"></path>
+                <path d="M8,9 L8,3.99339768 C8,3.44494629 7.55641359,3 7.00922203,3 L2.99077797,3 C2.45097518,3 2,3.44475929 2,3.99339768 L2,20.0066023 C2,20.5550537 2.44358641,21 2.99077797,21 L7.00922203,21 C7.54902482,21 8,20.5552407 8,20.0066023 L8,15 L14,15 L14,20.0066023 C14,20.5550537 14.4435864,21 14.990778,21 L19.009222,21 C19.5490248,21 20,20.5564587 20,20.0093228 L20,15.0006104 L23,12 L20,8.99267578 L20,4.00303919 C20,3.45042467 19.5564136,3 19.009222,3 L14.990778,3 C14.4509752,3 14,3.44475929 14,3.99339768 L14,9 L8,9 Z" id="Combined-Shape" fill="#000000" opacity="0.5"></path>
                 <path d="M2,9 L14,9 L14,3.99077797 C14,3.44358641 14.3203148,3.32031476 14.7062149,3.7062149 L23,12 L14.7062149,20.2937851 C14.3161832,20.6838168 14,20.5490248 14,20.009222 L14,15 L2,15 L2,9 Z" id="Rectangle-121" fill-opacity="0.5" fill="#000000"></path>
             </g>
         </g>
     </g>
-</svg>
\ No newline at end of file
+</svg>
diff --git a/client/src/assets/images/global/no.html b/client/src/assets/images/global/no.html
new file mode 100644 (file)
index 0000000..bb7b285
--- /dev/null
@@ -0,0 +1,10 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-312.000000, -863.000000)" stroke="#000000" stroke-width="2">
+            <g id="347" transform="translate(312.000000, 863.000000)">
+                <circle id="Oval-196" cx="12" cy="12" r="9"></circle>
+                <path d="M18,18 L6,6" id="Path-275"></path>
+            </g>
+        </g>
+    </g>
+</svg>
diff --git a/client/src/assets/images/global/sparkle.html b/client/src/assets/images/global/sparkle.html
new file mode 100644 (file)
index 0000000..3b29fef
--- /dev/null
@@ -0,0 +1,11 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+        <g transform="translate(-488.000000, -731.000000)" stroke="#000000" stroke-width="2">
+            <g id="291" transform="translate(488.000000, 731.000000)">
+                <path d="M10,9 C8.5,7.5 8,3 8,3 C8,3 7.5,7.5 6,9 C4.5,10.5 2,11 2,11 C2,11 4.5,11.5 6,13 C7.5,14.5 8,19 8,19 C8,19 8.5,14.5 10,13 C11.5,11.5 14,11 14,11 C14,11 11.5,10.5 10,9 Z" id="Combined-Shape"></path>
+                <path d="M19.6666667,4.75 C18.7916667,3.8125 18.5,1 18.5,1 C18.5,1 18.2083333,3.8125 17.3333333,4.75 C16.4583333,5.6875 15,6 15,6 C15,6 16.4583333,6.3125 17.3333333,7.25 C18.2083333,8.1875 18.5,11 18.5,11 C18.5,11 18.7916667,8.1875 19.6666667,7.25 C20.5416667,6.3125 22,6 22,6 C22,6 20.5416667,5.6875 19.6666667,4.75 Z" id="Combined-Shape"></path>
+                <path d="M17,17 C16.25,16.25 16,14 16,14 C16,14 15.75,16.25 15,17 C14.25,17.75 13,18 13,18 C13,18 14.25,18.25 15,19 C15.75,19.75 16,22 16,22 C16,22 16.25,19.75 17,19 C17.75,18.25 19,18 19,18 C19,18 17.75,17.75 17,17 Z" id="Combined-Shape"></path>
+            </g>
+        </g>
+    </g>
+</svg>
similarity index 88%
rename from client/src/assets/images/global/syndication.svg
rename to client/src/assets/images/global/syndication.html
index cb74cf81bce21badd5f88c3542ac9d972d16b336..e6c88a4dbc0dd7e0b9f06a791c8f319155d4dda5 100644 (file)
@@ -1,10 +1,8 @@
-<?xml version="1.0" encoding="iso-8859-1"?>\r
-<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->\r
 <svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"\r
         viewBox="0 0 559.372 559.372" style="enable-background:new 0 0 559.372 559.372;" xml:space="preserve">\r
 <g>\r
        <g>\r
-               <path style="fill:#010002;" d="M53.244,0.002c46.512,0,91.29,6.018,134.334,18.054s83.334,29.07,120.869,51.102\r
+               <path fill="#000000" d="M53.244,0.002c46.512,0,91.29,6.018,134.334,18.054s83.334,29.07,120.869,51.102\r
                        c37.537,22.032,71.707,48.45,102.514,79.254c30.803,30.804,57.221,64.974,79.254,102.51\r
                        c22.029,37.539,39.063,77.828,51.102,120.873c12.037,43.043,18.055,87.818,18.055,134.334c0,14.688-5.201,27.23-15.605,37.637\r
                        c-10.404,10.407-22.949,15.604-37.637,15.604c-14.689,0-27.234-5.199-37.641-15.604c-10.402-10.404-15.604-22.949-15.604-37.637\r
similarity index 63%
rename from client/src/assets/images/global/tick.svg
rename to client/src/assets/images/global/tick.html
index 230caa1117c497d0233d8fc721efe9d4bffd5b63..4784b4807c08b625a119cd1107df51d7460b4da1 100644 (file)
@@ -1,8 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-356.000000, -115.000000)" stroke="#585858" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-356.000000, -115.000000)" stroke="#000000" stroke-width="2">
             <g id="8" transform="translate(356.000000, 115.000000)">
                 <path d="M21,6 L9,18" id="Path-14"></path>
                 <path d="M9,13 L4,18" id="Path-14" transform="translate(6.500000, 15.500000) scale(-1, 1) translate(-6.500000, -15.500000) "></path>
diff --git a/client/src/assets/images/global/undo.html b/client/src/assets/images/global/undo.html
new file mode 100644 (file)
index 0000000..228245c
--- /dev/null
@@ -0,0 +1,9 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+    <g transform="translate(-180.000000, -115.000000)" fill="#000000">
+      <g id="4" transform="translate(180.000000, 115.000000)">
+        <path d="M10,19 C10.5522847,19 11,19.4477153 11,20 C11,20.5522847 10.5522847,21 10,21 C9.99404288,21 9.98809793,20.9999479 9.98216558,20.9998442 C5.01980239,20.990358 1,16.9646166 1,12 C1,7.02943725 5.02943725,3 10,3 C14.9705627,3 19,7.02943725 19,12 L17,12 C17,8.13400675 13.8659932,5 10,5 C6.13400675,5 3,8.13400675 3,12 C3,15.8659932 6.13400675,19 10,19 Z M14,12 L22,12 L18,16 L14,12 Z" id="Combined-Shape" transform="translate(11.500000, 12.000000) scale(-1, 1) translate(-11.500000, -12.000000) "/>
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/client/src/assets/images/global/undo.svg b/client/src/assets/images/global/undo.svg
deleted file mode 100644 (file)
index f1cca03..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-180.000000, -115.000000)" fill="#000">
-            <g id="4" transform="translate(180.000000, 115.000000)">
-                <path d="M10,19 C10.5522847,19 11,19.4477153 11,20 C11,20.5522847 10.5522847,21 10,21 C9.99404288,21 9.98809793,20.9999479 9.98216558,20.9998442 C5.01980239,20.990358 1,16.9646166 1,12 C1,7.02943725 5.02943725,3 10,3 C14.9705627,3 19,7.02943725 19,12 L17,12 C17,8.13400675 13.8659932,5 10,5 C6.13400675,5 3,8.13400675 3,12 C3,15.8659932 6.13400675,19 10,19 Z M14,12 L22,12 L18,16 L14,12 Z" id="Combined-Shape" transform="translate(11.500000, 12.000000) scale(-1, 1) translate(-11.500000, -12.000000) "></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/global/user-add.html b/client/src/assets/images/global/user-add.html
new file mode 100644 (file)
index 0000000..57df23c
--- /dev/null
@@ -0,0 +1,11 @@
+<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-136.000000, -863.000000)">
+            <g id="343" transform="translate(136.000000, 863.000000)">
+                <path d="M14.2571621,15 L7,15 C4.20063223,15 2.390348,16.1679253 1.5255785,18.0896353 C1.07423388,19.0926234 0.949016905,20.1108713 0.995546634,20.9698816 C0.998604759,21.0263393 1.0014872,21.0632937 1.00496281,21.0995037 C1.0599172,21.6490476 1.54995985,22.0499916 2.09950372,21.9950372 C2.64904758,21.9400828 3.04999158,21.4500401 2.99503719,20.9004963 C2.99555422,20.9071205 2.99399879,20.8871791 2.99261905,20.8617069 C2.96185588,20.2937714 3.05021139,19.575276 3.34942151,18.9103647 C3.890902,17.7070747 4.98686778,17 7,17 L12.0070975,17 L13.2070325,17 C13.4170071,16.2576107 13.7789623,15.5790321 14.2571621,15 Z" id="Path-41" fill="#000000" fill-rule="nonzero"></path>
+                <path d="M19,18 L19,16.4976988 C19,16.2228273 18.7680664,16 18.5,16 C18.2238576,16 18,16.2148438 18,16.4976988 L18,18 L16.4976988,18 C16.2148438,18 16,18.2238576 16,18.5 C16,18.7680664 16.2228273,19 16.4976988,19 L18,19 L18,20.5023012 C18,20.7771727 18.2319336,21 18.5,21 C18.7761424,21 19,20.7851562 19,20.5023012 L19,19 L20.5023012,19 C20.7851562,19 21,18.7761424 21,18.5 C21,18.2319336 20.7771727,18 20.5023012,18 L19,18 Z M18.5,23 C16.0147186,23 14,20.9852814 14,18.5 C14,16.0147186 16.0147186,14 18.5,14 C20.9852814,14 23,16.0147186 23,18.5 C23,20.9852814 20.9852814,23 18.5,23 Z" id="Combined-Shape" fill="#000000"></path>
+                <circle id="Oval-40" stroke="#000000" stroke-width="2" cx="12" cy="8" r="5"></circle>
+            </g>
+        </g>
+    </g>
+</svg>
similarity index 69%
rename from client/src/assets/images/global/validate.svg
rename to client/src/assets/images/global/validate.html
index 5c7ee9d146339ec89ca9e09fe173a9ebe98dbc4a..520624ff6605fd78b55dc10ea94c5e1bcafbc910 100644 (file)
@@ -1,8 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-400.000000, -1134.000000)" stroke="#ffffff" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-400.000000, -1134.000000)" stroke="#000000" stroke-width="2">
             <g id="Extras" transform="translate(48.000000, 1046.000000)">
                 <g id="yes" transform="translate(352.000000, 88.000000)">
                     <circle id="Oval-1" cx="12" cy="12" r="10"></circle>
diff --git a/client/src/assets/images/video/blacklist.svg b/client/src/assets/images/video/blacklist.svg
deleted file mode 100644 (file)
index 431c738..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>no</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-312.000000, -863.000000)" stroke="#000000" stroke-width="2">
-            <g id="347" transform="translate(312.000000, 863.000000)">
-                <circle id="Oval-196" cx="12" cy="12" r="9"></circle>
-                <path d="M18,18 L6,6" id="Path-275"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/video/dislike-white.svg b/client/src/assets/images/video/dislike-white.svg
deleted file mode 100644 (file)
index cfc6eaa..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#ffffff" stroke-width="2">
-            <g id="Extras" transform="translate(48.000000, 1046.000000)">
-                <g id="thumbs-down" transform="translate(704.000000, 44.000000)">
-                    <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path>
-                    <path d="M4,4.5 C4,4.5 3,7 3,10 C3,13 4,15.5 4,15.5" id="Path-189" transform="translate(3.500000, 10.000000) scale(1, -1) translate(-3.500000, -10.000000) "></path>
-                </g>
-            </g>
-        </g>
-    </g>
-</svg>
similarity index 77%
rename from client/src/assets/images/video/dislike-grey.svg
rename to client/src/assets/images/video/dislike.html
index 56a7908fb9d326786f12008bff978415c234f226..acde951e210910b9d182a575fca6809255adc356 100644 (file)
@@ -1,8 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-752.000000, -1090.000000)" stroke="#585858" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-752.000000, -1090.000000)" stroke="#000000" stroke-width="2">
             <g id="Extras" transform="translate(48.000000, 1046.000000)">
                 <g id="thumbs-down" transform="translate(704.000000, 44.000000)">
                     <path d="M6,16 C6,18.5 6.5,21 8,21 L16.9938335,21 C17.5495239,21 18.1819788,20.5956028 18.4072817,20.0949295 L20.8562951,14.6526776 C21.7640882,12.6353595 20.7154925,11 18.5092545,11 L15.5,11 C15.5,11 18.5,5 15,5 C12.5,5 11.5,11 8,11 C6.5,11 6,13.5 6,16 Z" id="Path-188" stroke-linejoin="round" transform="translate(13.591488, 13.000000) scale(1, -1) translate(-13.591488, -13.000000) "></path>
diff --git a/client/src/assets/images/video/download-grey.svg b/client/src/assets/images/video/download-grey.svg
deleted file mode 100644 (file)
index 5b0cca5..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>download</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#585858" stroke-width="2">
-            <g id="84" transform="translate(180.000000, 291.000000)">
-                <path d="M12,3 L12,15" id="Path-58"></path>
-                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
-                <path d="M3,18 L3,20.0590859 C3,20.6127331 3.44494889,21.0615528 3.99340349,21.0615528 L20.0067018,21.0615528 C20.5553434,21.0615528 21.0001052,20.6098102 21.0001051,20.0590859 L21.0001049,18" id="Path-12" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
diff --git a/client/src/assets/images/video/download-white.svg b/client/src/assets/images/video/download-white.svg
deleted file mode 100644 (file)
index 0e66e06..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>download</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-180.000000, -291.000000)" stroke="#ffffff" stroke-width="2">
-            <g id="84" transform="translate(180.000000, 291.000000)">
-                <path d="M12,3 L12,15" id="Path-58"></path>
-                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 14.000000) rotate(-270.000000) translate(-12.000000, -14.000000) " points="9 8 15 14 9 20"></polyline>
-                <path d="M3,18 L3,20.0590859 C3,20.6127331 3.44494889,21.0615528 3.99340349,21.0615528 L20.0067018,21.0615528 C20.5553434,21.0615528 21.0001052,20.6098102 21.0001051,20.0590859 L21.0001049,18" id="Path-12" stroke-linejoin="round"></path>
-            </g>
-        </g>
-    </g>
-</svg>
similarity index 66%
rename from client/src/assets/images/video/heart.svg
rename to client/src/assets/images/video/heart.html
index 5d64aee0f5f400fc4dd8398a0352816e885fa5bd..618f64f10aa6bc7b760261984cf7d9fe5d366e4a 100644 (file)
@@ -1,9 +1,7 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-48.000000, -1046.000000)" fill-rule="nonzero" fill="#585858">
-            <g id="Extras" transform="translate(48.000000, 1046.000000)">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-48.000000, -1046.000000)" fill-rule="nonzero" fill="#000000">
+            <g transform="translate(48.000000, 1046.000000)">
                 <g id="heart">
                     <path d="M12.0174466,21 L20.9041801,11.3556763 C22.6291961,9.13778099 22.2795957,5.90145416 20.1233257,4.12713796 C17.9670557,2.35282175 14.8206518,2.71241362 13.0956358,4.93030888 L12.0174465,6.5 L10.9043642,4.93030888 C9.17934824,2.71241362 6.0329443,2.35282175 3.87667432,4.12713796 C1.72040435,5.90145416 1.37080391,9.13778099 3.09581989,11.3556763 L12.0174466,21 Z"></path>
                 </g>
diff --git a/client/src/assets/images/video/like-white.svg b/client/src/assets/images/video/like-white.svg
deleted file mode 100644 (file)
index 88e5f6a..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>thumbs-up</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#ffffff" stroke-width="2">
-            <g id="256" transform="translate(708.000000, 643.000000)">
-                <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path>
-                <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path>
-            </g>
-        </g>
-    </g>
-</svg>
similarity index 61%
rename from client/src/assets/images/video/like-grey.svg
rename to client/src/assets/images/video/like.html
index 5ef6c7b318ca0b2b9963f257d977cea9c68644f8..d0e71763b4579380dc3512fe6e206f558d3cc1e6 100644 (file)
@@ -1,11 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>thumbs-up</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-708.000000, -643.000000)" stroke="#585858" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-708.000000, -643.000000)" stroke="#000000" stroke-width="2">
             <g id="256" transform="translate(708.000000, 643.000000)">
                 <path d="M6,14 C6,16.5 6.5,19 8,19 L16.9938335,19 C17.5495239,19 18.1819788,18.5956028 18.4072817,18.0949295 L20.8562951,12.6526776 C21.7640882,10.6353595 20.7154925,9 18.5092545,9 L15.5,9 C15.5,9 18.5,3 15,3 C12.5,3 11.5,9 8,9 C6.5,9 6,11.5 6,14 Z" id="Path-188" stroke-linejoin="round"></path>
                 <path d="M4,8.5 C4,8.5 3,11 3,14 C3,17 4,19.5 4,19.5" id="Path-189"></path>
similarity index 76%
rename from client/src/assets/images/video/more.svg
rename to client/src/assets/images/video/more.html
index dea392136b87bef6896f70b5daaa9c9837d39a50..39dcad10e973221c20a11a6ce5915a9f6730030d 100644 (file)
@@ -1,8 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Artboard-4" transform="translate(-444.000000, -115.000000)" fill="#585858">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g transform="translate(-444.000000, -115.000000)" fill="#000000">
             <g id="10" transform="translate(444.000000, 115.000000)">
                 <path d="M10,12 C10,10.8954305 10.8877296,10 12,10 C13.1045695,10 14,10.8877296 14,12 C14,13.1045695 13.1122704,14 12,14 C10.8954305,14 10,13.1122704 10,12 Z M17,12 C17,10.8954305 17.8877296,10 19,10 C20.1045695,10 21,10.8877296 21,12 C21,13.1045695 20.1122704,14 19,14 C17.8954305,14 17,13.1122704 17,12 Z M3,12 C3,10.8954305 3.88772964,10 5,10 C6.1045695,10 7,10.8877296 7,12 C7,13.1045695 6.11227036,14 5,14 C3.8954305,14 3,13.1122704 3,12 Z" id="Combined-Shape"></path>
             </g>
similarity index 62%
rename from client/src/assets/images/video/share.svg
rename to client/src/assets/images/video/share.html
index da0f43e8182cda97ac50853853070a79f5741524..7759b37afee9b6485b7bdc00d1960d2179146803 100644 (file)
@@ -1,11 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>share</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-312.000000, -203.000000)" stroke="#585858" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-312.000000, -203.000000)" stroke="#000000" stroke-width="2">
             <g id="47" transform="translate(312.000000, 203.000000)">
                 <path d="M20,15 L20,18.0026083 C20,19.1057373 19.1073772,20 18.0049107,20 L5.99508929,20 C4.8932319,20 4,19.1073772 4,18.0049107 L4,5.99508929 C4,4.8932319 4.89585781,4 5.9973917,4 L9,4" id="Rectangle-460"></path>
                 <polyline id="Path-93" stroke-linejoin="round" points="13 4 20.0207973 4 20.0207973 11.0191059"></polyline>
similarity index 69%
rename from client/src/assets/images/header/upload-white.svg
rename to client/src/assets/images/video/upload.html
index 2b07caf7663316bf81cca73c90737a99745449ec..3bc0d3a8a310b21b9c5b76a31c6ca47c798a05eb 100644 (file)
@@ -1,11 +1,6 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>cloud-upload</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#fff" stroke-width="2">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
+        <g transform="translate(-312.000000, -775.000000)" stroke="#000000" stroke-width="2">
             <g id="307" transform="translate(312.000000, 775.000000)">
                 <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path>
                 <path d="M12,13 L12,21" id="Path-58"></path>
diff --git a/client/src/assets/images/video/upload.svg b/client/src/assets/images/video/upload.svg
deleted file mode 100644 (file)
index c5b7cb4..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 43.2 (39069) - http://www.bohemiancoding.com/sketch -->
-    <title>cloud-upload</title>
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round">
-        <g id="Artboard-4" transform="translate(-312.000000, -775.000000)" stroke="#C6C6C6" stroke-width="2">
-            <g id="307" transform="translate(312.000000, 775.000000)">
-                <path d="M8,18 L5,18 L5,18 C2.790861,18 1,16.209139 1,14 C1,11.790861 2.790861,10 5,10 C5.35840468,10 5.70579988,10.0471371 6.03632437,10.1355501 C6.01233106,9.92702603 6,9.71495305 6,9.5 C6,6.46243388 8.46243388,4 11.5,4 C14.0673313,4 16.2238156,5.7590449 16.8299648,8.1376465 C17.2052921,8.04765874 17.5970804,8 18,8 C20.7614237,8 23,10.2385763 23,13 C23,15.7614237 20.7614237,18 18,18 L16,18" id="Combined-Shape" stroke-linejoin="round"></path>
-                <path d="M12,13 L12,21" id="Path-58"></path>
-                <polyline id="Path-59" stroke-linejoin="round" transform="translate(12.000000, 12.500000) scale(1, -1) translate(-12.000000, -12.500000) " points="15 11 12 14 9 11"></polyline>
-            </g>
-        </g>
-    </g>
-</svg>
index 7e381357041a345e1e294ab3159ddb60dd7e598c..059fca3083866b19f1b7ed9095e6b2aaca3d9d26 100644 (file)
@@ -60,6 +60,14 @@ function getAverageBandwidthInStore () {
   return undefined
 }
 
+function saveLastSubtitle (language: string) {
+  return setLocalStorage('last-subtitle', language)
+}
+
+function getStoredLastSubtitle () {
+  return getLocalStorage('last-subtitle')
+}
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -71,7 +79,9 @@ export {
   saveMuteInStore,
   saveTheaterInStore,
   saveAverageBandwidth,
-  getAverageBandwidthInStore
+  getAverageBandwidthInStore,
+  saveLastSubtitle,
+  getStoredLastSubtitle
 }
 
 // ---------------------------------------------------------------------------
index aaa1170b6287d9c3df1abac333df74c042529d70..2de6d7fefe4d75f4d6e2635105f23c913cc7f01d 100644 (file)
@@ -26,23 +26,24 @@ videojsUntyped.getComponent('CaptionsButton').prototype.controlText_ = 'Subtitle
 videojsUntyped.getComponent('CaptionsButton').prototype.label_ = ' '
 
 function getVideojsOptions (options: {
-  autoplay: boolean,
-  playerElement: HTMLVideoElement,
-  videoViewUrl: string,
-  videoDuration: number,
-  videoFiles: VideoFile[],
-  enableHotkeys: boolean,
-  inactivityTimeout: number,
-  peertubeLink: boolean,
-  poster: string,
+  autoplay: boolean
+  playerElement: HTMLVideoElement
+  videoViewUrl: string
+  videoDuration: number
+  videoFiles: VideoFile[]
+  enableHotkeys: boolean
+  inactivityTimeout: number
+  peertubeLink: boolean
+  poster: string
   startTime: number | string
-  theaterMode: boolean,
-  videoCaptions: VideoJSCaption[],
+  theaterMode: boolean
+  videoCaptions: VideoJSCaption[]
 
-  language?: string,
-  controls?: boolean,
-  muted?: boolean,
+  language?: string
+  controls?: boolean
+  muted?: boolean
   loop?: boolean
+  subtitle?: string
 
   userWatching?: UserWatching
 }) {
@@ -50,8 +51,10 @@ function getVideojsOptions (options: {
     // We don't use text track settings for now
     textTrackSettings: false,
     controls: options.controls !== undefined ? options.controls : true,
-    muted: options.controls !== undefined ? options.muted : false,
     loop: options.loop !== undefined ? options.loop : false,
+
+    muted: options.muted !== undefined ? options.muted : undefined, // Undefined so the player knows it has to check the local storage
+
     poster: options.poster,
     autoplay: false,
     inactivityTimeout: options.inactivityTimeout,
@@ -65,7 +68,8 @@ function getVideojsOptions (options: {
         videoViewUrl: options.videoViewUrl,
         videoDuration: options.videoDuration,
         startTime: options.startTime,
-        userWatching: options.userWatching
+        userWatching: options.userWatching,
+        subtitle: options.subtitle
       }
     },
     controlBar: {
@@ -250,6 +254,10 @@ function loadLocaleInVideoJS (serverUrl: string, videojs: any, locale: string) {
         loadLocaleInVideoJS.cache[path] = json
         return json
       })
+      .catch(err => {
+        console.error('Cannot get player translations', err)
+        return undefined
+      })
   }
 
   const completeLocale = getCompleteLocale(locale)
@@ -266,6 +274,10 @@ function getServerTranslations (serverUrl: string, locale: string) {
 
   return fetch(path + '/server.json')
     .then(res => res.json())
+    .catch(err => {
+      console.error('Cannot get server translations', err)
+      return undefined
+    })
 }
 
 // ############################################################################
index 4fd5a9be22d5fa04a4e98e814849cb7e0468029c..e9fb90c61e2659265f98131a6c9d9a410aa35760 100644 (file)
@@ -11,10 +11,12 @@ import { isMobile, timeToInt, videoFileMaxByResolution, videoFileMinByResolution
 import { PeertubeChunkStore } from './peertube-chunk-store'
 import {
   getAverageBandwidthInStore,
+  getStoredLastSubtitle,
   getStoredMute,
   getStoredVolume,
   getStoredWebTorrentEnabled,
   saveAverageBandwidth,
+  saveLastSubtitle,
   saveMuteInStore,
   saveVolumeInStore
 } from './peertube-player-local-storage'
@@ -67,10 +69,11 @@ class PeerTubePlugin extends Plugin {
   private currentVideoFile: VideoFile
   private torrent: WebTorrent.Torrent
   private videoCaptions: VideoJSCaption[]
+  private defaultSubtitle: string
 
   private renderer: any
   private fakeRenderer: any
-  private destoyingFakeRenderer = false
+  private destroyingFakeRenderer = false
 
   private autoResolution = true
   private forbidAutoResolution = false
@@ -106,11 +109,34 @@ class PeerTubePlugin extends Plugin {
     if (this.autoplay === true) this.player.addClass('vjs-has-autoplay')
 
     this.player.ready(() => {
+      const playerOptions = this.player.options_
+
       const volume = getStoredVolume()
       if (volume !== undefined) this.player.volume(volume)
-      const muted = getStoredMute()
+
+      const muted = playerOptions.muted !== undefined ? playerOptions.muted : getStoredMute()
       if (muted !== undefined) this.player.muted(muted)
 
+      this.defaultSubtitle = options.subtitle || getStoredLastSubtitle()
+
+      this.player.on('volumechange', () => {
+        saveVolumeInStore(this.player.volume())
+        saveMuteInStore(this.player.muted())
+      })
+
+      this.player.textTracks().on('change', () => {
+        const showing = this.player.textTracks().tracks_.find((t: { kind: string, mode: string }) => {
+          return t.kind === 'captions' && t.mode === 'showing'
+        })
+
+        if (!showing) {
+          saveLastSubtitle('off')
+          return
+        }
+
+        saveLastSubtitle(showing.language)
+      })
+
       this.player.duration(options.videoDuration)
 
       this.initializePlayer()
@@ -124,11 +150,6 @@ class PeerTubePlugin extends Plugin {
         this.runAutoQualitySchedulerTimer = setTimeout(() => this.runAutoQualityScheduler(), this.CONSTANTS.AUTO_QUALITY_SCHEDULER)
       })
     })
-
-    this.player.on('volumechange', () => {
-      saveVolumeInStore(this.player.volume())
-      saveMuteInStore(this.player.muted())
-    })
   }
 
   dispose () {
@@ -599,6 +620,9 @@ class PeerTubePlugin extends Plugin {
     this.player.src = this.savePlayerSrcFunction
     this.player.src(httpUrl)
 
+    // We changed the source, so reinit captions
+    this.initCaptions()
+
     return this.tryToPlay(err => {
       if (err && done) return done(err)
 
@@ -657,14 +681,14 @@ class PeerTubePlugin extends Plugin {
   }
 
   private renderFileInFakeElement (file: WebTorrent.TorrentFile, delay: number) {
-    this.destoyingFakeRenderer = false
+    this.destroyingFakeRenderer = false
 
     const fakeVideoElem = document.createElement('video')
     renderVideo(file, fakeVideoElem, { autoplay: false, controls: false }, (err, renderer) => {
       this.fakeRenderer = renderer
 
       // The renderer returns an error when we destroy it, so skip them
-      if (this.destoyingFakeRenderer === false && err) {
+      if (this.destroyingFakeRenderer === false && err) {
         console.error('Cannot render new torrent in fake video element.', err)
       }
 
@@ -675,7 +699,7 @@ class PeerTubePlugin extends Plugin {
 
   private destroyFakeRenderer () {
     if (this.fakeRenderer) {
-      this.destoyingFakeRenderer = true
+      this.destroyingFakeRenderer = true
 
       if (this.fakeRenderer.destroy) {
         try {
@@ -695,9 +719,12 @@ class PeerTubePlugin extends Plugin {
         label: caption.label,
         language: caption.language,
         id: caption.language,
-        src: caption.src
+        src: caption.src,
+        default: this.defaultSubtitle === caption.language
       }, false)
     }
+
+    this.player.trigger('captionsChanged')
   }
 
   // Thanks: https://github.com/videojs/video.js/issues/4460#issuecomment-312861657
index d127230fa73e850e3624a175dd4708606605940d..634c7fdc9df0e38484c31cebe5ed2b0b1b721c49 100644 (file)
@@ -39,6 +39,7 @@ type PeertubePluginOptions = {
   autoplay: boolean,
   videoCaptions: VideoJSCaption[]
 
+  subtitle?: string
   userWatching?: UserWatching
 }
 
index 698f4627a95525ce28eb987422064f652aa001cf..2a3460ae542b713be5403d2208e3a773bf92ec53 100644 (file)
@@ -48,6 +48,19 @@ class SettingsMenuItem extends MenuItem {
         // Update on rate change
         player.on('ratechange', this.submenuClickHandler)
 
+        if (subMenuName === 'CaptionsButton') {
+          // Hack to regenerate captions on HTTP fallback
+          player.on('captionsChanged', () => {
+            setTimeout(() => {
+              this.settingsSubMenuEl_.innerHTML = ''
+              this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_)
+              this.update()
+              this.bindClickEvents()
+
+            }, 0)
+          })
+        }
+
         this.reset()
       }, 0)
     })
index c872874820b2a316bd677f290655daf1031b3183..8b9f34b994033f628b41245287a22836ca648b35 100644 (file)
@@ -39,6 +39,7 @@ function buildVideoLink (time?: number, url?: string) {
 }
 
 function timeToInt (time: number | string) {
+  if (!time) return 0
   if (typeof time === 'number') return time
 
   const reg = /^((\d+)h)?((\d+)m)?((\d+)s?)?$/
index 5bb6f4b3446fecce885eb6187219f5e7e7aa9ea2..1ea4835548ed0c41eaeb759d9c871f19298ef0bf 100644 (file)
@@ -2,6 +2,13 @@
 // `ng build --env=prod` then `environment.prod.ts` will be used instead.
 // The list of which env maps to which file can be found in `.angular-cli.json`.
 
+// Reflect.metadata polyfill is only needed in the JIT/dev mode.
+//
+// In order to load these polyfills early enough (before app code), polyfill.ts imports this file to
+// to change the order in the final bundle.
+import 'core-js/es6/reflect'
+import 'core-js/es7/reflect'
+
 export const environment = {
   production: false,
   hmr: false,
index 7ebdd8e0760f826d5b032c055461d53cac6e83f1..3aa17882554661ea93ee0239fc6bad6af69a4a88 100644 (file)
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/login/login.component.html</context>
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">77</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.html</context>
           <context context-type="sourcefile">app/shared/forms/reactive-file.component.html</context>
           <context context-type="linenumber">11</context>
         </context-group>
+      </trans-unit><trans-unit id="f3e63578c50546530daf6050d2ba6f8226040f2c" datatype="html">
+        <source>You don&apos;t have notifications.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit><trans-unit id="f79d1d9ecaab3deb3d44e23017f8283a04d2a0f3" datatype="html">
+        <source>
+        &lt;x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/&gt; published a &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;new video&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt;
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit><trans-unit id="04f2cb4c88c17d5f3e5ce969479b4eba9db114cb" datatype="html">
+        <source>
+        Your video &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; has been unblacklisted
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit><trans-unit id="65514a0efdae3b173130166416700ddeb369f37f" datatype="html">
+        <source>
+        Your video &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; has been blacklisted
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit><trans-unit id="4ea67498da562ab450950a69f4331b8c4ddfd431" datatype="html">
+        <source>
+        &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;A new video abuse&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; has been created on video &lt;x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt;
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit><trans-unit id="23b7d6f08c5c3b8722ecd627c3d54f4950923156" datatype="html">
+        <source>
+        &lt;x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/&gt; commented your video &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt;
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit><trans-unit id="2d0ee93317d4daa301eee7fec775c21c2f7b5a4b" datatype="html">
+        <source>
+        Your video &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; has been published
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit><trans-unit id="371391b88724e5ee455582f07eb97728e371f24a" datatype="html">
+        <source>
+        &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;Your video import&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; &lt;x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/&gt; succeeded
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit><trans-unit id="56e72a0a79d53e9ff8d5f92528664bcb2cf1363a" datatype="html">
+        <source>
+        &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;Your video import&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; &lt;x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/&gt; failed
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">35</context>
+        </context-group>
+      </trans-unit><trans-unit id="d7f123ae20ca6bfb5ac0f897b90423fdc52d8e78" datatype="html">
+        <source>
+        User &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION" equiv-text="{{ notification.account.name }}"/&gt; registered&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; on your instance
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit><trans-unit id="9a05dc5206104085b2b6654fb9137291194a72ef" datatype="html">
+        <source>
+        &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;&lt;x id="INTERPOLATION" equiv-text="{{ notification.actorFollow.follower.displayName }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt; is following
+
+        &lt;x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/&gt;
+          your channel &lt;x id="INTERPOLATION_1" equiv-text="{{ notification.actorFollow.following.displayName }}"/&gt;
+        &lt;x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/&gt;
+        &lt;x id="START_TAG_NG-CONTAINER_1" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/&gt;your account&lt;x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/&gt;
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit><trans-unit id="98b174525a2c9b4de0a510fb6eae7bdf285c0c7f" datatype="html">
+        <source>
+        &lt;x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/&gt; mentioned you on &lt;x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/&gt;video &lt;x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/&gt;&lt;x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/&gt;
+      </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">52</context>
+        </context-group>
+      </trans-unit><trans-unit id="473117e02024f603dc2dbd24a0bf81f8722cf8dc" datatype="html">
+        <source>
+      &lt;x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/&gt;&lt;x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/&gt;
+    </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/users/user-notifications.component.html</context>
+          <context context-type="linenumber">57</context>
+        </context-group>
       </trans-unit><trans-unit id="4b3963c6d0863118fe9e9e33447d12be3c2db081" datatype="html">
         <source>Unlisted</source>
         <context-group purpose="location">
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">162</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-watch/video-watch.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-watch/modal/video-report.component.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">16</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
           <context context-type="sourcefile">app/shared/moderation/user-ban-modal.component.html</context>
           <context context-type="linenumber">25</context>
         </context-group>
+      </trans-unit><trans-unit id="c078d4901a5fac169665947cc7a6108b94dd80c7" datatype="html">
+        <source>&lt;x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/&gt;</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/shared/menu/top-menu-dropdown.component.html</context>
+          <context context-type="linenumber">11</context>
+        </context-group>
       </trans-unit><trans-unit id="12910217fdcdbca64bee06f511639b653d5428ea" datatype="html">
         <source>
     Login
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-settings.component.html</context>
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit><trans-unit id="b87e81682959464211443afc3e23c506865d2eda" datatype="html">
         <source>I forgot my password</source>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit><trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681" datatype="html">
         <source>Forgot your password</source>
           <context context-type="sourcefile">app/login/login.component.html</context>
           <context context-type="linenumber">57</context>
         </context-group>
+      </trans-unit><trans-unit id="f876804a6725f7b950c8e4c56ca596206856e6a2" datatype="html">
+        <source>
+      We are sorry, you cannot recover you password because your instance administrator did not configure the PeerTube email system.
+    </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/login/login.component.html</context>
+          <context context-type="linenumber">63</context>
+        </context-group>
       </trans-unit><trans-unit id="244aae9346da82b0922506c2d2581373a15641cc" datatype="html">
         <source>Email</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/login/login.component.html</context>
-          <context context-type="linenumber">63</context>
+          <context context-type="linenumber">68</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/signup/signup.component.html</context>
           <context context-type="sourcefile">app/+admin/users/user-list/user-list.component.html</context>
           <context context-type="linenumber">41</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html</context>
           <context context-type="linenumber">8</context>
         <source>Email address</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/login/login.component.html</context>
-          <context context-type="linenumber">65</context>
+          <context context-type="linenumber">70</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.html</context>
         <source>Send me an email to reset my password</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/login/login.component.html</context>
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit><trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa" datatype="html">
         <source>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">26</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit><trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1" datatype="html">
         <source>Features found on this instance</source>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
+        </context-group>
+      </trans-unit><trans-unit id="1c98d728375e7bd5b166d1aeb29485ef8b5d6e28" datatype="html">
+        <source>
+    Help to translate PeerTube!
+  </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/menu/language-chooser.component.html</context>
+          <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit><trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6" datatype="html">
         <source>
             </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit><trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb" datatype="html">
         <source>
             </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit><trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10" datatype="html">
         <source>
             </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit><trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1" datatype="html">
         <source>
             </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit><trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87" datatype="html">
         <source>Create an account</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit><trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238" datatype="html">
         <source>Videos</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">43</context>
+          <context context-type="linenumber">41</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+accounts/accounts.component.html</context>
         <source>Subscriptions</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit><trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5" datatype="html">
         <source>Overview</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit><trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807" datatype="html">
         <source>Trending</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit><trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1" datatype="html">
         <source>Recently added</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit><trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d" datatype="html">
         <source>Local</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit><trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f" datatype="html">
         <source>More</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit><trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919" datatype="html">
         <source>Administration</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit><trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a" datatype="html">
         <source>About</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">81</context>
+          <context context-type="linenumber">79</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+accounts/accounts.component.html</context>
         <source>Show keyboard shortcuts</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit><trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768" datatype="html">
         <source>Toggle dark interface</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/menu/menu.component.html</context>
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit><trans-unit id="2dc8a0a3763cd5c456c84630fc335398c9b86771" datatype="html">
+        <source>View your notifications</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/menu/avatar-notification.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit><trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab" datatype="html">
+        <source>Notifications</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/menu/avatar-notification.component.html</context>
+          <context context-type="linenumber">12</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-settings.component.html</context>
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit><trans-unit id="341e026e3f317aa3164916cc63a059c961a78b81" datatype="html">
+        <source>Update your notification preferences</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/menu/avatar-notification.component.html</context>
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit><trans-unit id="3d1b5c9cd76948c04fdb7bb3fe51b6c1242c1bd5" datatype="html">
+        <source>See all your notifications</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/menu/avatar-notification.component.html</context>
+          <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit><trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599" datatype="html">
         <source>Search...</source>
         <source>Display unlisted and private videos</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit><trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e" datatype="html">
         <source>No results.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/video-list/video-overview.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-videos/my-account-videos.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/shared/video/abstract-video-list.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit><trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6" datatype="html">
         <source>
           <context context-type="sourcefile">app/+about/about.component.html</context>
           <context context-type="linenumber">7</context>
         </context-group>
-      </trans-unit><trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2" datatype="html">
+      </trans-unit><trans-unit id="5fea66be16da46ed7a0775e9a62b7b5e94b77473" datatype="html">
+        <source>Contact &lt;x id="INTERPOLATION" equiv-text="{{ instanceName }}"/&gt; administrator</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/contact-admin-modal.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit><trans-unit id="533b2b9a76ee1335cb44c01f0bfd50d43e9400b0" datatype="html">
+        <source>Your name</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/contact-admin-modal.component.html</context>
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit><trans-unit id="0b892c7805a1c5afc0b7c21c3449760860fe7f3d" datatype="html">
+        <source>Your email</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/contact-admin-modal.component.html</context>
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit><trans-unit id="d2815c9b510b8172d8cac4008b9709df69d636df" datatype="html">
+        <source>Your message</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/contact-admin-modal.component.html</context>
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit><trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb" datatype="html">
         <source>
-  About &lt;x id="INTERPOLATION" equiv-text="{{ instanceName }}"/&gt; instance
-</source>
+          Cancel
+        </source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/contact-admin-modal.component.html</context>
+          <context context-type="linenumber">38</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/videos/+video-watch/modal/video-report.component.html</context>
+          <context context-type="linenumber">24</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit><trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd" datatype="html">
+        <source>Submit</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/contact-admin-modal.component.html</context>
+          <context context-type="linenumber">43</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html</context>
+          <context context-type="linenumber">25</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html</context>
+          <context context-type="linenumber">28</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/videos/+video-watch/modal/video-report.component.html</context>
+          <context context-type="linenumber">29</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit><trans-unit id="89e55a86cb300f06139ff398c9c8bb7376f78b07" datatype="html">
+        <source>About &lt;x id="INTERPOLATION" equiv-text="{{ instanceName }}"/&gt; instance</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit><trans-unit id="3c1aff50472b313c70a72ee02c081b8eeb1c616c" datatype="html">
+        <source>Contact administrator</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
+          <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit><trans-unit id="eec715de352a6b114713b30b640d319fa78207a0" datatype="html">
         <source>Description</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">14</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+accounts/account-about/account-about.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">30</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-video-channels/my-account-video-channel-edit.component.html</context>
         <source>Terms</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">16</context>
+          <context context-type="linenumber">20</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit><trans-unit id="9c6e6db693ab265457c6578df179c65694141d27" datatype="html">
         <source>User registration is allowed and</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">25</context>
+          <context context-type="linenumber">29</context>
         </context-group>
-      </trans-unit><trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e" datatype="html">
+      </trans-unit><trans-unit id="7a0a7b5a5bc9ee7b7e415f87ecc404145fb51dff" datatype="html">
         <source>
-      this instance provides a baseline quota of &lt;x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/&gt; space for the videos of its users.
-    </source>
+          this instance provides a baseline quota of &lt;x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/&gt; space for the videos of its users.
+        </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">31</context>
         </context-group>
-      </trans-unit><trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae" datatype="html">
+      </trans-unit><trans-unit id="7bee5dd41c0007820f150ee33b8257dc1aac281b" datatype="html">
         <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
+          this instance provides unlimited space for the videos of its users.
+        </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">31</context>
+          <context context-type="linenumber">35</context>
         </context-group>
-      </trans-unit><trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1" datatype="html">
+      </trans-unit><trans-unit id="b6e2ede24a2ee0f6ba2f1924ede2ae408ffc2574" datatype="html">
         <source>
-    User registration is currently not allowed.
-  </source>
+        User registration is currently not allowed.
+      </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+about/about-instance/about-instance.component.html</context>
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit><trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc" datatype="html">
         <source>
         <source>Name</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">12</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/follows/followers-list/followers-list.component.html</context>
         <source>Short description</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit><trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003" datatype="html">
         <source>Default client route</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit><trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d" datatype="html">
         <source>Videos Overview</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit><trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948" datatype="html">
         <source>Videos Trending</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit><trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883" datatype="html">
         <source>Videos Recently Added</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit><trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f" datatype="html">
         <source>Local videos</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit><trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9" datatype="html">
         <source>Policy on videos containing sensitive content</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit><trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df" datatype="html">
         <source>With &lt;strong&gt;Do not list&lt;/strong&gt; or &lt;strong&gt;Blur thumbnails&lt;/strong&gt;, a confirmation will be requested to watch the video.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">73</context>
+          <context context-type="linenumber">64</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html</context>
         <source>Do not list</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">78</context>
+          <context context-type="linenumber">69</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html</context>
         <source>Blur thumbnails</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">79</context>
+          <context context-type="linenumber">70</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html</context>
         <source>Display</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">80</context>
+          <context context-type="linenumber">71</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html</context>
         <source>Signup enabled</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit><trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7" datatype="html">
         <source>Signup requires email verification</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit><trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402" datatype="html">
         <source>Signup limit</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit><trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be" datatype="html">
+        <source>Users</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
           <context context-type="linenumber">105</context>
         </context-group>
+      </trans-unit><trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09" datatype="html">
+        <source>User default video quota</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit><trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe" datatype="html">
+        <source>User default daily upload limit</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
+          <context context-type="linenumber">121</context>
+        </context-group>
       </trans-unit><trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36" datatype="html">
         <source>Import</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">115</context>
+          <context context-type="linenumber">133</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/video-add-components/video-import-url.component.html</context>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit><trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e" datatype="html">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit><trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011" datatype="html">
         <source>Administrator</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit><trans-unit id="55a0f51e38679d3141841e8333da5779d349c587" datatype="html">
         <source>Admin email</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit><trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be" datatype="html">
-        <source>Users</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit><trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09" datatype="html">
-        <source>User default video quota</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">158</context>
         </context-group>
-      </trans-unit><trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe" datatype="html">
-        <source>User default daily upload limit</source>
+      </trans-unit><trans-unit id="f9bda6652199995a4bd4424f2e35b748eb0bda8a" datatype="html">
+        <source>Enable contact form</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">169</context>
         </context-group>
       </trans-unit><trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5" datatype="html">
         <source>Basic configuration</source>
         <source>Your Twitter username</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit><trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c" datatype="html">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit><trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605" datatype="html">
         <source>Instance whitelisted by Twitter</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
-      </trans-unit><trans-unit id="8b0ee765cc3fea9baef14bfb9d5288dfcbe386b6" datatype="html">
+      </trans-unit><trans-unit id="f1276a50033dfc7a71290086d0f57d89e3438e6b" datatype="html">
         <source>If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.&lt;br /&gt;
-    If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.&lt;br /&gt;&lt;br /&gt;
-    Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on &lt;a target=&apos;_blank&apos; rel=&apos;noopener noreferrer&apos; href=&apos;https://cards-dev.twitter.com/validator&apos;&gt;https://cards-dev.twitter.com/validator&lt;/a&gt; to see if you instance is whitelisted.</source>
+        If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.&lt;br /&gt;&lt;br /&gt;
+        Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on &lt;a target=&apos;_blank&apos; rel=&apos;noopener noreferrer&apos; href=&apos;https://cards-dev.twitter.com/validator&apos;&gt;https://cards-dev.twitter.com/validator&lt;/a&gt; to see if you instance is whitelisted.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">199</context>
+          <context context-type="linenumber">200</context>
         </context-group>
       </trans-unit><trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5" datatype="html">
         <source>Services</source>
         <source>Transcoding</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit><trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9" datatype="html">
         <source>Transcoding enabled</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit><trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f" datatype="html">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
+        </context-group>
+      </trans-unit><trans-unit id="0050a55afb9c565df1f9b3f750c2d4adb697698f" datatype="html">
+        <source>Allow additional extensions</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
+          <context context-type="linenumber">231</context>
+        </context-group>
+      </trans-unit><trans-unit id="9b82c3a407ee5a98c92483fbd987be8db8384c33" datatype="html">
+        <source>Allow your users to upload .mkv, .mov, .avi, .flv videos</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
+          <context context-type="linenumber">232</context>
         </context-group>
       </trans-unit><trans-unit id="a33feadefbb776217c2db96100736314f8b765c2" datatype="html">
         <source>Transcoding threads</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit><trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500" datatype="html">
         <source>Resolution &lt;x id="INTERPOLATION" equiv-text="{{resolution}}"/&gt; enabled</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit><trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5" datatype="html">
         <source>
         </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit><trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0" datatype="html">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit><trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7" datatype="html">
         <source>Previews cache size</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit><trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607" datatype="html">
         <source>Video captions cache size</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit><trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c" datatype="html">
         <source>Customizations</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit><trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c" datatype="html">
         <source>JavaScript</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit><trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c" datatype="html">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log(&apos;my instance is amazing&apos;);&lt;/pre&gt;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
-      </trans-unit><trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5" datatype="html">
+      </trans-unit><trans-unit id="d7caa08cd9b3119881bbaec3f5a3c5707f573dde" datatype="html">
         <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body &lt;x id="INTERPOLATION" equiv-text="{{ &apos;{&apos; }}"/&gt;
-        background-color: red;
-      &lt;x id="INTERPOLATION_1" equiv-text="{{ &apos;}&apos; }}"/&gt;
-                &lt;/pre&gt;
+                    Write directly CSS code. Example:&lt;br /&gt;
+                    &lt;pre&gt;
+          body &lt;x id="INTERPOLATION" equiv-text="{{ &apos;{&apos; }}"/&gt;
+            background-color: red;
+          &lt;x id="INTERPOLATION_1" equiv-text="{{ &apos;}&apos; }}"/&gt;
+                    &lt;/pre&gt;
 
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email &lt;x id="INTERPOLATION" equiv-text="{{ &apos;{&apos; }}"/&gt;
-        color: red;
-      &lt;x id="INTERPOLATION_1" equiv-text="{{ &apos;}&apos; }}"/&gt;
-                &lt;/pre&gt;
-              </source>
+                    Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
+                    &lt;pre&gt;
+          #custom-css .logged-in-email &lt;x id="INTERPOLATION" equiv-text="{{ &apos;{&apos; }}"/&gt;
+            color: red;
+          &lt;x id="INTERPOLATION_1" equiv-text="{{ &apos;}&apos; }}"/&gt;
+                    &lt;/pre&gt;
+                  </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">297</context>
+          <context context-type="linenumber">311</context>
         </context-group>
       </trans-unit><trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab" datatype="html">
         <source>Advanced configuration</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit><trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8" datatype="html">
         <source>Update configuration</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit><trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca" datatype="html">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/config/edit-custom-config/edit-custom-config.component.html</context>
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit><trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c" datatype="html">
         <source>
         <source>User&apos;s email must be verified to login</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/users/user-list/user-list.component.html</context>
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">72</context>
         </context-group>
       </trans-unit><trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b" datatype="html">
         <source>User&apos;s email is verified / User can login without email verification</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/users/user-list/user-list.component.html</context>
-          <context context-type="linenumber">74</context>
+          <context context-type="linenumber">76</context>
         </context-group>
       </trans-unit><trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee" datatype="html">
         <source>Ban reason:</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/users/user-list/user-list.component.html</context>
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit><trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f" datatype="html">
         <source>Moderation comment</source>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html</context>
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">25</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-ownership/my-account-ownership.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html</context>
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit><trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2" datatype="html">
         <source>Reason:</source>
           <context context-type="sourcefile">app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html</context>
           <context context-type="linenumber">9</context>
         </context-group>
+      </trans-unit><trans-unit id="b748c96a1ee98d2fa9a645fb71838f5d4938855b" datatype="html">
+        <source>Unfederated</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html</context>
+          <context context-type="linenumber">10</context>
+        </context-group>
       </trans-unit><trans-unit id="a7f42da3bb4eea0b71b0a20a2aff6612a82cab99" datatype="html">
         <source>Date &lt;x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/&gt;&lt;x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/&gt;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html</context>
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit><trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f" datatype="html">
         <source>Blacklist reason:</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.html</context>
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit><trans-unit id="90868353e7e6f5994109ee1011131cefa992116c" datatype="html">
         <source>Moderation</source>
           <context context-type="sourcefile">app/+admin/moderation/moderation.component.html</context>
           <context context-type="linenumber">9</context>
         </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">29</context>
-        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-blocklist/my-account-blocklist.component.html</context>
           <context context-type="linenumber">2</context>
           <context context-type="sourcefile">app/+my-account/my-account-blocklist/my-account-server-blocklist.component.html</context>
           <context context-type="linenumber">23</context>
         </context-group>
-      </trans-unit><trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6" datatype="html">
-        <source>My settings</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit><trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432" datatype="html">
-        <source>My library</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit><trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f" datatype="html">
-        <source>My channels</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit><trans-unit id="d02888c485d3aeab6de628508f4a00312a722894" datatype="html">
-        <source>My videos</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit><trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9" datatype="html">
-        <source>My subscriptions</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit><trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed" datatype="html">
-        <source>My imports</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit><trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029" datatype="html">
-        <source>Misc</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit><trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e" datatype="html">
-        <source>Muted instances</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">31</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account-blocklist/my-account-server-blocklist.component.html</context>
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit><trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7" datatype="html">
-        <source>Ownership changes</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account.component.html</context>
-          <context context-type="linenumber">33</context>
-        </context-group>
       </trans-unit><trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48" datatype="html">
         <source>Video quota:</source>
         <context-group purpose="location">
         <source>Profile</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-settings.component.html</context>
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit><trans-unit id="b5398623f87ee72ed23f5023918db1707771e925" datatype="html">
         <source>Video settings</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-settings.component.html</context>
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit><trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735" datatype="html">
         <source>Danger zone</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-settings.component.html</context>
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit><trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf" datatype="html">
         <source>Change ownership</source>
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-caption-add-modal.component.html</context>
           <context context-type="linenumber">35</context>
         </context-group>
-      </trans-unit><trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd" datatype="html">
-        <source>Submit</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.html</context>
-          <context context-type="linenumber">25</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.html</context>
-          <context context-type="linenumber">28</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/videos/+video-watch/modal/video-report.component.html</context>
-          <context context-type="linenumber">24</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
-          <context context-type="linenumber">24</context>
-        </context-group>
       </trans-unit><trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79" datatype="html">
         <source>&lt;x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/&gt; - &lt;x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/&gt; views</source>
         <context-group purpose="location">
@@ -2612,6 +2783,48 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="sourcefile">app/+my-account/my-account-ownership/my-account-ownership.component.html</context>
           <context context-type="linenumber">47</context>
         </context-group>
+      </trans-unit><trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e" datatype="html">
+        <source>Muted instances</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-blocklist/my-account-server-blocklist.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit><trans-unit id="e8e93a7ae9a47c035bf5170b105c418b1deae530" datatype="html">
+        <source>History enabled</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-history/my-account-history.component.html</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit><trans-unit id="0f1fd6758625c6a39d796378d362cdcc2b092123" datatype="html">
+        <source>Delete history</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-history/my-account-history.component.html</context>
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit><trans-unit id="6b4dc5732f1f2211833d4b5e76deb5985f3749af" datatype="html">
+        <source>You don&apos;t have videos history yet.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-history/my-account-history.component.html</context>
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit><trans-unit id="6aec8cb024acc333218d72f279caa8ea623bb628" datatype="html">
+        <source>&lt;x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/&gt; views</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-history/my-account-history.component.html</context>
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit><trans-unit id="3a6903ba6b8cf2d828d0c86fd1feb09a27be4105" datatype="html">
+        <source>Notification preferences</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-notifications/my-account-notifications.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit><trans-unit id="1da23f4068fd3796fbcb24d0c42bb62f92c96829" datatype="html">
+        <source>Mark all as read</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-notifications/my-account-notifications.component.html</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
       </trans-unit><trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9" datatype="html">
         <source>Change password</source>
         <context-group purpose="location">
@@ -2700,6 +2913,18 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.html</context>
           <context context-type="linenumber">4</context>
         </context-group>
+      </trans-unit><trans-unit id="dd3b6c367381ddfa8f317b8e9b31c55368c65136" datatype="html">
+        <source>Activities</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html</context>
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit><trans-unit id="847dffd493abbb2a5c71f3313f0eb730dd88a355" datatype="html">
+        <source>Web</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.html</context>
+          <context context-type="linenumber">3</context>
+        </context-group>
       </trans-unit><trans-unit id="e242e3e8608a3c4a944327eb3d5c221dc6e4e3cd" datatype="html">
         <source>
   Sorry, but we couldn&apos;t find the page you were looking for.
@@ -2870,17 +3095,25 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="sourcefile">app/videos/+video-edit/video-add-components/video-upload.component.html</context>
           <context context-type="linenumber">25</context>
         </context-group>
+      </trans-unit><trans-unit id="6357683911e256c566259880de43ea9403de00d3" datatype="html">
+        <source>
+  Congratulations! Your video is now available in your private library.
+</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/videos/+video-edit/video-add-components/video-upload.component.html</context>
+          <context context-type="linenumber">45</context>
+        </context-group>
       </trans-unit><trans-unit id="f7ac2376749c7985f94f0fc89ba75ea624de1215" datatype="html">
         <source>Publish will be available when upload is finished</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/video-add-components/video-upload.component.html</context>
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit><trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3" datatype="html">
         <source>Publish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/video-add-components/video-upload.component.html</context>
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit><trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b" datatype="html">
         <source>Select the torrent to import</source>
@@ -3038,13 +3271,13 @@ When you will upload a video in this channel, the video support field will be au
         <source>Wait transcoding before publishing the video</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit><trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63" datatype="html">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit><trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7" datatype="html">
         <source>Basic info</source>
@@ -3056,43 +3289,43 @@ When you will upload a video in this channel, the video support field will be au
         <source>Add another caption</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit><trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed" datatype="html">
         <source>See the subtitle file</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit><trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee" datatype="html">
         <source>Already uploaded       ✔</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit><trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad" datatype="html">
         <source>Will be created on update</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit><trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9" datatype="html">
         <source>Cancel create</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit><trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393" datatype="html">
         <source>Will be deleted on update</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit><trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c" datatype="html">
         <source>Cancel deletion</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit><trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0" datatype="html">
         <source>
@@ -3100,31 +3333,31 @@ When you will upload a video in this channel, the video support field will be au
           </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit><trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93" datatype="html">
         <source>Captions</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit><trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513" datatype="html">
         <source>Upload thumbnail</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit><trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639" datatype="html">
         <source>Upload preview</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit><trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604" datatype="html">
         <source>Support</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">208</context>
+          <context context-type="linenumber">209</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-watch/modal/video-support.component.html</context>
@@ -3138,13 +3371,13 @@ When you will upload a video in this channel, the video support field will be au
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit><trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1" datatype="html">
         <source>Advanced settings</source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-edit/shared/video-edit.component.html</context>
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit><trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0" datatype="html">
         <source>
@@ -3206,17 +3439,14 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="sourcefile">app/videos/+video-watch/modal/video-report.component.html</context>
           <context context-type="linenumber">3</context>
         </context-group>
-      </trans-unit><trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb" datatype="html">
+      </trans-unit><trans-unit id="827b1376aa35c7a7de90f7724d6a51ccfa20c908" datatype="html">
         <source>
-          Cancel
-        </source>
+      Your report will be sent to moderators of &lt;x id="INTERPOLATION" equiv-text="{{ currentHost }}"/&gt;.
+      &lt;x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/&gt; It will be forwarded to origin instance &lt;x id="INTERPOLATION_1" equiv-text="{{ originHost }}"/&gt; too.&lt;x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/&gt;
+    </source>
         <context-group purpose="location">
           <context context-type="sourcefile">app/videos/+video-watch/modal/video-report.component.html</context>
-          <context context-type="linenumber">19</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
-          <context context-type="linenumber">19</context>
+          <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit><trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9" datatype="html">
         <source>Share</source>
@@ -3260,6 +3490,12 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
           <context context-type="linenumber">3</context>
         </context-group>
+      </trans-unit><trans-unit id="9849bf6a9e45a9a91d13a419afbb5176f9b2367d" datatype="html">
+        <source>Unfederate the video (ask for its deletion from the remote instances)</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">app/videos/+video-watch/modal/video-blacklist.component.html</context>
+          <context context-type="linenumber">21</context>
+        </context-group>
       </trans-unit><trans-unit id="7584313e33a66811eb10646627914a01fff0347d" datatype="html">
         <source>
     The video is being imported, it will be available when the import is finished.
@@ -3531,13 +3767,27 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08" datatype="html">
-        <source>Error getting about from server</source>
+      <trans-unit id="e0e3a472479c8ce1b78f682ffadbe59daf04d331" datatype="html">
+        <source>Cannot get about information from server</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+about/about-instance/about-instance.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="9e601a3b227bb70afbb9b59cd43547b710af1e10" datatype="html">
+        <source>Your message has been sent.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+about/about-instance/contact-admin-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8d6d4f48dae547bb32e0669cda5a665dc8db536c" datatype="html">
+        <source>You already sent this form recently</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+about/about-instance/contact-admin-modal.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968" datatype="html">
         <source>No description</source>
         <context-group purpose="location">
@@ -3571,607 +3821,45 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
-        <source>Error</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+accounts/accounts.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
+      <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6" datatype="html">
+        <source>240p</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+accounts/accounts.component.ts</context>
+          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
+      </trans-unit>
+      <trans-unit id="c8cfad7e7a16c57c42535331b65cb7de40d8402e" datatype="html">
+        <source>360p</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
+      </trans-unit>
+      <trans-unit id="48f0af5a0d0bea4e84b27eaf41b19c85a531c2a5" datatype="html">
+        <source>480p</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
+      </trans-unit>
+      <trans-unit id="6f06138daf6363746ff26bfc0cb2491c09cdfdf2" datatype="html">
+        <source>720p</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/followers-list/followers-list.component.ts</context>
+          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
+      </trans-unit>
+      <trans-unit id="65c94f9beb6fe957808c40060da280cc7ace7ab9" datatype="html">
+        <source>1080p</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/following-add/following-add.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/following-list/following-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/following-list/following-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/shared/redundancy-checkbox.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/jobs/jobs-list/jobs-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-ownership/my-account-ownership.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-ownership/my-account-ownership.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-settings.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-imports/my-account-video-imports.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-videos/my-account-videos.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-videos/my-account-videos.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+verify-account/verify-account-email/verify-account-email.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+verify-account/verify-account-email/verify-account-email.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/core/auth/auth.service.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/login/login.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/reset-password/reset-password.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/search/search.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/forms/reactive-file.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-ban-modal.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-import-url.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/comment/video-comment-add.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/comment/video-comments.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/comment/video-comments.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/comment/video-comments.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/modal/video-blacklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/modal/video-report.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6" datatype="html">
-        <source>240p</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="c8cfad7e7a16c57c42535331b65cb7de40d8402e" datatype="html">
-        <source>360p</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="48f0af5a0d0bea4e84b27eaf41b19c85a531c2a5" datatype="html">
-        <source>480p</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="6f06138daf6363746ff26bfc0cb2491c09cdfdf2" datatype="html">
-        <source>720p</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="65c94f9beb6fe957808c40060da280cc7ace7ab9" datatype="html">
-        <source>1080p</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
+          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="421a937491f19774d17eefa1d24816dae1a9f111" datatype="html">
-        <source>Auto (via ffmpeg)</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba" datatype="html">
-        <source>Success</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/following-add/following-add.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/following-list/following-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/follows/shared/redundancy-checkbox.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/instance-blocklist/instance-account-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/instance-blocklist/instance-server-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-abuse-list/moderation-comment-modal.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-abuse-list/video-abuse-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-edit/user-create.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-edit/user-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+admin/users/user-list/user-list.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-blocklist/my-account-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-blocklist/my-account-server-blocklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-ownership/my-account-accept-ownership/my-account-accept-ownership.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-danger-zone/my-account-danger-zone.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-profile/my-account-profile.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-settings.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channel-create.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channel-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-videos/my-account-videos.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-videos/my-account-videos.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-videos/video-change-ownership/video-change-ownership.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+verify-account/verify-account-ask-send-email/verify-account-ask-send-email.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/login/login.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/reset-password/reset-password.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-ban-modal.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/moderation/user-moderation-dropdown.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/signup/signup.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-import-torrent.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-import-url.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-update.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/modal/video-blacklist.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/modal/video-download.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/modal/video-report.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/modal/video-share.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
+        <source>Auto (via ffmpeg)</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
+          <context context-type="sourcefile">src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -4423,6 +4111,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="0594812d4c50c2adbd1a892a3497c4e5c19e4b32" datatype="html">
+        <source>yes</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6320692861e01fa9c9d4e692d0d27b6c12b21c3b" datatype="html">
+        <source>no</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+admin/moderation/video-blacklist-list/video-blacklist-list.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="652845b2b32b2e117b9b02879b1af07859b0e223" datatype="html">
         <source>Do you really want to remove this video from the blacklist? It will be available again in the videos list.</source>
         <context-group purpose="location">
@@ -4575,6 +4277,48 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="80057baa3b97a4349304bdaa0a880e6f4778561f" datatype="html">
+        <source>My videos history</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-history/my-account-history.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="05f6dda1754741495451b8658bd2248856765d95" datatype="html">
+        <source>Videos history is enabled</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-history/my-account-history.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6bb9ade8637c5e35fb5cb36cf7dbec71c65d4013" datatype="html">
+        <source>Videos history is disabled</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-history/my-account-history.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8453a7a55b8b23bbbc293cd0939fb59a73307de8" datatype="html">
+        <source>Delete videos history</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-history/my-account-history.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f8f86df8a1ae711944c3ab819bb19bf360dfa7a4" datatype="html">
+        <source>Are you sure you want to delete all your videos history?</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-history/my-account-history.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="195d5ba6c8bd05762d9318d0afd0b094fd776164" datatype="html">
+        <source>Videos history deleted</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-history/my-account-history.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e" datatype="html">
         <source>Ownership accepted</source>
         <context-group purpose="location">
@@ -4624,6 +4368,76 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7c193bf704577e514b63497c4f366511afdb6585" datatype="html">
+        <source>New video from your subscriptions</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ba897defa2e6c34d5ee3d10edf8d797a35e7e3e5" datatype="html">
+        <source>New comment on your video</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0a9650640ddd1dfadfe456891d6d4f6093ad428e" datatype="html">
+        <source>New video abuse on local video</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="abac8b7629cfcd85bff25770f83ea229f646f996" datatype="html">
+        <source>One of your video is blacklisted/unblacklisted</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f3eff4df9e4aa9dab411e6eb83833a33016a88bc" datatype="html">
+        <source>Video published (after transcoding/scheduled update)</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ec7ddc265da1df78011ae7677d62a2ae10aef7a4" datatype="html">
+        <source>Video import finished</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c327bbac87cca61f5c52f5825d564878e98b9034" datatype="html">
+        <source>A new user registered on your instance</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f407b90e99a04e2e0d1872c02f01eadbf53e08e2" datatype="html">
+        <source>You or your channel(s) has a new follower</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14c3050a9da4c1bc49d555c45d5660804d08e83b" datatype="html">
+        <source>Someone mentioned you in video comments</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a0f04081717f5f00c0a2c723903c3a2d4c296401" datatype="html">
+        <source>Preferences saved</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="db4ff52375f6a25ad0472e92754c8c265ae47c6b" datatype="html">
         <source>Profile updated.</source>
         <context-group purpose="location">
@@ -4677,24 +4491,28 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814" datatype="html">
-        <source>Do you really want to delete &lt;x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/&gt;? It will delete all videos uploaded in this channel too.</source>
+      <trans-unit id="3859ca2a7577ba8797058d7d97eb8054bc56ec99" datatype="html">
+        <source>Please type the display name of the video channel (&lt;x id="INTERPOLATION" equiv-text="{{displayName}}"/&gt;) to confirm</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e" datatype="html">
-        <source>Please type the name of the video channel to confirm</source>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2" datatype="html">
+        <source>Video channel &lt;x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/&gt; deleted.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2" datatype="html">
-        <source>Video channel &lt;x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/&gt; deleted.</source>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894" datatype="html">
+        <source>My videos</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts</context>
+          <context context-type="sourcefile">src/app/+my-account/my-account-videos/my-account-videos.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -4772,15 +4590,57 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19" datatype="html">
-        <source>Channels</source>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432" datatype="html">
+        <source>My library</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f" datatype="html">
+        <source>My channels</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9" datatype="html">
+        <source>My subscriptions</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4f953496ca94b4f83af049ff715172df2729fb79" datatype="html">
+        <source>My history</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029" datatype="html">
+        <source>Misc</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7" datatype="html">
+        <source>Ownership changes</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6" datatype="html">
+        <source>My settings</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d" datatype="html">
-        <source>Video imports</source>
+      <trans-unit id="0e2434e7d84145c4e8a930ccc4c26c3cb2887e0d" datatype="html">
+        <source>My notifications</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/+my-account/my-account.component.ts</context>
           <context context-type="linenumber">1</context>
@@ -4914,6 +4774,17 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
+        <source>Error</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/core/auth/auth.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/core/notification/notifier.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87" datatype="html">
         <source>You need to reconnect.</source>
         <context-group purpose="location">
@@ -4935,6 +4806,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5" datatype="html">
+        <source>Info</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/core/notification/notifier.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba" datatype="html">
+        <source>Success</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/core/notification/notifier.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe" datatype="html">
         <source>Incorrect username or password.</source>
         <context-group purpose="location">
@@ -5156,10 +5041,10 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5db300f6fba918a35597160183205ede13e8e149" datatype="html">
-        <source>Username is required.</source>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0" datatype="html">
+        <source>Email is required.</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/forms/form-validators/login-validators.service.ts</context>
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
         <context-group purpose="location">
@@ -5167,10 +5052,10 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4eb39d69b74d7a56652ec84fa6826994ee26c0e5" datatype="html">
-        <source>Password is required.</source>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1" datatype="html">
+        <source>Email must be valid.</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/forms/form-validators/login-validators.service.ts</context>
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
         <context-group purpose="location">
@@ -5178,43 +5063,93 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c90872a06666a51c2957c4b29724e68df5c67154" datatype="html">
-        <source>Confirmation of the password is required.</source>
+      <trans-unit id="ac451f128840b34804ea69c820dc3566f476fb33" datatype="html">
+        <source>Your name is required.</source>
         <context-group purpose="location">
-          <context context-type="sourcefile">src/app/shared/forms/form-validators/reset-password-validators.service.ts</context>
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1fc4633008a2431fdec891d58efcc8b865d7de1a" datatype="html">
+        <source>Your name must be at least 1 character long.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c7b44b92c0ce3ccd2f804d001e13da399524e11b" datatype="html">
+        <source>Your name cannot be more than 120 characters long.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="40b35cf927f9f9a59404a6c914ec4632690b69b2" datatype="html">
+        <source>A message is required.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d8d4a23f467ee3e93ca0edb1198c233ed633cf64" datatype="html">
+        <source>The message must be at least 3 characters long.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="07422f6141cfcabaf3c2ce77e3e063222849ef60" datatype="html">
+        <source>The message cannot be more than 5000 characters long.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/instance-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea" datatype="html">
-        <source>Username must be at least 3 characters long.</source>
+      <trans-unit id="5db300f6fba918a35597160183205ede13e8e149" datatype="html">
+        <source>Username is required.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/login-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef" datatype="html">
-        <source>Username cannot be more than 20 characters long.</source>
+      <trans-unit id="4eb39d69b74d7a56652ec84fa6826994ee26c0e5" datatype="html">
+        <source>Password is required.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/login-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9" datatype="html">
-        <source>Username should be only lowercase alphanumeric characters.</source>
+      <trans-unit id="c90872a06666a51c2957c4b29724e68df5c67154" datatype="html">
+        <source>Confirmation of the password is required.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/form-validators/reset-password-validators.service.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6330d25a3bc6f55dfd5177da6e681d1d3b1a2b1a" datatype="html">
+        <source>Username must be at least 1 character long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0" datatype="html">
-        <source>Email is required.</source>
+      <trans-unit id="aaaf3d00c35f809eebc7fd68a3f7b8b0230b197a" datatype="html">
+        <source>Username cannot be more than 50 characters long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1" datatype="html">
-        <source>Email must be valid.</source>
+      <trans-unit id="6f3e95be2538a22da07beaefc39bb2195683990c" datatype="html">
+        <source>Username should be lowercase alphanumeric; dots and underscores are allowed.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
@@ -5287,8 +5222,8 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674" datatype="html">
-        <source>Display name must be at least 3 characters long.</source>
+      <trans-unit id="085b2d6f79819a72a2b56cada4ef5085ba51d90c" datatype="html">
+        <source>Display name must be at least 1 character long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
@@ -5298,8 +5233,8 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9" datatype="html">
-        <source>Display name cannot be more than 120 characters long.</source>
+      <trans-unit id="5a920575b8e1067f5b11c66a4a36d3ced87756f1" datatype="html">
+        <source>Display name cannot be more than 50 characters long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/user-validators.service.ts</context>
           <context context-type="linenumber">1</context>
@@ -5366,8 +5301,8 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9" datatype="html">
-        <source>Report reason cannot be more than 300 characters long.</source>
+      <trans-unit id="8c7d4c82b057aea5dbae811e16935f9bcae4c2aa" datatype="html">
+        <source>Report reason cannot be more than 3000 characters long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/video-abuse-validators.service.ts</context>
           <context context-type="linenumber">1</context>
@@ -5387,8 +5322,8 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f" datatype="html">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
+      <trans-unit id="23c1c2e105a98b0b6728949418a256b026b8971c" datatype="html">
+        <source>Moderation comment cannot be more than 3000 characters long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/video-abuse-validators.service.ts</context>
           <context context-type="linenumber">1</context>
@@ -5450,22 +5385,22 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069" datatype="html">
-        <source>Name must be at least 3 characters long.</source>
+      <trans-unit id="b8b59b6284a14fc71268cf722ed98c62c5af4a76" datatype="html">
+        <source>Name must be at least 1 character long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/video-channel-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482" datatype="html">
-        <source>Name cannot be more than 20 characters long.</source>
+      <trans-unit id="e14cd37d29f13eac7384c339e4f1df58d96e4e3d" datatype="html">
+        <source>Name cannot be more than 50 characters long.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/video-channel-validators.service.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9" datatype="html">
-        <source>Name should be only lowercase alphanumeric characters.</source>
+      <trans-unit id="135185da003b14cbb69521f570fa617a00bbbe18" datatype="html">
+        <source>Name should be lowercase alphanumeric; dots and underscores are allowed.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/forms/form-validators/video-channel-validators.service.ts</context>
           <context context-type="linenumber">1</context>
@@ -5597,6 +5532,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2f5f2093f14679fed82ff76a0cd2a28145a83ca9" datatype="html">
+        <source>PeerTube cannot handle this kind of file. Accepted extensions are &lt;x id="INTERPOLATION" equiv-text="{{extensions}}"/&gt;.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/shared/forms/reactive-file.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0bf41abaa85526711f7952b4600e4044bc7f04a4" datatype="html">
         <source>All unsaved data will be lost, are you sure you want to leave this page?</source>
         <context-group purpose="location">
@@ -6313,29 +6255,29 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1" datatype="html">
-        <source>Subscribed</source>
+      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded" datatype="html">
+        <source>Subscribed to &lt;x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/&gt;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded" datatype="html">
-        <source>Subscribed to &lt;x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/&gt;</source>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1" datatype="html">
+        <source>Subscribed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471" datatype="html">
-        <source>Unsubscribed</source>
+      <trans-unit id="3e7735fa326fcdc9e1188b6d9ff4b4329312fc26" datatype="html">
+        <source>Unsubscribed from &lt;x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/&gt;</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="3e7735fa326fcdc9e1188b6d9ff4b4329312fc26" datatype="html">
-        <source>Unsubscribed from &lt;x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/&gt;</source>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471" datatype="html">
+        <source>Unsubscribed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/shared/user-subscription/subscribe-button.component.ts</context>
           <context context-type="linenumber">1</context>
@@ -6415,13 +6357,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5" datatype="html">
-        <source>Info</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9" datatype="html">
         <source>Upload cancelled</source>
         <context-group purpose="location">
@@ -6429,13 +6364,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa" datatype="html">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/videos/+video-edit/video-add-components/video-upload.component.ts</context>
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee" datatype="html">
         <source>Your video quota is exceeded with this video (video size: &lt;x id="INTERPOLATION" equiv-text="{{videoSize}}"/&gt;, used: &lt;x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/&gt;, quota: &lt;x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/&gt;)</source>
         <context-group purpose="location">
@@ -6457,6 +6385,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="bfdf9de4bd9140f77feb6a5fe2b51f3f0565eaa4" datatype="html">
+        <source>You have unsaved changes! If you leave, your changes will be lost.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/videos/+video-edit/video-update.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="757e9c083c8f3d578bd74f055cc337c72417e187" datatype="html">
         <source>Video updated.</source>
         <context-group purpose="location">
@@ -6545,6 +6480,34 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5b94148c16fa19e3db89972d11e93f790a73a054" datatype="html">
+        <source>Trending for the last 24 hours</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/videos/video-list/video-trending.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8c429645223c24afe30218fc45bb07e352bb1938" datatype="html">
+        <source>Trending videos are those totalizing the greatest number of views during the last 24 hours.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/videos/video-list/video-trending.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6da9ddede61711ecfeaa94fc61a6b7bb844ab3df" datatype="html">
+        <source>Trending for the last &lt;x id="INTERPOLATION" equiv-text="{{days}}"/&gt; days</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/videos/video-list/video-trending.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98b98154eca3533e16b81c5b08611d19949e8661" datatype="html">
+        <source>Trending videos are those totalizing the greatest number of views during the last &lt;x id="INTERPOLATION" equiv-text="{{days}}"/&gt; days.</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/videos/video-list/video-trending.component.ts</context>
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1b157e15c434469d91e56d027b78bf69c9983165" datatype="html">
         <source>Videos from your subscriptions</source>
         <context-group purpose="location">
index b6e62ce80fd1883da1f1ed1579d0fd1987014bab..f5e125549a1ae7af1ad837ae1b0af720c050a5ad 100644 (file)
         <source>Failed</source>
         <target>undefined</target>
       </trans-unit>
+      <trans-unit id="This video does not exist.">
+        <source>This video does not exist.</source>
+        <target>undefined</target>
+      </trans-unit>
+      <trans-unit id="We cannot fetch the video. Please try again later.">
+        <source>We cannot fetch the video. Please try again later.</source>
+        <target>undefined</target>
+      </trans-unit>
+      <trans-unit id="Sorry">
+        <source>Sorry</source>
+        <target>undefined</target>
+      </trans-unit>
+      <trans-unit id="This video is not available because the remote instance is not responding.">
+        <source>This video is not available because the remote instance is not responding.</source>
+        <target>undefined</target>
+      </trans-unit>
       <trans-unit id="Misc">
         <source>Misc</source>
         <target>undefined</target>
index 05e55793d3b24a97fe5f8237f1789ee5fb6d0f04..978f69571f7dd0187f494312fde48fdc33261dbb 100644 (file)
         <source>Password</source>
         <target>الكلمة السرية</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>تسجيل الدخول</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>أرسل لي رسالة لإعادة تعيين كلمتي السرية</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>سجل</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>تغيير اللغة</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
             </source>
         <target>صفحتي العمومية</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
             </source>
         <target>حسابي</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
             </source>
         <target>فيديوهاتي</target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
             </source>
         <target>الخروج</target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>إنشاء حساب</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>الإشتراكات</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>نظرة شاملة</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>الشائعة</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>التي تم إضافتها حديثًا</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>المحلية</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>المزيد</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>الإدارة</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Toggle dark interface</source>
         <target>الإنتقال إلى الواجهة الداكنة</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>No results.</source>
         <target>لا نتائج</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ff78f059449d44322f627d0f66df07abe476962b">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
-  حول مثيل الخدوم <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Cancel
+        </source>
+        <target>إلغاء</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>إرسال</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>الشروط</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>التسجيل مسموح و</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-مثيل الخادوم هذا يوفر مساحة <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/>  لفيديوهات المستخدمين.</target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-مثيل الخادوم هذا يوفر مساحة غير محددة لفيديوهات المستخدمين.</target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-التسجيل غير مسموح حاليا.</target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>الوصف القصير</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>نظرة شاملة عن الفيديوهات</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>الفيديوهات الشائعة</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>الفيديوهات المُضافة حديثًا</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>الفيديوهات المحلية</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>سياسة الفيديوهات التي تحتوي على محتوى حساس</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5e155c34fb3ed8159bf0a486a366cfbc6874f9fe">
         <source>Signup enabled</source>
         <target>التسجيل مُفعل</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>يتطلب التسجيل رسالة تأكيد</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>حد التسجيل</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>المستخدِمون</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>حصة الفيديو الافتراضية للمستخدم</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>حد الرفع الإفتراضي للمستخدِم</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>استيراد</target>
         <source>Administrator</source>
         <target>المدير</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>البريد الإلكتروني للمدير</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>المستخدِمون</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>حصة الفيديو الافتراضية للمستخدم</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>حد الرفع الإفتراضي للمستخدِم</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>اسم المستخدِم الخاص بك على تويتر</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Customizations</source>
         <target>التخصيصات</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>الجافا سكريبت</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>الإعدادات المتقدمة</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
         <source>Ban reason:</source>
         <target>سبب الحظر:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>الإجراءات</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Blacklist reason:</source>
         <target>سبب الحجب:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>إعداداتي</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>مكتبتي</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>قنواتي</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>فيديوهاتي</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>اشتراكاتي</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>وارداتي</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>أخرى</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>مثيلات الخوادم المكتومة</target>
-        <context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>تقم الفيديو:</target>
         <source>Profile</source>
         <target>الملف الشخصي</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>إعدادات الفيديو</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>منطقة الخطر</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>إرسال</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="4a806761798181e907e28ed1af053d466526800d">
         <source>Blacklisted</source>
         <target>تم حجبه</target>
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>مثيلات الخوادم المكتومة</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>تغيير الكلمة السرية</target>
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>عذرا، لقد حدث خلل ما</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="047f50bc5b5d17b5bec0196355953e1a5c590ddb">
         <source>Update</source>
         <target>تحديث</target>
           <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21add64f0f3ebbedf1150ca822c6e149494ab7a9">
+        <source>Select the file to upload</source>
+        <target>اختر الملف الذي تريد ارساله</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5e420747842373fa99a75a7a18df068cc81e46fb">
         <source>Scheduled</source>
         <target>مبرمجة</target>
         <source>Publish</source>
         <target>أنشر</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
         <source>Cancel create</source>
         <target>إلغاء الإنشاء</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>إلغاء الحذف</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
+        <source>Upload thumbnail</source>
+        <target>تحديث الصورة المصغرة</target>
+        <context-group name="null">
+          <context context-type="linenumber">196</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
+        <source>Upload preview</source>
+        <target>إرسال معاينة</target>
+        <context-group name="null">
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
         <source>Advanced settings</source>
         <target>الإعدادات المتقدمة</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9aafb2a928664aa7a9375fd37c533f0375f8b611">
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>إلغاء</target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>شارك</target>
           <context context-type="linenumber">57</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="623698f075025b2b2fc2e0c59fd95f4f4662a509">
+        <source>Dislike this video</source>
+        <target>إلغاء الإعجاب بهذه الفيديو</target>
+        <context-group name="null">
+          <context context-type="linenumber">64</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="144fff5c40b85414d59e644d8dee7cfefba925a2">
         <source>Download the video</source>
         <target>تنزيل الفيديو</target>
           <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="007ab5fa2aae8a7372307d3fc45a2dbcb11ffd61">
+        <source>Blacklist</source>
+        <target>حجب في القائمة السوداء</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="803c6317abd2dbafcc93226c4e273c62932e3037">
         <source>Blacklist this video</source>
         <target>حجب هذه الفيديو</target>
           <context context-type="linenumber">152</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4c0ba3cde3b3c58b855ffb4beaa5804a2fc3826b">
+        <source>Friendly Reminder: </source>
+        <target>تذكير أخوي:</target>
+        <context-group name="null">
+          <context context-type="linenumber">208</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e60c11e1b1dfbbeda577364b8de39ded2d796c5e">
         <source>More information</source>
         <target>المزيد من التفاصيل</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>خطأ</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>تÙ\85 Ø¨Ù\86جاح</target>
+      <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
+        <source>Configuration updated.</source>
+        <target>تÙ\85 ØªØ­Ø¯Ù\8aØ« Ø§Ù\84إعدادات</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4d8f527638f3e0b518a96e07d41d886bcce01246">
+        <source>enabled</source>
+        <target>مفعّل</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="795733aac948794cadeb3be6386882efac2c38ad">
+        <source>disabled</source>
+        <target>خامل</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="800cd3cdf47751b576587259ba3a1bc0a7f435b6">
         <source>Comment updated.</source>
         <target>تم تحديث التعليق.</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e">
+        <source>Ownership accepted</source>
+        <target>تم قبول الملكية</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="19508af0dfbc685cbf10cf02061bb5a0f423b6fc">
         <source>Password updated.</source>
         <target>تم تحديث الكلمة السرية.</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="466fc8cf56fd4e4e90fec4b900ef083d52bec38c">
+        <source>You current password is invalid.</source>
+        <target>كلمتك السرية الحالية غير صالحة. </target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ca8e8cf0f1686604db3b6a2ebadab7f7b426a047">
         <source>Are you sure you want to delete your account? This will delete all you data, including channels, videos etc.</source>
         <target>متأكد أنك تريد حذف حسابك ؟هذا سيحذف بياناتك,قنواتك,فيديوهاتك الخ.</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>رجاء أدخل اسم القنات للتأكيد</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>فيديوهاتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dd9f3264feed4935008861c15d81c947124e4ac3">
+        <source>Published</source>
+        <target>المنشورة</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="289fe8342e8b7df689c75026a24a60fd7f5e9392">
+        <source>To import</source>
+        <target>للاستيراد</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>مكتبتي</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>القنوات</target>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>قنواتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>اشتراكاتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>أخرى</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>إعداداتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ff6becacbce7fc0943b0af0df4dd67e5e11bf598">
+        <source>Subscribe to the account</source>
+        <target>الاشتراك في الحساب</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b19ee83cbd2b735fd081b9aa483a890578019099">
+        <source>Toggle the left menu</source>
+        <target>الانتقال إلى القائمة اليسرى</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b54759e30f7c1983940cdacb8eb03f102a869084">
+        <source>Go to the videos overview page</source>
+        <target>الذهاب إلى صفحة معاينة الفيديوهات</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e919c88a3f889d6659288e69d3e178da0ea7ab0">
+        <source>Go to the trending videos page</source>
+        <target>الذهاب إلى صفحة الفيديوهات الشائعة</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="249618dcdd7fbdc863c0714e2eb9e8940bc9c37d">
+        <source>Go to the recently added videos page</source>
+        <target>الذهاب إلى صفحةالفيديوهات المضافة حديثا</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7e194daef3a3509128c4300d4c7c292c49ebf3f5">
+        <source>Go to the local videos page</source>
+        <target>الذهاب إلى صفحة الفيديوهات المحلية</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1fb6204f39a7338e5110b2f113643c9288496ba">
+        <source>Go to the videos upload page</source>
+        <target>الذهاب إلى صفحة إرسال الفيديوهات</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0ed7b40c11da9d4565af9c041df20c15bc6be97e">
+        <source>Toggle Dark theme</source>
+        <target>التغيير إلى السمة الداكنة</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="badd4b24618ccc8a34620acb9053fc654b9612b2">
+        <source>Go to my subscriptions</source>
+        <target>الذهاب إلى اشتراكاتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b7184b5a236618e8edd747529869c392ab6dace1">
+        <source>Go to my videos</source>
+        <target>الذهاب إلى فيديوهاتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="acf985bd42886b9b3030b5f68f0e8417c39b40a7">
+        <source>Go to my imports</source>
+        <target>الذهاب إلى استيراداتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cfe3c51f0ae9385dc2ce6df740d87e5514aa9390">
+        <source>Go to my channels</source>
+        <target>الذهاب إلى قنواتي</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>خطأ</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5c0c574151dc8671d9199980ee04bf65aec3b452">
+        <source>Keyboard Shortcuts:</source>
+        <target>اختصارات لوحة المفاتيح:</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>معلومات</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>تم بنجاح</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>اسم المستخدم أو كلمة المرور خاطئة.</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b0f24b7136e551a0deba831f1525711245b31a26">
+        <source>Your password has been successfully reset!</source>
+        <target>لقد تم إعادة تعيين كلمتك السرية بنجاح!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7fb1099e29660162f9154d5b2feee7743a423df6">
         <source>Today</source>
         <target>اليوم</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>البريد الإلكتروني مطلوب.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>يجب أن يكون عنوان البريد الإلكتروني عنوانًا صالحًا.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>اسم المستخدم مطلوب.</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>يجب أن يكون طول اسم المستخدِم أكبر مِن 3 أحرف. </target>
+      <trans-unit id="545e77fd5d9526228a2133109447c23225ed9c85">
+        <source>User role is required.</source>
+        <target>دور المستخدم مطلوب.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>اÙ\84برÙ\8aد Ø§Ù\84Ø¥Ù\84Ù\83ترÙ\88Ù\86Ù\8a Ù\85Ø·Ù\84Ù\88ب.</target>
+      <trans-unit id="1c417b7aef730d6ef5d62fa8a0a7e25e3a2393e4">
+        <source>Display name is required.</source>
+        <target>عرض Ø§Ù\84اسÙ\85 Ù\84ازÙ\85.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>يجب أن يكون عنوان البريد الإلكتروني عنوانًا صالحًا.</target>
+      <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
+        <source>Description must be at least 3 characters long.</source>
+        <target>طول الوصف يجب أن يتعدى 3حروف.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b3cf1889d2fdd6b15e697c270c9b80772fe2cae6">
+        <source>Report reason is required.</source>
+        <target>سبب الإبلاغ لازم.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
+        <source>Moderation comment is required.</source>
+        <target>تعليق الإشراف لازم.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
+        <source>The channel is required.</source>
+        <target>القناة لازمة.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd7fc070c728dc6dbf3959d49fe5bb27ce15d294">
+        <source>The username is required.</source>
+        <target>اسم المستخدم مطلوب.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c8465c3773699dd075e0147e264d2e232f605803">
+        <source>You can only transfer ownership to a local account</source>
+        <target>لا يمكن نقل الملكية إلى حساب محلي</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="34a0811f9a2a7366cc9efcdad52ea59b105326ea">
+        <source>A tag should be less than 30 characters long.</source>
+        <target>طول الوسم لا يجب أن يتجاوز 30 حرفا.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="3b7ed22d0730d03b38c254332829d855ee7256c4">
         <source>This file is too large.</source>
         <target>حجم هذا الملف كبير جدًّا.</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="99ee4faa69cd2ea8e3678c1f557c0ff1f05aae46">
+        <source>Clear</source>
+        <target>مسح</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4e231a74ad4739e7b0606e8e66d5a656f5855a5a">
+        <source>Torrent import</source>
+        <target>استيراد تورنت</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="dc60677d5a906e69f38a5cf9da7f2eb03931bea0">
         <source>Links</source>
         <target>الروابط</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ab783a52f2df9ff7a20139cab0da6d0764f3cc5d">
+        <source>Too many attempts, please try again later.</source>
+        <target>محاولات كثيرة، يرجى العودة لاحقا.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0f286a597f0053c3578a52e044769c204ee516fc">
+        <source>Server error. Please retry later.</source>
+        <target>خطأ على السيرفر. يرجى إعادة المحاولة لاحقا.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
         <source>Subscribed</source>
         <target>مشترك</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>معلومات</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>تم إلغاء الإرسال</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="fa2601e52cbf5725a13d33fe14458823b882ea50">
+        <source>Video reported.</source>
+        <target>فيديو تم الإبلاغ عنها.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0e65067fdcc9d8725a41896cb1e229d1415a45f6">
         <source>Like the video</source>
         <target>الإعجاب بالفيديو</target>
index 7444b71b08a8d7d699728ec3d70eed37ac4bd726..15c1bbca0452076c509df21f6dc7eece0ca8d410 100644 (file)
         <source>Password</source>
         <target>Contrasenya</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Iniciar sessió</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Envia'm un correu per reiniciar la meva contrasenya</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Registra't</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
         <source>Change the language</source>
         <target>Canvia la llengua</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Registrar un compte</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Trending</source>
         <target>Tendència</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Afegits fa poc</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Local</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Més</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administració</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>No results.</source>
         <target>Sense resultats.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ff78f059449d44322f627d0f66df07abe476962b">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
-        <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
-  Quant a la instància <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Envia</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Termes</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>El registre d'usuaris és permès i</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      aquesta instància proporciona una quota bàsica de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> d''espai per els vídeos dels seus usuaris.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      aquesta instància proporciona espai il·limitat per els vídeos del seus usuaris.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    El registre d'usuaris actualment no és permès.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Descripció curta</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Ruta per defecte del client</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Vídeos tendència</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Vídeos afegits fa poc</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Vídeos locals</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Política sobre vídeos que contenen contingut sensible</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Registre activat</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limit de registres</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Usuaris</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Quota de vídeo per defecte de l'usuari</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administrador</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Correu del Administrador</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Usuaris</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Quota de vídeo per defecte de l'usuari</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>El teu nom d'usuari de Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indica el compte de Twitter del lloc web o plataforma en què es va publicar el contingut.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instància a la llista blanca de Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Transcodificació</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transcodificació activada</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Si desactives la transcodificació, molts vídeos dels teus usuaris no funcionaran.</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Subprocessos per la transcodificació</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Memòria cau per a visualitzacions prèvies</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalitzacions</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Escriu directament el codi JavaScript.&lt;br /&gt;Exemple: &lt;pre&gt;console.log('la meva instància és sorprenent');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Configuració avançada</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Actualitza la configuració</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Sembla que la configuració no és vàlida. Cerca possibles errors a les diferents pestanyes.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>La meva configuració</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Els meus vídeos</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Quota de vídeo:</target>
         <source>Profile</source>
         <target>Perfil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Ajustos de vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Envia</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
@@ -1485,14 +1427,14 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
         <source>Publish will be available when upload is finished</source>
         <target>La publicació estarà disponible quan finalitzi la càrrega</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publica</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fdf7cbdc140d0aab0f0b6c06065a0fd448ed6a2e">
@@ -1548,7 +1490,7 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
         <source>Wait transcoding before publishing the video</source>
         <target>Espera la transcodificació abans de publicar el vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -1562,14 +1504,14 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
         <source>Upload thumbnail</source>
         <target>Puja miniatura</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Previsualitza la càrrega</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -1583,14 +1525,14 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Text breu per dir a la gent com us poden ajudar (plataforma de pertinença ...).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Ajustos avançats</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -1841,13 +1783,6 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>S'ha produït un error en obtenir quant a del servidor</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Sense descripció</target>
@@ -1869,20 +1804,6 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Error</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Èxit</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>S'ha actualitzat la configuració.</target>
@@ -2044,23 +1965,16 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Estàs segur que vols eliminar <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? També s''esborraràn tots els vídeos carregats en aquest canal.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Escriu el nom del canal de vídeo per confirmar</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Canal de vídeo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> eliminat.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Canal de vídeo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> eliminat.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Els meus vídeos</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2121,6 +2035,13 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>La meva configuració</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ccbf0490fb6b60d21e03bb2c9003df0ce1a58752">
         <source>Unable to find user id or verification string.</source>
         <target>No es pot trobar l'identificador d'usuari ni la cadena de verificació.</target>
@@ -2144,6 +2065,13 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Error</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Necessites tornar a connectar.</target>
@@ -2158,6 +2086,20 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Informació</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Èxit</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b0f24b7136e551a0deba831f1525711245b31a26">
         <source>Your password has been successfully reset!</source>
         <target>La contrasenya s'ha restablit correctament.</target>
@@ -2263,6 +2205,20 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>Es requereix un correu electrònic.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>El correu electrònic ha de ser vàlid.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Es requereix nom d'usuari.</target>
@@ -2284,41 +2240,6 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>El nom d'usuari ha de tenir com a mínim 3 caràcters.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>El nom d'usuari no pot tenir més de 20 caràcters.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>El nom d'usuari ha de ser només caràcters alfanumèrics en minúscules.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Es requereix un correu electrònic.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>El correu electrònic ha de ser vàlid.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>La contrasenya ha de tenir com a mínim 6 caràcters.</target>
@@ -2368,20 +2289,6 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>El nom de visualització ha de tenir un mínim de 3 caràcters.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>El nom de visualització no pot tenir més de 120 caràcters.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>La descripció ha de tenir almenys 3 caràcters de longitud.</target>
@@ -2403,13 +2310,6 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>El motiu de l'informe no pot tenir més de 300 caràcters.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>El text de suport ha de tenir un mínim de 3 caràcters.</target>
@@ -3054,13 +2954,6 @@ Quan pugis un vídeo en aquest canal, el camp d'assistència de vídeo s'omplir
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Informació</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Pujada cancel·lada</target>
index 59af086390ba6f15057e119e61036750c060f454..7b6f99ddb86ee19e69aea97de76e689cea97c857 100644 (file)
     Unsubscribe
   </source>
         <target>
-Přestat odebírat</target>
+    Přestat odebírat
+  </target>
         <context-group name="null">
           <context context-type="linenumber">18</context>
         </context-group>
@@ -337,14 +338,21 @@ Přestat odebírat</target>
       </trans-unit>
       <trans-unit id="5047522cc670b1f4a288bce07f9b1c5061e913ed">
         <source>Subscribe with a Mastodon account:</source>
-        <target>Odebírat přes Mastodon účet</target>
+        <target>Odebírat přes účet na Mastodonu:</target>
         <context-group name="null">
           <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d8758664cadd6452256ca25ca0c7259074f427c1">
+        <source>Using a syndication feed</source>
+        <target>Použít syndikační proud</target>
+        <context-group name="null">
+          <context context-type="linenumber">48</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="d5e5bc7d213694fc0414a76f0ff3085bae44268a">
         <source>Subscribe via RSS</source>
-        <target>Odebírat RSS</target>
+        <target>Odebírat přes RSS</target>
         <context-group name="null">
           <context context-type="linenumber">49</context>
         </context-group>
@@ -362,6 +370,20 @@ Přestat odebírat</target>
           <context context-type="linenumber">10</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="319933e1af77ca2e35b75a5e9270a3c90e83dd4b">
+        <source>You can subscribe to the channel via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there.</source>
+        <target>Tento kanál můžete odebírat z jakékoliv instance na fediverse používající ActivityPub. Například u Mastodonu nebo Pleromy můžete napsat URL adresu kanálu do vyhledávacího pole a tam začít odebírat.</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2767d5461b6c622ccdeb868df8becf26bc16b99a">
+        <source>You can interact with this via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there.</source>
+        <target>S tímto videem můžete interagovat z jakékoliv instance na fediverse používající ActivityPub. Například u Mastodonu nebo Pleromy můžete napsat aktuální URL adresu do vyhledávacího pole a odtamtud interagovat.</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="15f046007e4fca2e8477966745e2ec4e3e81bc3b">
         <source>Video quota</source>
         <target>Limit na videa</target>
@@ -438,7 +460,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
       </trans-unit>
       <trans-unit id="51ef29329faccb28d94369897068897d1b3d0478">
         <source>Username or email address</source>
-        <target>Uživatelské jméno nebo email</target>
+        <target>Uživatelské jméno nebo e-mail</target>
         <context-group name="null">
           <context context-type="linenumber">15</context>
         </context-group>
@@ -476,7 +498,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Password</source>
         <target>Heslo</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
@@ -490,7 +512,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Login</source>
         <target>Přihlásit</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
@@ -502,23 +524,23 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
       </trans-unit>
       <trans-unit id="244aae9346da82b0922506c2d2581373a15641cc">
         <source>Email</source>
-        <target>Email</target>
+        <target>E-mail</target>
         <context-group name="null">
           <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
       <trans-unit id="69b6ac577a19acc39fc0c22342092f327fff2529">
         <source>Email address</source>
-        <target>Emailová adresa</target>
+        <target>E-mailová adresa</target>
         <context-group name="null">
           <context context-type="linenumber">10</context>
         </context-group>
       </trans-unit>
       <trans-unit id="78be69e4d26b3b654c49962839d8545e61bf8b55">
         <source>Send me an email to reset my password</source>
-        <target>Poslat email pro resetování hesla</target>
+        <target>Poslat e-mail pro resetování hesla</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
@@ -589,7 +611,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Signup</source>
         <target>Registrovat</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
@@ -659,7 +681,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Change the language</source>
         <target>Změnit jazyk</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
@@ -670,7 +692,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
              Můj veřejný profil
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
@@ -681,7 +703,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
               Můj účet
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
@@ -692,7 +714,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
               Moje videa
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
@@ -703,14 +725,14 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
               Odhlásit
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Vytvořit účet</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
@@ -724,49 +746,49 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Subscriptions</source>
         <target>Odběry</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Přehled</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Trendy</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Nedávno přidané</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Místní</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Další</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administrace</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
@@ -776,11 +798,18 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">25</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4752e5e33da1c3396d3248eb8fef59bca5d00cb3">
+        <source>Show keyboard shortcuts</source>
+        <target>Zobrazit klávesové zkratky</target>
+        <context-group name="null">
+          <context context-type="linenumber">89</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>Přepnout tmavé rozhraní</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
@@ -811,6 +840,20 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">15</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="a02ea1d4e7424ca989929da5e598f379940fdbf2">
+        <source>Duration</source>
+        <target>Trvání</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dc67060f94f0f2b58549f54a5c07925dffd20238">
+        <source>Display sensitive content</source>
+        <target>Zobrazit citlivý obsah</target>
+        <context-group name="null">
+          <context context-type="linenumber">33</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="4f20f2d5a6882190892e58b85f6ccbedfa737952">
         <source>Yes</source>
         <target>Ano</target>
@@ -867,11 +910,18 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">94</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="41ed53a3f1d4dfc57011d0aba13b8b074e8b41b6">
+        <source>Display unlisted and private videos</source>
+        <target>Zobrazit neuvedená a soukromá videa</target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Žádné výsledky.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
@@ -929,15 +979,11 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
-        <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
-  O instanci <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Odeslat</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
@@ -951,47 +997,14 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Terms</source>
         <target>Podmínky</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Registrace uživatelů je povolena a</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      tato instance poskytuje základní limit <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> místa pro videa svým uživatelům.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      tato instance poskytuje neomezený prostor pro videa svých uživatelů.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Registrace uživatelů není momentálně povolena.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
@@ -1126,6 +1139,21 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b4c2ef0143270626106b26196d40baf3439aa7b0">
+        <source>
+      Web peers are not publicly accessible: because we use WebRTC inside the web browser (<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>with the WebTorrent library<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>), the protocol is different from classic BitTorrent.
+      When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers to forward the information to.
+      See <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/>this document<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> for more information
+    </source>
+        <target>
+      Webové peery nejsou veřejně dostupné: jelikož používáme WebRTC v prohlížeči (<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>s knihovnou WebTorrent<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>), je protokol odlišný od klasického BitTorrentu.
+      Když jste ve webovém prohlížeči, pošlete trackeru signál obsahující vaši IP adresu. Tracker pak náhodně vybere další peery, kterým přepošle informace.
+      Pro více informací si přečtěte <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/>tento dokument<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">55</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="50d8e8388f5ceab292850ed828f306c9f2cab389">
         <source>
     The worst-case scenario of an average person spying on their friends is quite unlikely.
@@ -1141,7 +1169,7 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
       </trans-unit>
       <trans-unit id="4bf47a1ae952bf42a4682a5ecddb0bfb8c9adfaf">
         <source>How does PeerTube compare with YouTube?</source>
-        <target>Jaký e PeerTube v porovnání s YouTube?</target>
+        <target>Jaký je PeerTube v porovnání s YouTube?</target>
         <context-group name="null">
           <context context-type="linenumber">67</context>
         </context-group>
@@ -1153,9 +1181,9 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
     Moreover, YouTube is owned by Google/Alphabet, a company that tracks you across many websites (via AdSense or Google Analytics).
   </source>
         <target>
-    Ohrožení soukromí je na YouTube odlišné od toho na PeerTube.
+    Ohrožení soukromí je na YouTubu odlišné od toho na PeerTubu.
     V případě YouTube, tato služba o vás sbírá obrovské mnžoství osobních informací (nejen vaší IP adresu), aby je poté analyzovala a sledovala vás.
-    Kromě toho, YouTube je vlastněn Google/Alphabet, společností, která vás sleduje napříč různými webovými stránkami (přes AdSense nebo Google Analytics).
+    Kromě toho je YouTube vlastněn Googlem/Alphabetem, společností, která vás sleduje napříč různými webovými stránkami (přes AdSense nebo Google Analytics).
   </target>
         <context-group name="null">
           <context context-type="linenumber">69</context>
@@ -1190,6 +1218,19 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">83</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b1372cb61ca791a0f7f95bf31c86c97df142adc4">
+        <source>
+    PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released.
+    In the meantime, we want to test different ideas related to this issue:
+  </source>
+        <target>
+    PeerTube je v rané fázi a chceme vám doručit ta nejlepší možná opatření, dokud nebude vydána stabilní verze.
+    Mezitím chceme vyzkoušet různé nápady spojené s tímto problémem:
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">85</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="d32608aba08c6bb3cc4e4e8ec6223e5f4e78ca19">
         <source>Set a limit to the number of peers sent by the tracker</source>
         <target>Nastavit limit počtu peerů odeslaných trackerem</target>
@@ -1225,6 +1266,13 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="bd2edf99dd6562385ccec19a7ab2d1898e626605">
+        <source>Banned</source>
+        <target>Zablokován</target>
+        <context-group name="null">
+          <context context-type="linenumber">12</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a835d8a12e14eb96919245a0bbafd8069c146578">
         <source><x id="INTERPOLATION" equiv-text="{{ account.followersCount }}"/> subscribers</source>
         <target><x id="INTERPOLATION" equiv-text="{{ account.followersCount }}"/> odběratelů</target>
@@ -1278,42 +1326,49 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Short description</source>
         <target>Krátký popis</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Výchozí hlavní stránka</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
+        <source>Videos Overview</source>
+        <target>Přehled videí</target>
+        <context-group name="null">
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Trendy</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Naposledy přidaná videa</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Místní videa</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Pravidla pro videa obsahující citlivý obsah</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
@@ -1348,42 +1403,63 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Signup enabled</source>
         <target>Povolit registrace</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
+        <source>Signup requires email verification</source>
+        <target>Registrace vyžaduje ověření e-mailem.</target>
+        <context-group name="null">
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limit registrací</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Uživatelé</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
-        <source>Administrator</source>
-        <target>Administrátor</target>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Výchozí limit na uživatele</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">109</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
-        <source>Admin email</source>
-        <target>Email administrátora</target>
+      <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
+        <source>Import</source>
+        <target>Import</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
+          <context context-type="linenumber">42</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Uživatelé</target>
+      <trans-unit id="29aa67f13fd34a2421ff9d7de7d5142790676b9e">
+        <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
+        <target>Import videa pomocí URL HTTP (např. YouTube) povolen</target>
         <context-group name="null">
-          <context context-type="linenumber">144</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Výchozí limit na uživatele</target>
+      <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
+        <source>Administrator</source>
+        <target>Administrátor</target>
+        <context-group name="null">
+          <context context-type="linenumber">155</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
+        <source>Admin email</source>
+        <target>E-mail administrátora</target>
         <context-group name="null">
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
@@ -1404,21 +1480,21 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Your Twitter username</source>
         <target>Váš účet na Twitteru</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Uveďte Twitter účet stránky nebo služby, na které byl obsah publikován.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Twitter povolil tuto instanci</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
@@ -1432,77 +1508,99 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Transcoding</source>
         <target>Překódování</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Překódování povoleno</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Pokud zakážete překódování, mnoho videí od vašich uživatelů nebude fungovat!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Vlákna na překódování</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
+        <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
+        <target>Rozlišení <x id="INTERPOLATION" equiv-text="{{resolution}}"/> povoleno</target>
+        <context-group name="null">
+          <context context-type="linenumber">252</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
+        <source>
+          Cache
+
+          <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
+        </source>
+        <target>
+          Mezipaměť
+
+          <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
+        </target>
+        <context-group name="null">
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Velikost mezipaměti náhledů</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Přizpůsobení</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Pište přímo JavaScript kód.&lt;br /&gt;Například: &lt;pre&gt;console.log('moje instance je úžasná');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Pokročilá nastavení</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Aktualizovat nastavení</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Zdá se, že vaše konfigurace není validní. Prosím, vyhledejte potencialní chyby v jiné záložce.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
@@ -1527,6 +1625,17 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1a5c7f9b1bec1463728f44933f0e256de9c45154">
+        <source>
+      Moderation
+    </source>
+        <target>
+      Moderace
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7bea88c54fdccfdc9f687b0ffe9bf6a653d19368">
         <source>
       Jobs
@@ -1574,6 +1683,13 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="25925fc5826bc5b3eeae7c45b08b0ed74b9e2954">
+        <source>Filter...</source>
+        <target>Filtrovat...</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c">
         <source>ID</source>
         <target>ID</target>
@@ -1713,6 +1829,13 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="adba7c8b43e42581460fbe5d08b5cb5ab60eba4b">
+        <source>(banned)</source>
+        <target>(zablokován)</target>
+        <context-group name="null">
+          <context context-type="linenumber">65</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="be73b652c2707f42b5d780d0c7b8fc5ea0b1706c">
         <source>Go to the account page</source>
         <target>Přejít na stránku kanálu</target>
@@ -1720,6 +1843,31 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
+        <source>Ban reason:</source>
+        <target>Důvod zablokování:</target>
+        <context-group name="null">
+          <context context-type="linenumber">95</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5731e5d5ac989bf08848b5a57a5586cf84d80964">
+        <source>
+        This comment can only be seen by you or the other moderators.
+      </source>
+        <target>
+        Tento komentář můžete vidět pouze vy nebo ostatní moderátoři.
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0562e455c88234829f3c27a38f3039f027bfd5d2">
+        <source>Update this comment</source>
+        <target>Aktualizovat tento komentář</target>
+        <context-group name="null">
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2bf5a31043ff476ca081a4080f3f3f17518dc6f2">
         <source>Reporter</source>
         <target>Autor nahlášení</target>
@@ -1748,25 +1896,25 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
           <context context-type="linenumber">33</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="00ecde6001106fe7406a34cc3459cc5b88e4aec1">
-        <source>Blacklisted videos</source>
-        <target>Videa na černé listině</target>
+      <trans-unit id="030b4423b92167200e39519599f9b863b4f7c62c">
+        <source>Actions</source>
+        <target>Akce</target>
         <context-group name="null">
-          <context context-type="linenumber">7</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Moje nastavení</target>
+      <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
+        <source>Reason:</source>
+        <target>Důvod:</target>
         <context-group name="null">
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Moje videa</target>
+      <trans-unit id="00ecde6001106fe7406a34cc3459cc5b88e4aec1">
+        <source>Blacklisted videos</source>
+        <target>Videa na černé listině</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
@@ -1780,21 +1928,14 @@ Blokovaný uživatel se už nebude moci přihlásit.</target>
         <source>Profile</source>
         <target>Profil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Nastavení videí</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Odeslat</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
@@ -1915,6 +2056,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">27</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4b50f2ef2e8b9a24e674d12012ee310f378a5503">
+        <source><x id="INTERPOLATION" equiv-text="{{ actor.followersCount }}"/> subscribers</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ actor.followersCount }}"/> odběratelů</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c860c88df9ad58b1187084251340b232cdf0a7f9">
         <source>(extensions: <x id="INTERPOLATION" equiv-text="{{ avatarExtensions }}"/>, max size: <x id="INTERPOLATION_1" equiv-text="{{ maxAvatarSize | bytes }}"/>)</source>
         <target>(typ souboru: <x id="INTERPOLATION" equiv-text="{{ avatarExtensions }}"/>, maximální velikost: <x id="INTERPOLATION_1" equiv-text="{{ maxAvatarSize | bytes }}"/>)</target>
@@ -1986,14 +2134,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Publish will be available when upload is finished</source>
         <target>Publikovat lze jakmile bude dokončeno nahrávání</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publikovat</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fdf7cbdc140d0aab0f0b6c06065a0fd448ed6a2e">
@@ -2005,11 +2153,18 @@ When you will upload a video in this channel, the video support field will be au
       </trans-unit>
       <trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b">
         <source>Tags</source>
-        <target>Tagy</target>
+        <target>Štítky</target>
         <context-group name="null">
           <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="457b1cff4d8d7fad0c8742f69c413ecf5e443851">
+        <source>Tags could be used to suggest relevant recommendations.&lt;/br&gt;Press Enter to add a new tag.</source>
+        <target>Štítky mohou být použity pro navržení relevantních doporučení.&lt;/br&gt;Stisknutím klávesy Enter přidáte nový štítek.</target>
+        <context-group name="null">
+          <context context-type="linenumber">18</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="50f53834157770b8205ada0e7a6e235211e4765e">
         <source>Video descriptions are truncated by default and require manual action to expand them.</source>
         <target>Popisy videí jsou ve výchozím stavu sbaleny a rozbalují se kliknutím.</target>
@@ -2049,7 +2204,7 @@ When you will upload a video in this channel, the video support field will be au
         <source>Wait transcoding before publishing the video</source>
         <target>Čekat na překódování před publikováním videa</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2063,14 +2218,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Upload thumbnail</source>
         <target>Nahrát miniaturu</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Nahrát náhled</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2084,14 +2239,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Krátký text, co řekne lidem, jak vás mohou podpořit.</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Rozšířená nastavení</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -2342,13 +2497,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Chyba při získávání popisu</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Žádný popis</target>
@@ -2370,20 +2518,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Chyba</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Úspěšně</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Nastavení aktualizováno.</target>
@@ -2489,6 +2623,27 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="50dc7afa2305131cdbdb384cfc1f2a5f0f4647d8">
+        <source>Unban</source>
+        <target>Odblokovat</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98119091712a8ca72905e3b4c1cf60649af7565e">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{num}}"/> users?</source>
+        <target>Opravdu chcete odblokovat <x id="INTERPOLATION" equiv-text="{{num}}"/> uživatelů?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6121be086a51c4c73bbdd8aebdddd9744c8f1ffd">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users unbanned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> uživatelů odblokováno.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="911fc197949e47aa5f0541627bc319f59edd9d11">
         <source>You cannot delete root.</source>
         <target>Uživatel root nelze odstranit.</target>
@@ -2545,23 +2700,16 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Opravdu chcete odstranit <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Tato akce odstraní i veškerá videa na tomto kanálu.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Prosím, napište název tohoto kanálu pro potvrzení</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Video kanál <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> odstraněn.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Video kanál <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> odstraněn.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Moje videa</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2622,6 +2770,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Moje odběry</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Moje nastavení</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ccbf0490fb6b60d21e03bb2c9003df0ce1a58752">
         <source>Unable to find user id or verification string.</source>
         <target>Nelze najít uživatelovo id nebo verifikační řetězec.</target>
@@ -2629,6 +2791,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ff6becacbce7fc0943b0af0df4dd67e5e11bf598">
+        <source>Subscribe to the account</source>
+        <target>Odebírat účet</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="edeaa933b09690523e46977e11064e9c655d77d7">
         <source>Cannot retrieve OAuth Client credentials: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
 </source>
@@ -2645,6 +2814,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Chyba</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Musíte se znovu připojit.</target>
@@ -2659,6 +2835,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Info</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Úspěšně</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b0f24b7136e551a0deba831f1525711245b31a26">
         <source>Your password has been successfully reset!</source>
         <target>Vaše heslo bylo úspěšně resetováno!</target>
@@ -2745,14 +2935,14 @@ When you will upload a video in this channel, the video support field will be au
       </trans-unit>
       <trans-unit id="1245841647f9b42d3e7554903c1c50bdd80ab021">
         <source>Admin email is required.</source>
-        <target>Email administrátora je vyžadován.</target>
+        <target>E-mail administrátora je vyžadován.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fd2feb77dfe57fe82573e3cdf996105e2fafc66">
         <source>Admin email must be valid.</source>
-        <target>Email administrátora musí být platný.</target>
+        <target>E-mail administrátora musí být platný.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2764,6 +2954,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>E-mail je vyžadován.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>E-mail musí být platný.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Uživatelské jméno je vyžadováno.</target>
@@ -2785,41 +2989,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Uživatelské jméno musí mít délku minimálně 3 znaky.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Uživatelské jméno nemůže být delší než 20 znaků.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Uživatelské jméno by mělo obsahovat pouze malá písmena a číslice.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Email je vyžadován.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>Email musí být platný.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Heslo musí mít délku minimálně 6 znaků.</target>
@@ -2869,20 +3038,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Zobrazované jméno musí mít delku minimálně 3 znaky.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Zobrazované jméno nesmí být delší než 120 znaků.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>Popis musí mít délku minimálně 3 znaky.</target>
@@ -2904,13 +3059,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Důvod nahlášení nesmí být delší než 300 znaků.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>Text pro podporu musí mít délku minimálně 3 znaky.</target>
@@ -3506,6 +3654,34 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f9b4f2d8146c789cd40314f640ec4e88efbaf681">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users banned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> uživatelů zablokováno.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3ab99e62550869aebc85661fca2faf46785263dd">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> banned.</source>
+        <target>Uživatel <x id="INTERPOLATION" equiv-text="{{username}}"/> zablokován.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="faafee0c03ad25c8a43aa91bd5d98185b67ff734">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{username}}"/>?</source>
+        <target>Opravdu chcete odblokovat uživatele <x id="INTERPOLATION" equiv-text="{{username}}"/>?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="925ba9946b7b256a586f0fcbe3e04fa7a0dee7bd">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> unbanned.</source>
+        <target>Uživatel <x id="INTERPOLATION" equiv-text="{{username}}"/> odblokován.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="28220fae6799ab98ef6b41af449aa9680082357a">
         <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> deleted.</source>
         <target>Uživatel <x id="INTERPOLATION" equiv-text="{{username}}"/> odstraněn.</target>
@@ -3541,16 +3717,51 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
+        <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target>Odebíráte kanál <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
         <source>Subscribed</source>
-        <target>Odebírám</target>
+        <target>Odebíráte</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3e7735fa326fcdc9e1188b6d9ff4b4329312fc26">
+        <source>Unsubscribed from <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target>Již neodebíráte kanál <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Odběr zrušen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="15be15cbdc6e960f57e801f457c19165ab39632b">
+        <source>Anyone can see this video</source>
+        <target>Kdokoliv může vidět toto video</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>Nyní jste přihlášen/a jako <x id="INTERPOLATION" equiv-text="{{username}}"/>!</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24840228f2826b66252cfcaab9820b1c7e0da264">
         <source>But associated data (tags, description...) will be lost, are you sure you want to leave this page?</source>
-        <target>Přiřazená data (tagy, popis...) budou ztraceny, opravdu chcete opustit tuto stránku?</target>
+        <target>Ovšem přidružená data (štítky, popis...) budou ztraceny, opravdu chcete opustit tuto stránku?</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3562,13 +3773,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Info</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Nahrávání zrušeno</target>
@@ -3611,6 +3815,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="0e65067fdcc9d8725a41896cb1e229d1415a45f6">
+        <source>Like the video</source>
+        <target>To se mi líbí</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1a999e06e1aca0a70cd7d0e3e5c2c63d0e1885c8">
+        <source>Dislike the video</source>
+        <target>To se mi nelíbí</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="f1abd89c9280323209e939fa9c30f6e5cda20c95">
         <source>Do you really want to delete this video?</source>
         <target>Opravdu chcete odstranit toto video?</target>
@@ -3620,7 +3838,7 @@ When you will upload a video in this channel, the video support field will be au
       </trans-unit>
       <trans-unit id="d5a4811e15319ad9354e1b62e9ca0131192b489e">
         <source><x id="INTERPOLATION" equiv-text="{{likesNumber}}"/> likes / <x id="INTERPOLATION_1" equiv-text="{{dislikesNumber}}"/> dislikes</source>
-        <target><x id="INTERPOLATION" equiv-text="{{likesNumber}}"/> se to líbí / <x id="INTERPOLATION_1" equiv-text="{{dislikesNumber}}"/> se to nelíbí</target>
+        <target><x id="INTERPOLATION" equiv-text="{{likesNumber}}"/> se to líbí / <x id="INTERPOLATION_1" equiv-text="{{dislikesNumber}}"/> lidem se to nelíbí</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3639,5 +3857,12 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1b157e15c434469d91e56d027b78bf69c9983165">
+        <source>Videos from your subscriptions</source>
+        <target>Videa od vašich odběrů</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
     </body>
   </file></xliff>
\ No newline at end of file
index 46e6229084282c1683a37c716d5ce5451352e514..39610dfdb8a05c22522a97d97e55088055acd2c7 100644 (file)
         <source>Password</source>
         <target>Passwort</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Anmelden</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Mir eine E-Mail schicken, um mein Passwort zurückzusetzen</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
@@ -600,11 +600,18 @@ Konto erstellen</target>
           <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7fe213724c4c0a4112c40c673884acb98a0a3b92">
+        <source>I am at least 16 years old and agree to the &lt;a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'&gt;Terms&lt;/a&gt; of this instance</source>
+        <target>Ich bin mindestens 16 Jahre alt und stimme den &lt;a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'&gt;Bestimmungen&lt;/a&gt; dieser Instanz zu</target>
+        <context-group name="null">
+          <context context-type="linenumber">55</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="717a5e3574fec754fbeb348c2d5561c4d81facc4">
         <source>Signup</source>
         <target>Registrieren</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
@@ -632,6 +639,19 @@ Konto erstellen</target>
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7c603b9ed878097782e2b8908f662e2344b46061">
+        <source>
+          Filters
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </source>
+        <target>
+          Filter
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e2dbf0426cbb0b573faf49dffeb7d5bdf16eda5d">
         <source>
     No results found
@@ -661,7 +681,7 @@ Konto erstellen</target>
         <source>Change the language</source>
         <target>Sprache wechseln</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
@@ -672,7 +692,7 @@ Konto erstellen</target>
              Mein öffentliches Profil
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
@@ -683,7 +703,7 @@ Konto erstellen</target>
               Mein Konto
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
@@ -694,7 +714,7 @@ Konto erstellen</target>
               Meine Videos
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
@@ -705,14 +725,14 @@ Konto erstellen</target>
               Abmelden
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Konto erstellen</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
@@ -726,49 +746,49 @@ Konto erstellen</target>
         <source>Subscriptions</source>
         <target>Abos</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Übersicht</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Beliebt</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Kürzlich hinzugefügt</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Lokal</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Mehr</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administration</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
@@ -782,14 +802,14 @@ Konto erstellen</target>
         <source>Show keyboard shortcuts</source>
         <target>Zeige Tastatur-Kürzel</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>Dunkle Oberfläche umschalten</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
@@ -894,14 +914,14 @@ Konto erstellen</target>
         <source>Display unlisted and private videos</source>
         <target>Private und nicht gelisteten Videos aufzeigen</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Keine Ergebnisse.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
@@ -959,15 +979,22 @@ Konto erstellen</target>
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  Über die Instanz <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Abbrechen
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Abschicken</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
@@ -981,47 +1008,14 @@ Konto erstellen</target>
         <source>Terms</source>
         <target>Bestimmungen</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Benutzerregistrierung ist möglich und</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      für die Videos ihrer Nutzer stellt diese Instanz einen Speicherplatz von <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> zur Verfügung.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      für die Videos ihrer Nutzer stellt diese Instanz unbegrenzten Speicherplatz zur Verfügung.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Die Benutzerregistrierung ist zur Zeit nicht erlaubt.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
@@ -1037,7 +1031,7 @@ Konto erstellen</target>
       </trans-unit>
       <trans-unit id="bd29138e1e17572596ce8f2fe61bcea6ac5fb0bf">
         <source>PeerTube is a federated (ActivityPub) video streaming platform using P2P (WebTorrent) directly in the web browser.</source>
-        <target>PeerTube ist eine föderierte Videostreamingplattform basierend auf dem ActivityPub-Protokoll, die mit WebTorrent P2P-Technologie direkt im Browser verwendet.</target>
+        <target>PeerTube ist eine föderierte Videostreamingplattform basierend auf dem ActivityPub-Protokoll, die WebTorrent P2P-Technologie direkt im Browser verwendet.</target>
         <context-group name="null">
           <context context-type="linenumber">6</context>
         </context-group>
@@ -1242,6 +1236,19 @@ Konto erstellen</target>
           <context context-type="linenumber">83</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b1372cb61ca791a0f7f95bf31c86c97df142adc4">
+        <source>
+    PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released.
+    In the meantime, we want to test different ideas related to this issue:
+  </source>
+        <target>
+    PeerTube ist in der frühen Entwicklungsphase und es ist geplant, die besten Gegenmaßnahmen in der nächsten stabilen Version zu implementieren.
+    Bis dahin wollen wir verschiedene Ideen für dieses Problem testen:
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">85</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="d32608aba08c6bb3cc4e4e8ec6223e5f4e78ca19">
         <source>Set a limit to the number of peers sent by the tracker</source>
         <target>Die Zahl der Peers, die durch einen Tracker gesendet wird, begrenzen.</target>
@@ -1278,27 +1285,37 @@ Konto erstellen</target>
         </context-group>
       </trans-unit>
       <trans-unit id="bd2edf99dd6562385ccec19a7ab2d1898e626605">
-        <source>Banned</source><target>Banned</target><context-group name="null">
+        <source>Banned</source>
+        <target>Gebannt</target>
+        <context-group name="null">
           <context context-type="linenumber">12</context>
         </context-group>
       </trans-unit>
       <trans-unit id="62a557fcfdbd25a31d1a0332294f94a466fee809">
-        <source>Muted</source><target>Muted</target><context-group name="null">
+        <source>Muted</source>
+        <target>Stummgeschaltet</target>
+        <context-group name="null">
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="48bbf6dbdb22e0ef4bd257eae2ab356f2ea66c89">
-        <source>Muted by your instance</source><target>Muted by your instance</target><context-group name="null">
+        <source>Muted by your instance</source>
+        <target>Von deiner Instanz stummgeschaltet</target>
+        <context-group name="null">
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="44bd08a7ec1e407356620967d65d8fe2d8639d0a">
-        <source>Instance muted</source><target>Instance muted</target><context-group name="null">
+        <source>Instance muted</source>
+        <target>Instanz stummgeschaltet</target>
+        <context-group name="null">
           <context context-type="linenumber">15</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1a6443bb7ed01046dd83cf78806f795f1204ffa1">
-        <source>Instance muted by your instance</source><target>Instance muted by your instance</target><context-group name="null">
+        <source>Instance muted by your instance</source>
+        <target>Diese Instanz wurde deiner Instanz stummgeschaltet</target>
+        <context-group name="null">
           <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
@@ -1355,49 +1372,49 @@ Konto erstellen</target>
         <source>Short description</source>
         <target>Kurze Beschreibung</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Standardpfad (Client)</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Video-Übersicht</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Beliebte Videos</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Kürzlich hinzugefügte Videos</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Lokale Videos</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Verhalten bei Videos mit anstößigen Inhalten</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
@@ -1432,23 +1449,44 @@ Konto erstellen</target>
         <source>Signup enabled</source>
         <target>Registrierung aktiviert</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Registrierung erfordert eine Bestätigung der E-Mail-Adresse</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Obergrenze für Registrierungen</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Benutzer</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Standardkontingent für die Videos eines Nutzers</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Tägliches Obergrenze eines Nutzers beim Hochladen</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importieren</target>
@@ -1460,49 +1498,28 @@ Konto erstellen</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>Video durch HTTP URL (z.b. YouTube©) import erlaubt.</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Video-Import über eine Torrent-Datei oder einen Magnet-Link aktiviert</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administrator</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Admin E-Mail</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Benutzer</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Standardkontingent für die Videos eines Nutzers</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Tägliches Obergrenze eines Nutzers beim Hochladen</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
@@ -1523,21 +1540,21 @@ Konto erstellen</target>
         <source>Your Twitter username</source>
         <target>Dein Twitter-Benutzername</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Zeigt den Twitter-Account für die Webseite, auf der der Inhalt veröffentlicht wurde.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instanz von Twitter vertraut</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
@@ -1551,35 +1568,35 @@ Konto erstellen</target>
         <source>Transcoding</source>
         <target>Transkodierung</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transkodierung an</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Wenn du die Transkodierung abschaltest, werden viele Videos nicht laufen!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Transcodierungsthreads</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Eingestellte Auflösung: <x id="INTERPOLATION" equiv-text="{{resolution}}"/></target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
@@ -1594,82 +1611,47 @@ Konto erstellen</target>
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Einige Dateien (Vorschau, Untertitel) werden nicht verteilt gespeichert. Wir laden sie direkt von der Ursprungsinstanz und speichern sie zwischen.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Cachegröße der Vorschau</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Cachegröße der Untertitel</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalisierung</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Füge dein JavaScript ein.&lt;br /&gt;Beispiel: &lt;pre&gt;console.log('Meine Instanz ist großartig');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                Schreibe direkt CSS-Code. Beispiel:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Stelle &lt;em&gt;#custom-css&lt;/em&gt; voran, um den Stil zu überschreiben. Beispiel:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
@@ -1678,21 +1660,21 @@ Konto erstellen</target>
         <source>Advanced configuration</source>
         <target>Erweiterte Einstellungen</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Einstellungen aktualisieren</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Die Einstellungen sind anscheinend ungültig. Bitte suche nach potentiellen Fehlern in den verschiedenen Reitern.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
@@ -1963,7 +1945,9 @@ Konto erstellen</target>
         </context-group>
       </trans-unit>
       <trans-unit id="adba7c8b43e42581460fbe5d08b5cb5ab60eba4b">
-        <source>(banned)</source><target>(banned)</target><context-group name="null">
+        <source>(banned)</source>
+        <target>(gebannt)</target>
+        <context-group name="null">
           <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
@@ -1974,11 +1958,25 @@ Konto erstellen</target>
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>Die E-Mail-Adresse des Nutzers muss vor dem Einloggen bestätigt werden</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>Die E-Mail-Addresse des Nutzers wurde bestätigt / Nutzer kann ohne E-Mail-Bestätigung einloggen</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
         <source>Ban reason:</source>
         <target>Grund für die Sperrung:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
@@ -2045,7 +2043,7 @@ Konto erstellen</target>
         <source>Actions</source>
         <target>Aktionen</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
@@ -2080,14 +2078,14 @@ Konto erstellen</target>
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Datum <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Grund für die Sperre:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
@@ -2112,12 +2110,16 @@ Konto erstellen</target>
         </context-group>
       </trans-unit>
       <trans-unit id="b1ff109b26ae8f08650415454b9098c43eba2e2c">
-        <source>Muted accounts</source><target>Muted accounts</target><context-group name="null">
+        <source>Muted accounts</source>
+        <target>Stummgeschaltete Accounts</target>
+        <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bd0611346af048015e0a1275091ef68ce98832d2">
-        <source>Muted servers</source><target>Muted servers</target><context-group name="null">
+        <source>Muted servers</source>
+        <target>Stummgeschaltete Server</target>
+        <context-group name="null">
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
@@ -2129,109 +2131,59 @@ Konto erstellen</target>
         </context-group>
       </trans-unit>
       <trans-unit id="079e99cce11c87b142e80fdd14dae98a61012fc4">
-        <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source><target>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target><context-group name="null">
+        <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Stummgeschaltet am <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1f689fada9748a830117f5b429a88ef8629082a8">
-        <source>Unmute</source><target>Unmute</target><context-group name="null">
+        <source>Unmute</source>
+        <target>Stummschalten aufheben</target>
+        <context-group name="null">
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Meine Einstellungen</target>
+      <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
+        <source>Video quota:</source>
+        <target>Videokontingent:</target>
         <context-group name="null">
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Meine Bibliothek</target>
+      <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
+        <source>Profile</source>
+        <target>Profil</target>
         <context-group name="null">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Meine Kanäle</target>
+      <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
+        <source>Video settings</source>
+        <target>Videoeinstellungen</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Meine Videos</target>
+      <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
+        <source>Danger zone</source>
+        <target>Gefahrenzone</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Meine Abos</target>
+      <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
+        <source>Change ownership</source>
+        <target>Besitzer ändern</target>
         <context-group name="null">
-          <context context-type="linenumber">16</context>
+          <context context-type="linenumber">46</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Meine Importe</target>
+      <trans-unit id="046c4fa30411e6b1aa46dc51bf82d07b1adf14d4">
+        <source>Select the next owner</source>
+        <target>Wähle den nächsten Besitzer</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source><target>Muted instances</target><context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Besitzer ändern</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
-        <source>Video quota:</source>
-        <target>Videokontingent:</target>
-        <context-group name="null">
-          <context context-type="linenumber">4</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
-        <source>Profile</source>
-        <target>Profil</target>
-        <context-group name="null">
-          <context context-type="linenumber">8</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
-        <source>Video settings</source>
-        <target>Videoeinstellungen</target>
-        <context-group name="null">
-          <context context-type="linenumber">15</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
-        <source>Danger zone</source>
-        <target>Gefahrenzone</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
-        <source>Change ownership</source>
-        <target>Besitzer ändern</target>
-        <context-group name="null">
-          <context context-type="linenumber">46</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="046c4fa30411e6b1aa46dc51bf82d07b1adf14d4">
-        <source>Select the next owner</source>
-        <target>Wähle den nächsten Besitzer</target>
-        <context-group name="null">
-          <context context-type="linenumber">9</context>
+          <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a5433ae2324496bea9537caa5e8a2719d8e958d8">
@@ -2245,13 +2197,6 @@ Konto erstellen</target>
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Abschicken</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> Aufrufe</target>
@@ -2411,6 +2356,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Stummgeschaltete Instanzen</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Passwort ändern</target>
@@ -2616,6 +2568,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>Entschuldigung, etwas ist schiefgegangen</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2652,14 +2611,14 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         <source>Publish will be available when upload is finished</source>
         <target>Veröffentlichung ist möglich, sobald das Hochladen abgeschlossen ist</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Veröffentlichen</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2842,14 +2801,14 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         <source>Wait transcoding before publishing the video</source>
         <target>Transkodieren abwarten, bevor das Video veröffentlicht wird</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Wenn du dich entschließt, das Transkodieren nicht abzuwarten, kann das Video unabspielbar sein, bis das Transkodieren beendet ist.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2863,49 +2822,49 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         <source>Add another caption</source>
         <target>Weitere Untertitel hinzufügen</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Siehe in der Untertiteldatei</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>Fast hochgeladen       ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
         <source>Will be created on update</source>
         <target>Wird bei einer Aktualisierung erstellt</target>
         <context-group name="null">
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Erstellen abbrechen</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
         <source>Will be deleted on update</source>
         <target>Wird bei einer Aktualisierung gelöscht</target>
         <context-group name="null">
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Löschen abbrechen</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
@@ -2916,28 +2875,28 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
             Bis auf Weiteres keine Untertitel.
           </target>
         <context-group name="null">
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Untertitel</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Miniaturansicht hochladen</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Vorschau hochladen</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2951,14 +2910,14 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Ein kurzer Text, der anderen erklärt, wie sie dich unterstützen können.</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Erweiterte Einstellungen</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3025,17 +2984,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Abbrechen
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Teilen</target>
@@ -3422,13 +3370,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Server-Fehler.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Keine Beschreibung</target>
@@ -3450,13 +3391,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Fehler</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3499,13 +3433,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Erfolg</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Einstellungen aktualisiert.</target>
@@ -3668,7 +3595,16 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         </context-group>
       </trans-unit>
       <trans-unit id="53cc0f4a4566c4139c65f93b5dce2fe8302e78da">
-        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source><target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</target><context-group name="null">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ist von deiner Instanz nicht mehr stummgeschatet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="468b52e3c04fb9a3d8c8213555dfcad0cbcae330">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</source>
+        <target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> ist von deiner Instanz nicht mehr stummgeschaltet.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -3679,6 +3615,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="586bee8c27a761611eb05661524cc7ca944b5978">
+        <source>Delete this report</source>
+        <target>Missbrauchsmeldung löschen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="cf3b28ba29a907b334ab0e6dccd080a60ba23321">
         <source>Update moderation comment</source>
         <target>Moderationskommentar aktualisieren</target>
@@ -3700,6 +3643,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="73b70e37cddaa6494d8a666b6cba90dc80595599">
+        <source>Do you really want to delete this abuse report?</source>
+        <target>Wollen Sie wirklich diese Missbrauchsmeldung löschen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="6a7938b8780c27540ea70cc0f8f4d928c8916cf9">
         <source>Abuse deleted.</source>
         <target>Missbrauchsmeldung gelöscht.</target>
@@ -3749,6 +3699,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>E-Mail als bestätigt setzen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
         <source>You cannot ban root.</source>
         <target>Du kannst root nicht sperren.</target>
@@ -3756,6 +3713,20 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="98119091712a8ca72905e3b4c1cf60649af7565e">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{num}}"/> users?</source>
+        <target>Willst du wirklich den Bann von <x id="INTERPOLATION" equiv-text="{{num}}"/> Benutzern aufheben?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6121be086a51c4c73bbdd8aebdddd9744c8f1ffd">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users unbanned.</source>
+        <target>Bann von <x id="INTERPOLATION" equiv-text="{{num}}"/> Benutzern aufgehoben.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="911fc197949e47aa5f0541627bc319f59edd9d11">
         <source>You cannot delete root.</source>
         <target>Du kannst die Wurzel nicht löschen.</target>
@@ -3764,7 +3735,37 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         </context-group>
       </trans-unit>
       <trans-unit id="9de914fe915cc730efc57e81c987188a24d3ac51">
-        <source>If you remove these users, you will not be able to create others with the same username!</source><target>If you remove these users, you will not be able to create others with the same username!</target><context-group name="null">
+        <source>If you remove these users, you will not be able to create others with the same username!</source>
+        <target>Wenn du diesen Benutzer löschst, kannst du keine neuen Benutzer mit gleichem Benutzernamen einrichten !</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b708d332e3f89b24745e749fa530210f0bdea329">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> Benutzer gelöscht.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target>E-Mail von <x id="INTERPOLATION" equiv-text="{{num}}"/> Benutzern als bestätigt markiert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
+        <target>Stummschaltung von Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> aufgehoben.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c6af80b42938d4a49e6f6c4f60ce26228916994c">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</source>
+        <target>Stummschaltung von Instanz <x id="INTERPOLATION" equiv-text="{{host}}"/> aufgehoben.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -3845,6 +3846,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f359f6adf6cccca7770019f947ed594169ee7d47">
+        <source>This name already exists on this instance.</source>
+        <target>Dieser Name existiert bereits auf dieser Instanz.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="70a67e04629f6d412db0a12d51820b480788d795">
         <source>Create</source>
         <target>Erstellen</target>
@@ -3859,23 +3867,16 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Willst du wirklich <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> löschen? Alle hochgeladenen Videos des Kanals werden dann ebenfalls unwiderruflich gelöscht.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Bitte gib zur Bestätigung den Namen des Videokanals ein</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Videokanal <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> entfernt.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Videokanal <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> entfernt.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Meine Videos</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3950,16 +3951,44 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Kanäle</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Meine Bibliothek</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Meine Kanäle</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Meine Abos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Verschiedenes</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Video-Importe</target>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Besitzer ändern</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Meine Einstellungen</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3979,7 +4008,23 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
         </context-group>
       </trans-unit>
       <trans-unit id="ff6becacbce7fc0943b0af0df4dd67e5e11bf598">
-        <source>Subscribe to the account</source><target>Subscribe to the account</target><context-group name="null">
+        <source>Subscribe to the account</source>
+        <target>Diesen Account abonnieren</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c95cc372311830f936b39f73c5d6d20c0b16013">
+        <source>Focus the search bar</source>
+        <target>Die Suchleiste fokussieren</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b19ee83cbd2b735fd081b9aa483a890578019099">
+        <source>Toggle the left menu</source>
+        <target>Linkes Menü umschalten</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -4018,6 +4063,20 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="0ed7b40c11da9d4565af9c041df20c15bc6be97e">
+        <source>Toggle Dark theme</source>
+        <target>Dunkles Theme umschalten</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="badd4b24618ccc8a34620acb9053fc654b9612b2">
+        <source>Go to my subscriptions</source>
+        <target>Gehe zu meinen Abos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b7184b5a236618e8edd747529869c392ab6dace1">
         <source>Go to my videos</source>
         <target>zur meine Videos gehen</target>
@@ -4025,6 +4084,20 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="acf985bd42886b9b3030b5f68f0e8417c39b40a7">
+        <source>Go to my imports</source>
+        <target>Gehe zu meinen Importen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cfe3c51f0ae9385dc2ce6df740d87e5514aa9390">
+        <source>Go to my channels</source>
+        <target>Gehe zu meinen Kanälen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="edeaa933b09690523e46977e11064e9c655d77d7">
         <source>Cannot retrieve OAuth Client credentials: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
 </source>
@@ -4041,6 +4114,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Fehler</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Bitte verbinde dich erneut.</target>
@@ -4062,6 +4142,20 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Infos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Erfolg</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Falscher Benutzername oder falsches Passwort.</target>
@@ -4279,6 +4373,20 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>Bitte gib eine E-Mail-Adresse ein.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>Bitte gebe eine gültige E-Mail-Adresse ein.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Bitte gib einen Benutzernamen ein.</target>
@@ -4300,41 +4408,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Der Benutzername muss mindestens 3 Zeichen lang sein.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Der Benutzername darf nicht länger als 20 Zeichen lang sein.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Der Benutzername sollte nur kleine alphanumerische Zeichen enthalten.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Bitte gib eine E-Mail-Adresse ein.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>Bitte gebe eine gültige E-Mail-Adresse ein.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Das Passwort muss mindestens 6 Zeichen lang sein.</target>
@@ -4398,23 +4471,16 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Der Anzeigename muss mindestens 3 Zeichen lang sein.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Der Anzeigename darf nicht länger als 120 Zeichen lang sein.</target>
+      <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
+        <source>Description must be at least 3 characters long.</source>
+        <target>Die Beschreibung muss mindestens 3 Zeichen umfassen.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
-        <source>Description must be at least 3 characters long.</source>
-        <target>Die Beschreibung muss mindestens 3 Zeichen umfassen.</target>
+      <trans-unit id="a4179e366d4aa335f1ddd0a13e9109c71a9338d0">
+        <source>Description cannot be more than 1000 characters long.</source>
+        <target>Beschreibung kann nicht länger als 1000 Zeichen sein.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4454,13 +4520,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Der Grund für die Meldung darf nicht mehr als 300 Zeichen umfassen.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Der Moderationskommentar muss angegeben werden.</target>
@@ -4475,13 +4534,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Der Moderationskommentar darf nicht mehr als 300 Zeichen umfassen.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>Der Kanal muss angegeben werden.</target>
@@ -4524,37 +4576,30 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="541087322c34e8b26954fd67ff4fc80d1a6c1b33">
-        <source>Name is required.</source>
-        <target>Der Name muss angegeben werden.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Der Name muss mindestens 3 Zeichen umfassen.</target>
+      <trans-unit id="c8465c3773699dd075e0147e264d2e232f605803">
+        <source>You can only transfer ownership to a local account</source>
+        <target>Du kannst den Besitz nur auf einen lokalen Account übertragen</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Der Name darf nicht mehr als 20 Zeichen umfassen.</target>
+      <trans-unit id="541087322c34e8b26954fd67ff4fc80d1a6c1b33">
+        <source>Name is required.</source>
+        <target>Der Name muss angegeben werden.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Der Name sollte nur kleine alphanumerische Zeichen enthalten.</target>
+      <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
+        <source>Support text must be at least 3 characters long.</source>
+        <target>Die Beschreibung zur Unterstützung muss mindestens 3 Zeichen umfassen.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
-        <source>Support text must be at least 3 characters long.</source>
-        <target>Die Beschreibung zur Unterstützung muss mindestens 3 Zeichen umfassen.</target>
+      <trans-unit id="15ec53d9ee65cb930c5f5d10ae2e8dd3fd44fc85">
+        <source>Support text cannot be more than 1000 characters long.</source>
+        <target>Die Beschreibung zur Unterstützung kann nicht mehr als 1000 Zeichen lang sein.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4650,6 +4695,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f17de746af56840511cae11559539b6d8b6955ad">
+        <source>Video support cannot be more than 1000 characters long.</source>
+        <target>Die Beschreibung zur Unterstützung darf nicht mehr als 1000 Zeichen umfassen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="453413bf387dea681958871319bab489dd5e6ec0">
         <source>A date is required to schedule video update.</source>
         <target>Bitte gib ein ein Datum für die geplante Veröffentlichung ein.</target>
@@ -5175,6 +5227,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f9b4f2d8146c789cd40314f640ec4e88efbaf681">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users banned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> Benutzer gebannt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="3ab99e62550869aebc85661fca2faf46785263dd">
         <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> banned.</source>
         <target>Benutzer <x id="INTERPOLATION" equiv-text="{{username}}"/> gesperrt.</target>
@@ -5210,6 +5269,111 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>E-Mail des Benutzers <x id="INTERPOLATION" equiv-text="{{username}}"/> als bestätigt markiert</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> stummgeschaltet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="086eda792aeb1b0d131d633b50fdd1792f5f24c6">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</source>
+        <target>Instanz <x id="INTERPOLATION" equiv-text="{{host}}"/> stummgeschaltet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb72d6d1219e89d182e9fd09d853d83baf8d6499">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> von der Instanz stummgeschaltet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8686834bc4afe42c1991c6c18f0bce174a0e17a6">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> nicht mehr von der Instanz stummgeschaltet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="35d3509161861a610b0895bf084c781e56ba2830">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</source>
+        <target>Instanz <x id="INTERPOLATION" equiv-text="{{host}}"/> von der Instanz stummgeschaltet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="978aeec5613fa97e8a5336d3599cebb23ee5a90f">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</source>
+        <target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> nicht mehr von der Instanz stummgeschaltet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a09bf8724e7659fbb5ec33647529cdef7614bdc">
+        <source>Mute this account</source>
+        <target>Dieses Konto stummschalten</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d666ca3261aef72b2ddcd649d7b32af488f59952">
+        <source>Unmute this account</source>
+        <target>Stummschaltung für dieses Konto aufheben</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e17218983b1de76e5a920b04e1c2ecbdb6e3e06d">
+        <source>Mute the instance</source>
+        <target>Diese Instanz stummschalten</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a23514d8aca2f8633622dda0e86b399dc576a2b9">
+        <source>Unmute the instance</source>
+        <target>Stummschaltung für diese Instanz aufheben</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4e4107055b44eee44b6954c41120de1cb4d46432">
+        <source>Mute this account by your instance</source>
+        <target>Dieses Konto durch deine Instanz stummschalten lassen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a51c59cb5ecb7004a6a8ddd2855b5c52266ad957">
+        <source>Unmute this account by your instance</source>
+        <target>Stummschaltung dieses Kontos durch deine Instanz aufheben</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="588073e831cec240d6bb0db0b133e45dab69f178">
+        <source>Mute the instance by your instance</source>
+        <target>Diese Instanz durch deine Instanz stummschalten lassen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="676221cdabd4805901343976988c028dbf71b20a">
+        <source>Unmute the instance by your instance</source>
+        <target>Stummschaltung dieser Instanz durch deine Instanz aufheben</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0c0f5bbcd2386018ec057877f9d3c5c2c9880cac">
         <source>Request is too large for the server. Please contact you administrator if you want to increase the limit size.</source>
         <target>Die Anfrage ist zu groß. Bitte kontaktiere den Administrator, um die Obergrenze für die Größe zu erhöhen.</target>
@@ -5238,13 +5402,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Abonniert</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> abonniert</target>
@@ -5252,9 +5409,9 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Abo beendet</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Abonniert</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5266,6 +5423,13 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Abo beendet</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>Moderator</target>
@@ -5294,6 +5458,20 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>Wilkommen! Bitte überprüfe deine Mails um dein Benutzerkonto zu bestätigen und die Registrierung abzuschließen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>Du bist jetzt eingeloggt als <x id="INTERPOLATION" equiv-text="{{username}}"/>!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>Zu importierendes Video wurde aktualisiert.</target>
@@ -5322,13 +5500,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Infos</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Hochladen abgebrochen</target>
@@ -5336,13 +5507,6 @@ Wenn du ein Video in diesen Kanal hochlädst, wird das entsprechende Feld automa
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Leider kann PeerTube keine Videos verarbeiten, die größer als 8 GB sind.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>Dein Videokontingent wird mit diesem Video überschritten (Videogröße: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, benutzt: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, Kontingent: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index faa52fbdb2eb214a3ec73e0568b232ee368f2e20..0d54b5a5d35c9a23ffb5a20257a0c4d6e79ea74b 100644 (file)
         <source>Password</source>
         <target>Pasvorto</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Saluti</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Sendu al mi retleteron por restarigi mian pasvorton</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Registriĝo</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
         <source>Change the language</source>
         <target>Ŝanĝi la lingvon</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Miaj filmoj
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Adiaŭi
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Krei konton</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Abonoj</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Superrigardo</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Furoraj</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Freŝe aldonitaj</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Lokaj</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Pli</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administrado</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>No results.</source>
         <target>Nenio troviĝis.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ff78f059449d44322f627d0f66df07abe476962b">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
-        <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
-  Pri la nodo « <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> »
-</target>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Sendi</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Kondiĉoj</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Registrado de uzantoj estas permesata kaj</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      ĉi tiu nodo donas bazan datumlimon de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> da spaco por filmoj de siaj uzantoj.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      ĉi tiu nodo donas senliman spacon por filmoj de siaj uzantoj.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Registrado de novaj uzantoj nun ne estas permesata.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Mallonga priskribo</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Norma klienta vojo</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Filma superrigardo</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Filmoj furoraj</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Filmoj freŝe aldonitaj</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Filmoj lokaj</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Politiko pri filmoj kun konsterna enhavo</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Registriĝoj ŝaltitaj</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Registriĝo bezonas kontrolon de retpoŝtadreso</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limo de registriĝoj</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Uzantoj</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Norma datumlimo por filmoj de uzantoj</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Enporti</target>
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Enporto de filmoj per torenta dosiero aŭ magneta ligilo ŝaltita</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administranto</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Retpoŝtadreso de administranto</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Uzantoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Norma datumlimo por filmoj de uzantoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Via Tvitera salutnomo</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indikas konton de Twitter por la retejo aŭ platformo, sur kiu la afero publikiĝis.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Nodo permesata de Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Transkodado</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transkodado ŝaltita</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Se vi malŝaltos transkodadon, multaj filmoj de viaj uzantoj eble ne funkcios!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Fadenoj por transkodado</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Distingo <x id="INTERPOLATION" equiv-text="{{resolution}}"/> ŝaltita</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Iuj dosieroj ne estas federataj (antaŭrigardoj, transskriboj). Ni prenas kaj kaŝmemoras ilin rekte el la fonta nodo.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Grando de antaŭrigarda kaŝmemoro</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Grandeco de kaŝmemoro por filmaj transskriboj.</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Adaptoj</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>Ĝavoskripto</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Skribu rekte Ĝavoskriptan kodon.&lt;br /&gt;Ekzemple: &lt;pre&gt;console.log('mia nodo bonegas');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Specialaj agordoj</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Efektivigi agordojn</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Ŝajnas, ke la agordo estas nevalida. Bonvolu serĉi eblajn erarojn en la langetoj.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Miaj agordoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Miaj kanaloj</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Miaj filmoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Miaj abonoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Miaj enportoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Datumlimo por filmoj:</target>
         <source>Profile</source>
         <target>Profilo</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Filmaj agordoj:</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Danĝera areo</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Sendi</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
@@ -1801,14 +1722,14 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
         <source>Publish will be available when upload is finished</source>
         <target>Eldono eblos post fino de alŝuto</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Eldoni</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -1952,14 +1873,14 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
         <source>Wait transcoding before publishing the video</source>
         <target>Atendi transkodadon antaŭ publikigi la filmon</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Se vi decidos ne atendi finon de transkodado antaŭ publikigo, ĝi povus esti neludebla ĝis la transkodo finiĝos.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -1973,49 +1894,49 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
         <source>Add another caption</source>
         <target>Aldoni alian transskribon</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Rigardi la dosieron kun subtekstoj</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Nuligi kreon</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Nuligi forigon</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Transskriboj</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Alŝuti miniaturon</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Alŝuti antaŭrigardon</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2029,14 +1950,14 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Mallonga teksto por sciigi homojn, kiel ili povas subteni vin.</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Specialaj agordoj</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -2319,13 +2240,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Eraro ricevante prion de la servilo</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Neniu priskribo</target>
@@ -2347,20 +2261,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Eraro</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Sukceso</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Agordo ĝisdatigita</target>
@@ -2550,23 +2450,16 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Ĉu vi certe volas forigi kanalon <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Tiel vi ankaŭ forigos ĉiujn filmojn alŝutitajn al tiu ĉi kanalo.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Bonvolu tajpi nomon de la filma kanalo por konfirmi</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Filma kanalo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> forigita.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Filma kanalo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> forigita.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Miaj filmoj</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2627,6 +2520,27 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Miaj kanaloj</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Miaj abonoj</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Miaj agordoj</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ccbf0490fb6b60d21e03bb2c9003df0ce1a58752">
         <source>Unable to find user id or verification string.</source>
         <target>Ne povas trovi identigilon aŭ kontrolan ĉenon de uzanto</target>
@@ -2650,6 +2564,13 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Eraro</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Vi devas rekonektiĝi</target>
@@ -2664,6 +2585,20 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Informoj</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Sukceso</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7701e3762dc4a2b2e302c24f17820bc8dd7cacc1">
         <source>An email with the reset password instructions will be sent to <x id="INTERPOLATION" equiv-text="{{email}}"/>.</source>
         <target>Retletero kun instrukcioj por restarigi la pasvorton sendiĝos al <x id="INTERPOLATION" equiv-text="{{email}}"/>.</target>
@@ -2860,6 +2795,20 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>Necesas retpoŝtadreso.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>Retpoŝtadreso devas esti valida.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Necesas salutnomo.</target>
@@ -2881,41 +2830,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Salutnomo devas havi almenaŭ 3 signojn.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Salutnomo ne povas havi pli ol 20 signojn.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Salutnomo havu nur ciferojn kaj minusklajn literojn.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Necesas retpoŝtadreso.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>Retpoŝtadreso devas esti valida.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Pasvorto devas havi almenaŭ 6 signojn.</target>
@@ -2965,20 +2879,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Prezenta nomo devas havi almenaŭ 3 signojn.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Prezenta nomo ne povas havi pli ol 120 signojn.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>Priskribo havu almenaŭ 3 signojn.</target>
@@ -3007,13 +2907,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Kialo de raporto maldevas havi pli ol 300 signojn.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c9eadf8830b3bc09bd444d739af86414eed9bd9e">
         <source>Video caption language is required.</source>
         <target>Necesas lingvo de filma transskribo.</target>
@@ -3686,13 +3579,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Informoj</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Alŝuto nuligita.</target>
@@ -3700,13 +3586,6 @@ Kiam vi alŝutos filmon al tiu ĉi kanalo, la kampo pri subteno memfare enhavos
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Pardonu nin, sed PeerTube ne kapablas trakti filmojn super 8GB</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="972fc644f847cf84e4732ec012915c4cdaf865ce">
         <source>Video published.</source>
         <target>Filmo publikigita.</target>
index 947c9a91d86758efa8c1d7463172e1c8a66ce0a0..828de2a57c8f5393c17ae287ea748726aa1a8eeb 100644 (file)
@@ -5,7 +5,7 @@
     <body>
       <trans-unit id="ngb.alert.close">
         <source>Close</source>
-        <target>Cerrar</target>
+        <target>tppCerrar</target>
         <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f3e63578c50546530daf6050d2ba6f8226040f2c">
+        <source>You don't have notifications.</source>
+        <target>No tiene notificaciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f79d1d9ecaab3deb3d44e23017f8283a04d2a0f3">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/> published a <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>new video<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/> ha publicado un <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>nuevo vídeo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="04f2cb4c88c17d5f3e5ce969479b4eba9db114cb">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been unblacklisted
+      </source>
+        <target>
+        Su vídeo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> ha sido desbloqueado
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="65514a0efdae3b173130166416700ddeb369f37f">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been blacklisted
+      </source>
+        <target>
+        Su vídeo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> ha sido bloqueado
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ea67498da562ab450950a69f4331b8c4ddfd431">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Una nueva denuncia de abuso<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> ha sido creada sobre el vídeo <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="23b7d6f08c5c3b8722ecd627c3d54f4950923156">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> commented your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> ha comentado su vídeo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2d0ee93317d4daa301eee7fec775c21c2f7b5a4b">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been published
+      </source>
+        <target>
+        Su vídeo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> ha sido publicado
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="371391b88724e5ee455582f07eb97728e371f24a">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Your video import<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> succeeded
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Importación de vídeo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> exitosa
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="56e72a0a79d53e9ff8d5f92528664bcb2cf1363a">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Your video import<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> failed
+      </source>
+        <target>
+        Error durante la <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>importación de vídeo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d7f123ae20ca6bfb5ac0f897b90423fdc52d8e78">
+        <source>
+        User <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.account.name }}"/> registered<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> on your instance
+      </source>
+        <target>
+        Nuevo usuario <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.account.name }}"/>registrado<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> en su instancia
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9a05dc5206104085b2b6654fb9137291194a72ef">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.actorFollow.follower.displayName }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> is following
+
+        <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>
+          your channel <x id="INTERPOLATION_1" equiv-text="{{ notification.actorFollow.following.displayName }}"/>
+        <x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+        <x id="START_TAG_NG-CONTAINER_1" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>your account<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.actorFollow.follower.displayName }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> ahora sigue
+
+        <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>
+          su canal <x id="INTERPOLATION_1" equiv-text="{{ notification.actorFollow.following.displayName }}"/>
+        <x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+        <x id="START_TAG_NG-CONTAINER_1" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>su cuenta<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98b174525a2c9b4de0a510fb6eae7bdf285c0c7f">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> mentioned you on <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>video <x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> le ha mencionado en el <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>vídeo <x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">52</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="473117e02024f603dc2dbd24a0bf81f8722cf8dc">
+        <source>
+      <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+    </source>
+        <target>
+      <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="4b3963c6d0863118fe9e9e33447d12be3c2db081">
         <source>Unlisted</source>
         <target>No listado</target>
@@ -433,6 +582,13 @@ Cancelar la subscripción</target>
           <context context-type="linenumber">25</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="c078d4901a5fac169665947cc7a6108b94dd80c7">
+        <source><x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="12910217fdcdbca64bee06f511639b653d5428ea">
         <source>
     Login
@@ -497,7 +653,7 @@ Iniciar sesión</target>
         <source>Password</source>
         <target>Contraseña</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
@@ -511,7 +667,7 @@ Iniciar sesión</target>
         <source>Login</source>
         <target>Identificarse</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
@@ -521,6 +677,17 @@ Iniciar sesión</target>
           <context context-type="linenumber">57</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f876804a6725f7b950c8e4c56ca596206856e6a2">
+        <source>
+      We are sorry, you cannot recover you password because your instance administrator did not configure the PeerTube email system.
+    </source>
+        <target>
+      Lo sentimos, no puede recuperar su contraseña porque el administrador de su instancia no configuró el sistema de correos electrónicos de PeerTube.
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">63</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="244aae9346da82b0922506c2d2581373a15641cc">
         <source>Email</source>
         <target>Correo electrónico </target>
@@ -539,7 +706,7 @@ Iniciar sesión</target>
         <source>Send me an email to reset my password</source>
         <target>Enviar un correo electrónico para restablecer mi contraseña</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
@@ -608,7 +775,7 @@ Iniciar sesión</target>
         <source>Signup</source>
         <target>Registro</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
@@ -678,7 +845,18 @@ Iniciar sesión</target>
         <source>Change the language</source>
         <target>Cambiar el idioma</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c98d728375e7bd5b166d1aeb29485ef8b5d6e28">
+        <source>
+    Help to translate PeerTube!
+  </source>
+        <target>
+    ¡Ayude a traducir PeerTube!
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
@@ -689,7 +867,7 @@ Iniciar sesión</target>
              Mi perfil público
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
@@ -700,7 +878,7 @@ Iniciar sesión</target>
               Mi cuenta
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
@@ -711,7 +889,7 @@ Iniciar sesión</target>
               Mis vídeos
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
@@ -722,14 +900,14 @@ Iniciar sesión</target>
               Desconectarse
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Crear una cuenta</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
@@ -743,49 +921,49 @@ Iniciar sesión</target>
         <source>Subscriptions</source>
         <target>Suscripciones</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Vista general</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Tendencias</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Añadidos recientemente</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Local</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Más</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administración</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
@@ -799,14 +977,42 @@ Iniciar sesión</target>
         <source>Show keyboard shortcuts</source>
         <target>Mostrar los atajos de teclado</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>Alternar con la interfaz oscura</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2dc8a0a3763cd5c456c84630fc335398c9b86771">
+        <source>View your notifications</source>
+        <target>Ver sus notificaciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab">
+        <source>Notifications</source>
+        <target>Notificaciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="341e026e3f317aa3164916cc63a059c961a78b81">
+        <source>Update your notification preferences</source>
+        <target>Actualizar sus preferencias de notificación</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3d1b5c9cd76948c04fdb7bb3fe51b6c1242c1bd5">
+        <source>See all your notifications</source>
+        <target>Ver todas sus notificaciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
@@ -911,14 +1117,14 @@ Iniciar sesión</target>
         <source>Display unlisted and private videos</source>
         <target>Mostrar los vídeos no listados y privados</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target> Ningún resultados</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
@@ -976,15 +1182,64 @@ Iniciar sesión</target>
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="5fea66be16da46ed7a0775e9a62b7b5e94b77473">
+        <source>Contact <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> administrator</source>
+        <target>Contactar al administrador de <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="533b2b9a76ee1335cb44c01f0bfd50d43e9400b0">
+        <source>Your name</source>
+        <target>Su nombre</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0b892c7805a1c5afc0b7c21c3449760860fe7f3d">
+        <source>Your email</source>
+        <target>Su dirección de correo electrónico</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d2815c9b510b8172d8cac4008b9709df69d636df">
+        <source>Your message</source>
+        <target>Su mensaje</target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  Acerca del nodo <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Cancelar
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Enviar</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="89e55a86cb300f06139ff398c9c8bb7376f78b07">
+        <source>About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance</source>
+        <target>Acerca de la instancia <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3c1aff50472b313c70a72ee02c081b8eeb1c616c">
+        <source>Contact administrator</source>
+        <target>Contactar al administrador</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
@@ -998,47 +1253,47 @@ Iniciar sesión</target>
         <source>Terms</source>
         <target>Términos de uso</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>El registro de usuarios está permitido y</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
+      <trans-unit id="7a0a7b5a5bc9ee7b7e415f87ecc404145fb51dff">
         <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
+          this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
+        </source>
         <target>
-      este nodo ofrece una cuota estándar de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> de espacio para los vídeos de sus usuarios.
-    </target>
+          esta instancia provee un espacio máximo de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> para los vídeos de sus usuarios.
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
+      <trans-unit id="7bee5dd41c0007820f150ee33b8257dc1aac281b">
         <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
+          this instance provides unlimited space for the videos of its users.
+        </source>
         <target>
-      este nodo ofrece espacio ilimitado para los vídeos de sus usuarios.
-    </target>
+          esta instancia provee un espacio ilimitado para los vídeos de sus usuarios.
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">31</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
+      <trans-unit id="b6e2ede24a2ee0f6ba2f1924ede2ae408ffc2574">
         <source>
-    User registration is currently not allowed.
-  </source>
+        User registration is currently not allowed.
+      </source>
         <target>
-    El registro de usuarios no está permitido actualmente.
-  </target>
+        El registro de usuarios no está abierto actualmente.
+      </target>
         <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
@@ -1395,49 +1650,49 @@ Iniciar sesión</target>
         <source>Short description</source>
         <target>Descripción corta</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Routa de cliente por defecto</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Vista general de los vídeos</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Vídeos en Tendencia</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Vídeos Recientemente Añadidos</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Vídeos locales</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Política para los vídeos que contengan material sensible</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
@@ -1472,27 +1727,48 @@ Iniciar sesión</target>
         <source>Signup enabled</source>
         <target>Registro habilitado</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>La suscripción requiere una verificación mediante correo electrónico</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Límite de registro</target>
         <context-group name="null">
-          <context context-type="linenumber">105</context>
+          <context context-type="linenumber">96</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
-        <source>Import</source>
-        <target>Importar</target>
-        <context-group name="null">
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Usuarios</target>
+        <context-group name="null">
+          <context context-type="linenumber">105</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Cuota de vídeo por defecto del usuario</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Límite diario de subida por día por usuario</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
+        <source>Import</source>
+        <target>Importar</target>
+        <context-group name="null">
           <context context-type="linenumber">42</context>
         </context-group>
       </trans-unit>
@@ -1500,49 +1776,35 @@ Iniciar sesión</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>La importación de vídeos mediante URL HTTP (por ejemplo YouTube) está activada</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Importar video con un archivo torrent o un enlace magnet activado</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administrador</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Correo del administrador</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Usuarios</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Cuota de vídeo por defecto del usuario</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Límite diario de subida por día por usuario</target>
+      <trans-unit id="f9bda6652199995a4bd4424f2e35b748eb0bda8a">
+        <source>Enable contact form</source>
+        <target>Habilitar el formulario de contacto</target>
         <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">169</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
@@ -1563,21 +1825,32 @@ Iniciar sesión</target>
         <source>Your Twitter username</source>
         <target>Tu usuario de Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indica la cuenta de Twitter del sitio web o de la plataforma en la que el contenido fue publicado</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Nodo en lista blanca de Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1276a50033dfc7a71290086d0f57d89e3438e6b">
+        <source>If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.&lt;br /&gt;
+        If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.&lt;br /&gt;&lt;br /&gt;
+        Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on &lt;a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'&gt;https://cards-dev.twitter.com/validator&lt;/a&gt; to see if you instance is whitelisted.</source>
+        <target>Si su instancia está autorizada por Twitter, un reproductor de vídeo estará incorporado al hilo Twitter cuando se comparta un vídeo desde PeerTube.&lt;br /&gt;
+        Si la instancia no está autorizada, usamos una tarjeta con una imagen con vínculo que redireccionará hacia su instancia PeerTube.&lt;br /&gt;&lt;br /&gt;
+        Seleccione esta casilla, guarde la configuración y pruebe colocando el URL de un vídeo de su instancia (https://example.com/videos/watch/blabla) en &lt;a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'&gt;https://cards-dev.twitter.com/validator&lt;/a&gt; para verificar si su instancia está autorizada por Twitter.</target>
+        <context-group name="null">
+          <context context-type="linenumber">200</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
@@ -1591,98 +1864,162 @@ Iniciar sesión</target>
         <source>Transcoding</source>
         <target>Transcodificar</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transcodificación activada</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>¡Si desactivas la transcodificación, muchos vídeos de tus usuarios no funcionarán!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0050a55afb9c565df1f9b3f750c2d4adb697698f">
+        <source>Allow additional extensions</source>
+        <target>Autorizar extensiones adicionales</target>
+        <context-group name="null">
+          <context context-type="linenumber">231</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9b82c3a407ee5a98c92483fbd987be8db8384c33">
+        <source>Allow your users to upload .mkv, .mov, .avi, .flv videos</source>
+        <target>Autorizar sus usuarios a subir vídeos .mkv, .mov, .avi y .flv</target>
+        <context-group name="null">
+          <context context-type="linenumber">232</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Hilos de transcodificaciones</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Resolución <x id="INTERPOLATION" equiv-text="{{resolution}}"/> activada</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
+        <source>
+          Cache
+
+          <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
+        </source>
+        <target>
+          Caché
+
+          <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
+        </target>
+        <context-group name="null">
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Algunos archivos (previsualizaciones, subtítulos) no están federados. Los obtenemos directamente del nodo de origen y las ponemos en caché.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Tamaño de caché de las previsualizaciones</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Tamaño de caché de los subtítulos</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalizaciones</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Escribir código Javascript directamente.&lt;br /&gt;Ejemplo: &lt;pre&gt;console.log('mi nodo es maravilloso');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d7caa08cd9b3119881bbaec3f5a3c5707f573dde">
+        <source>
+                    Write directly CSS code. Example:&lt;br /&gt;
+                    &lt;pre&gt;
+          body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            background-color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
+
+                    Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
+                    &lt;pre&gt;
+          #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
+                  </source>
+        <target>
+                    Escriba directamente código CSS. Por ejemplo:&lt;br /&gt;
+                    &lt;pre&gt;
+          body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            background-color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
+
+                    Prefijar con &lt;em&gt;#custom-css&lt;/em&gt; para sobrecargar estilos. Por ejemplo:
+                    &lt;pre&gt;
+          #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
+                  </target>
+        <context-group name="null">
+          <context context-type="linenumber">311</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Configuración avanzada</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Actualizar configuración</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Parece que la configuración no es válida. Por favor, busque errores potenciales en las diferentes pestañas.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
@@ -1707,6 +2044,17 @@ Iniciar sesión</target>
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1a5c7f9b1bec1463728f44933f0e256de9c45154">
+        <source>
+      Moderation
+    </source>
+        <target>
+      Moderación
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7bea88c54fdccfdc9f687b0ffe9bf6a653d19368">
         <source>
       Jobs
@@ -1754,6 +2102,13 @@ Iniciar sesión</target>
           <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="25925fc5826bc5b3eeae7c45b08b0ed74b9e2954">
+        <source>Filter...</source>
+        <target>Filtrar...</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c">
         <source>ID</source>
         <target>ID</target>
@@ -1789,6 +2144,27 @@ Iniciar sesión</target>
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7823909fb1d8d313382f6f4bd842f1a7ef6f08d1">
+        <source>Accepted</source>
+        <target>Aceptado</target>
+        <context-group name="null">
+          <context context-type="linenumber">32</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e6a27066251ca1e04c5be86ad758380856df2506">
+        <source>Pending</source>
+        <target>Pendiente</target>
+        <context-group name="null">
+          <context context-type="linenumber">33</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1d729bcbe3529d2fe2295b7a3a41282ee09de2c8">
+        <source>Redundancy allowed</source>
+        <target>Redundancia autorizada</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5fccee488a9ea908c16d2ab9dbdaf264f1aac479">
         <source>Manage follows</source>
         <target>Gestionar seguimientos</target>
@@ -1893,6 +2269,13 @@ Iniciar sesión</target>
           <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6ded52553dd8720fd3698b8fbc3a6d037c07b496">
+        <source>Daily video quota</source>
+        <target>Cuota diaria de vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5e8b4663c17c337a1f11160c0a683350936faa1f">
         <source>Users list</source>
         <target>Lista de usuarios</target>
@@ -1900,6 +2283,13 @@ Iniciar sesión</target>
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ea762ca1d74c96d8568ac68482778f52ca531cc4">
+        <source>Batch actions</source>
+        <target>Acciones másivas</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="08ea8692dc2a7050026df26fc39b22960bde9de5">
         <source>Username <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Nombre de usuario <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
@@ -1907,6 +2297,13 @@ Iniciar sesión</target>
           <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="adba7c8b43e42581460fbe5d08b5cb5ab60eba4b">
+        <source>(banned)</source>
+        <target>(expulsado)</target>
+        <context-group name="null">
+          <context context-type="linenumber">65</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="be73b652c2707f42b5d780d0c7b8fc5ea0b1706c">
         <source>Go to the account page</source>
         <target>Ir a la página de la cuenta</target>
@@ -1914,6 +2311,52 @@ Iniciar sesión</target>
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>Se requiere validar la dirección de correo electrónico del usuario antes de conectarse</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>La dirección de correo electrónico del usuario ha sido verificada / El usuario puede conectarse sin verificación de dirección de correo electrónico</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
+        <source>Ban reason:</source>
+        <target>Razón de la expulsión:</target>
+        <context-group name="null">
+          <context context-type="linenumber">95</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
+        <source>Moderation comment</source>
+        <target>Comentarios de moderación</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5731e5d5ac989bf08848b5a57a5586cf84d80964">
+        <source>
+        This comment can only be seen by you or the other moderators.
+      </source>
+        <target>
+        Este comentario puede ser visto solo por usted y los otros moderadores.
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0562e455c88234829f3c27a38f3039f027bfd5d2">
+        <source>Update this comment</source>
+        <target>Actualizar este comentario</target>
+        <context-group name="null">
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2bf5a31043ff476ca081a4080f3f3f17518dc6f2">
         <source>Reporter</source>
         <target>Reportador</target>
@@ -1928,6 +2371,13 @@ Iniciar sesión</target>
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7e7ad19f1bcc2c33cdba4c1ad25e2b398ad453d9">
+        <source>State <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Estado <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c6ab75e099e131d7a4f94e1732e7436d8fc386c7">
         <source>Go to the account</source>
         <target>Ir a la cuenta</target>
@@ -1942,6 +2392,69 @@ Iniciar sesión</target>
           <context context-type="linenumber">33</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="030b4423b92167200e39519599f9b863b4f7c62c">
+        <source>Actions</source>
+        <target>Acciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
+        <source>Reason:</source>
+        <target>Razón:</target>
+        <context-group name="null">
+          <context context-type="linenumber">53</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="018cbb63c7eda4b82d17dd9058cfaa0fd055c638">
+        <source>Moderation comment:</source>
+        <target>Comentario de moderación:</target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b14fd2fc28c5eecd05554d2bcbc3a938c599e2bf">
+        <source>Video name <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Nombre del vídeo <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="96dfa3efa02bfafc0bc6d4ab186ebef2813a9e8a">
+        <source>Sensitive</source>
+        <target>Sensible</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a7f42da3bb4eea0b71b0a20a2aff6612a82cab99">
+        <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Fecha <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
+        <source>Blacklist reason:</source>
+        <target>Razón del bloqueo:</target>
+        <context-group name="null">
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
+        <source>Moderation</source>
+        <target>Moderación</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="23a793ed0df2e10823dd469c5cea9b5c36be8f7e">
+        <source>Video abuses</source>
+        <target>Vídeos denunciados como abusivos</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="00ecde6001106fe7406a34cc3459cc5b88e4aec1">
         <source>Blacklisted videos</source>
         <target>Vídeos en lista negra</target>
@@ -1949,18 +2462,39 @@ Iniciar sesión</target>
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Mis ajustes</target>
+      <trans-unit id="b1ff109b26ae8f08650415454b9098c43eba2e2c">
+        <source>Muted accounts</source>
+        <target>Cuentas silenciadas</target>
         <context-group name="null">
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Mis vídeos</target>
+      <trans-unit id="bd0611346af048015e0a1275091ef68ce98832d2">
+        <source>Muted servers</source>
+        <target>Servidores silenciados</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92">
+        <source>Account</source>
+        <target>Cuenta</target>
+        <context-group name="null">
+          <context context-type="linenumber">12</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="079e99cce11c87b142e80fdd14dae98a61012fc4">
+        <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Silenciado en <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1f689fada9748a830117f5b429a88ef8629082a8">
+        <source>Unmute</source>
+        <target>Dejar de silenciar</target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
@@ -1974,28 +2508,46 @@ Iniciar sesión</target>
         <source>Profile</source>
         <target>Perfil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Ajustes de vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Zona peligrosa</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Enviar</target>
+      <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
+        <source>Change ownership</source>
+        <target>Cambiar el titular</target>
         <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">46</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="046c4fa30411e6b1aa46dc51bf82d07b1adf14d4">
+        <source>Select the next owner</source>
+        <target>Seleccionar el próxima titular</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a5433ae2324496bea9537caa5e8a2719d8e958d8">
+        <source>
+        Cancel
+      </source>
+        <target>
+        Cancelar
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
@@ -2005,6 +2557,13 @@ Iniciar sesión</target>
           <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4a806761798181e907e28ed1af053d466526800d">
+        <source>Blacklisted</source>
+        <target>Bloqueado</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="17a9d3860d9ad593dd09a9f934e03999d9e76a7a">
         <source>
             Cancel
@@ -2036,6 +2595,13 @@ Cancelar</target>
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="915d4704e1649016512cbf5eeac55b4dbf933558">
+        <source>Example: my_channel</source>
+        <target>Por ejemplo: mi_canal</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="bc155f9fc3be3f32083f19b2c77d4ad3b696d9b9">
         <source>Display name</source>
         <target>Nombre a mostrar</target>
@@ -2059,6 +2625,13 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="3a5d57052d13d2da1cbcffdbb8effb9874b1595a">
+        <source>You don't have any subscriptions yet.</source>
+        <target>No tiene suscripciones todavía.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c65641c36859c328928e6b0f14c3f913886f8add">
         <source>Created by <x id="INTERPOLATION" equiv-text="{{ videoChannel.ownerBy }}"/></source>
         <target>Creado por <x id="INTERPOLATION" equiv-text="{{ videoChannel.ownerBy }}"/></target>
@@ -2073,32 +2646,157 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
-        <source>Change password</source>
-        <target>Cambiar contraseña</target>
+      <trans-unit id="fbc450919a486e8ed311a7e91a41987d47d83804">
+        <source>Accept ownership</source>
+        <target>Aceptar la titularidad</target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e70e209561583f360b1e9cefd2cbb1fe434b6229">
-        <source>New password</source>
-        <target>Nueva contraseña</target>
+      <trans-unit id="4570c754149df06f31096510abfc925968c35562">
+        <source>Select the target channel</source>
+        <target>Seleccionar el canal objetivo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="ede41f01c781b168a783cfcefc6fb67d48780d9b">
-        <source>Confirm new password</source>
-        <target>Confirmar nueva contraseña</target>
+      <trans-unit id="e98239d8a6be1100119ff4b5630c822b82786740">
+        <source>Initiator</source>
+        <target>Iniciador</target>
         <context-group name="null">
-          <context context-type="linenumber">23</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="20f62f24170d57b1efeb2387a0949f482cd4d129">
-        <source>Default policy on videos containing sensitive content</source>
-        <target>Política por defecto para vídeos que contengan material sensible</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
+      <trans-unit id="b08d67fe4e192ea8352bebdc6aabbd1bb7abed02">
+        <source>
+        Created
+        <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/>
+      </source>
+        <target>
+        Creado
+        <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1">
+        <source>Status</source>
+        <target>Estatus</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1bd5e17c9582661e20763a7634ef07881e33bbd7">
+        <source>Action</source>
+        <target>Acción</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4212e793d36e1aaa6ee1b09881677f783b5feff">
+        <source><x id="INTERPOLATION" equiv-text="{{ videoChangeOwnership.status }}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{ videoChangeOwnership.status }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a5613f6b472c1ed863dff1be932913a251f27a2">
+        <source>Refuse</source>
+        <target>Rechazar</target>
+        <context-group name="null">
+          <context context-type="linenumber">47</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Instancias silenciadas</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e8e93a7ae9a47c035bf5170b105c418b1deae530">
+        <source>History enabled</source>
+        <target>Historial habilitado</target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0f1fd6758625c6a39d796378d362cdcc2b092123">
+        <source>Delete history</source>
+        <target>Borrar el historial</target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6b4dc5732f1f2211833d4b5e76deb5985f3749af">
+        <source>You don't have videos history yet.</source>
+        <target>No tiene historial de vídeos todavía</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6aec8cb024acc333218d72f279caa8ea623bb628">
+        <source><x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> vistas</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3a6903ba6b8cf2d828d0c86fd1feb09a27be4105">
+        <source>Notification preferences</source>
+        <target>Preferencias de notificación</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1da23f4068fd3796fbcb24d0c42bb62f92c96829">
+        <source>Mark all as read</source>
+        <target>Marcar todo como leído</target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
+        <source>Change password</source>
+        <target>Cambiar contraseña</target>
+        <context-group name="null">
+          <context context-type="linenumber">30</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0dd390d056411e1709ec97ec51c46d78600e3f7b">
+        <source>Current password</source>
+        <target>Contraseña actual</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e70e209561583f360b1e9cefd2cbb1fe434b6229">
+        <source>New password</source>
+        <target>Nueva contraseña</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ede41f01c781b168a783cfcefc6fb67d48780d9b">
+        <source>Confirm new password</source>
+        <target>Confirmar nueva contraseña</target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="20f62f24170d57b1efeb2387a0949f482cd4d129">
+        <source>Default policy on videos containing sensitive content</source>
+        <target>Política por defecto para vídeos que contengan material sensible</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d044c51156e295824813a866dba9545bdb59466b">
+        <source>Use WebTorrent to exchange parts of the video with others</source>
+        <target>Usar WebTorrent para intercambiar partes del vídeo con otros</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fb17c44abac2d1ed2a54cdd28bae289dc0b9a1c2">
@@ -2143,6 +2841,13 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">18</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d1a04ba05116499d4cf59a48a282a8bcbf5b622d">
+        <source>Once you delete your account, there is no going back. Please be certain.</source>
+        <target>Eliminar su cuenta es definitivo. ¿Está seguro?</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="9a2f889dde4574a6883c853d1034e75891b28c45">
         <source>Delete your account</source>
         <target>Eliminar tu cuenta</target>
@@ -2150,6 +2855,20 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="dd3b6c367381ddfa8f317b8e9b31c55368c65136">
+        <source>Activities</source>
+        <target>Actividades</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="847dffd493abbb2a5c71f3313f0eb730dd88a355">
+        <source>Web</source>
+        <target>Web</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e242e3e8608a3c4a944327eb3d5c221dc6e4e3cd">
         <source>
   Sorry, but we couldn't find the page you were looking for.
@@ -2161,6 +2880,60 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="09a69cde5889927629e2ac9dc63a71b88252b530">
+        <source>
+    Verify account email confirmation
+  </source>
+        <target>
+    Verificar la confirmación de la dirección de correo electrónico de la   cuenta
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="066569dd934e07e4a5f70c415692be17d5715b57">
+        <source>
+    Your email has been verified and you may now login. Redirecting...
+  </source>
+        <target>
+    Su dirección de correo electrónico ha sido verificada y puede conectarse ahora. En curso de redirección...
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7ee8fad77b2664dabfb90ea03470f75a6f6d1d48">
+        <source>An error occurred. </source>
+        <target>Un error ocurrió.</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2d02841904de7f5f60e2618670ac1059f3abec97">
+        <source>
+    Request email for account verification
+  </source>
+        <target>
+    Solicitar un correo electrónico de verificación de la cuenta
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="eb539ec6941044e284f237f5b40d6a0159afe7af">
+        <source>Send verification email</source>
+        <target>Enviar un correo electrónico de verificación</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a08080316e052053fd20647731a6de826dc8072f">
+        <source>This instance does not require email verification.</source>
+        <target>Esta instancia no requiere verificación por correo electrónico</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1380539d91f77f565de6e21ce210da891e6644b8">
         <source>Support this channel</source>
         <target>Apoyar este canal</target>
@@ -2203,6 +2976,13 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>Disculpas, algo salió mal</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2228,18 +3008,25 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5e420747842373fa99a75a7a18df068cc81e46fb">
+        <source>Scheduled</source>
+        <target>Programado</target>
+        <context-group name="null">
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="f7ac2376749c7985f94f0fc89ba75ea624de1215">
         <source>Publish will be available when upload is finished</source>
         <target>La publicación estará disponible cuando finalice la subida</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publicar</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2249,6 +3036,13 @@ Cuando subas un vídeo a este canal, el campo de soporte del vídeo se rellenar
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1b518e7f8c067fa55ea797bb1b35b4a2d31dccbc">
+        <source>Or</source>
+        <target>O</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0d6558176587662e9bb3b79cca57d42591cf82f9">
         <source>Paste magnet URI</source>
         <target>Pegar el enlace magnético</target>
@@ -2323,6 +3117,17 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="c34c61401151c29fb3679638a7d0b95258145ec3">
+        <source>
+        This will replace an existing caption!
+      </source>
+        <target>
+        Eso remplazará el texto existente!
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="39702b643cfe3d5b96a4587c1b44a29fa665406c">
         <source>Add this caption</source>
         <target>Añadir este subtítulo</target>
@@ -2344,6 +3149,27 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="457b1cff4d8d7fad0c8742f69c413ecf5e443851">
+        <source>Tags could be used to suggest relevant recommendations.&lt;/br&gt;Press Enter to add a new tag.</source>
+        <target>Se puede utilizar etiquetas para sugerir recomendaciones relevantes.&lt;/br&gt;Presione Enter para añadir una nueva etiqueta.</target>
+        <context-group name="null">
+          <context context-type="linenumber">18</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9bdd535a2817bf0b843a124bf65e4992625e7ecf">
+        <source>+ Tag</source>
+        <target>+ Etiqueta</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8389e9cde2928cc27aaecbdee818a255bf7984b0">
+        <source>Enter a new tag</source>
+        <target>Ingresar una nueva etiqueta</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="50f53834157770b8205ada0e7a6e235211e4765e">
         <source>Video descriptions are truncated by default and require manual action to expand them.</source>
         <target>Las descripciones de vídeo se muestran truncadas por defecto y requieren de acción manual para expandirlas.</target>
@@ -2383,14 +3209,14 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
         <source>Wait transcoding before publishing the video</source>
         <target>Esperar transcodificación antes de publicar el vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Si decides no esperar a la transcodificación antes de publicar el vídeo, quizás no se pueda reproducir hasta que finalice la transcodificación.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2404,49 +3230,81 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
         <source>Add another caption</source>
         <target>Añadir otro subtítulo</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Ver el archivo de subtítulo</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
+        <source>Already uploaded       ✔</source>
+        <target>Ya ha sido subido      ✔</target>
+        <context-group name="null">
+          <context context-type="linenumber">160</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
+        <source>Will be created on update</source>
+        <target>Estará creado al actualizar</target>
+        <context-group name="null">
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Cancelar creación</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
+        <source>Will be deleted on update</source>
+        <target>Estará eliminado al actualizar</target>
+        <context-group name="null">
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Cancelar borrado</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
+        <source>
+            No captions for now.
+          </source>
+        <target>
+            Ningún texto por el momento.
+          </target>
+        <context-group name="null">
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Subtítulos</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Subir miniatura</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Subir previsualización</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2460,14 +3318,14 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Breve texto para explicar a la gente cómo pueden apoyarte (plataforma de miembros...).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Ajustes avanzados</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -2509,6 +3367,17 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="da44efc7b658c318651866454d258bbbe57ff21c">
+        <source>
+      Cancel
+    </source>
+        <target>
+      Cancelar
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">47</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="dc75033a5238fdc4f462212c847a45ba8018a3fd">
         <source>Download</source>
         <target>Descargar</target>
@@ -2523,6 +3392,19 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="827b1376aa35c7a7de90f7724d6a51ccfa20c908">
+        <source>
+      Your report will be sent to moderators of <x id="INTERPOLATION" equiv-text="{{ currentHost }}"/>.
+      <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/> It will be forwarded to origin instance <x id="INTERPOLATION_1" equiv-text="{{ originHost }}"/> too.<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+    </source>
+        <target>
+      Su reporte estará enviado a los moderadores de <x id="INTERPOLATION" equiv-text="{{ currentHost }}"/>.
+      <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/> También estará transferido a la instancia original <x id="INTERPOLATION_1" equiv-text="{{ originHost }}"/>.<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Compartir</target>
@@ -2544,6 +3426,31 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">34</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="90e0a0a3da80b46e550c1395ff4e97c27259bef8">
+        <source>
+      The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
+    </source>
+        <target>
+      El URL no es seguro (no utiliza HTTPS), por lo que el vídeo embebido no funcionará en los sitios web HTTPS (los navegadores web bloquean las consultas HTTP inseguras en los sitios web HTTPS).
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">45</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8">
+        <source>Close</source>
+        <target>Cerrar</target>
+        <context-group name="null">
+          <context context-type="linenumber">51</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f672385c803647b063687d3c912e2ce5738b51c8">
+        <source>Blacklist video</source>
+        <target>Bloquear el vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7584313e33a66811eb10646627914a01fff0347d">
         <source>
     The video is being imported, it will be available when the import is finished.
@@ -2566,6 +3473,46 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">15</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="c89a08fd2a05d1013fed8478024f5ba37ac3d308">
+        <source>
+    This video will be published on <x id="INTERPOLATION" equiv-text="{{ video.scheduledUpdate.updateAt | date: 'full' }}"/>.
+  </source>
+        <target>
+    Este vídeo será publicado el <x id="INTERPOLATION" equiv-text="{{ video.scheduledUpdate.updateAt | date: 'full' }}"/>.
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd7055d3e38beff538463e75d508d1c75c683710">
+        <source>This video is blacklisted.</source>
+        <target>Este vídeo está bloqueado</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3da5360f8314aa95973aa52629c9f635363c5a36">
+        <source>
+                Published <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
+              </source>
+        <target>
+                Publicado <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> vistas
+              </target>
+        <context-group name="null">
+          <context context-type="linenumber">37</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="07087373dbf99b5e8b2b2f962fd53baa97d9ab95">
+        <source>
+                  Published <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
+                </source>
+        <target>
+                  Publicado <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> vistas
+                </target>
+        <context-group name="null">
+          <context context-type="linenumber">46</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="82b59049f3f89d900c98da9319e156dd513e3ced">
         <source>Like this video</source>
         <target>Me gusta este vídeo</target>
@@ -2629,6 +3576,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">100</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="61021f5011bc24f69cfc3f6dbbbd8f1948328b25">
+        <source>Unblacklist this video</source>
+        <target>Desbloquear este vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">99</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="3dbfdc68f83d91cb360172eb65578cae94e7cbe5">
         <source>Delete this video</source>
         <target>Eliminar este vídeo</target>
@@ -2643,6 +3597,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">123</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="0b7f242da10ece3f2995095c455b9a92ebcdd3b4">
+        <source>By <x id="INTERPOLATION" equiv-text="{{ video.byAccount }}"/></source>
+        <target>Por <x id="INTERPOLATION" equiv-text="{{ video.byAccount }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">134</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="f0c5f6f270e70cbe063b5368fcf48f9afc1abd9b">
         <source>Show more</source>
         <target>Mostrar más</target>
@@ -2657,6 +3618,24 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">152</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4c0ba3cde3b3c58b855ffb4beaa5804a2fc3826b">
+        <source>Friendly Reminder: </source>
+        <target>Recuerdo amistoso:</target>
+        <context-group name="null">
+          <context context-type="linenumber">208</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9e66f7507eb263abdbab7abafd825f1dc8bc880b">
+        <source>
+        the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers.
+      </source>
+        <target>
+        el sistema utilizado para compartir este vídeo implica que algunas informaciones técnicas acerca de su sistema (como la dirección IP pública) pueden estar enviadas a otros pares.
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">209</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e60c11e1b1dfbbeda577364b8de39ded2d796c5e">
         <source>More information</source>
         <target>Más información</target>
@@ -2682,6 +3661,17 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="abf2b0f7b6405fa2841ca39c827e86089a95cc27">
+        <source>
+        Other videos
+    </source>
+        <target>
+        Otros vídeos
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b5f5df598f2d75640849b2a7744f91e5dbd390e7">
         <source>
       Comments
@@ -2736,6 +3726,57 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="8b2bb53dfb5f059f2b68cc4ac00661a865909135">
+        <source>You are one step away from commenting</source>
+        <target>Está a un paso de poder comentar</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7984a44ce86b961f4f18c9a58c638f5e8f07a225">
+        <source>
+      If you have an account on this instance, you can login:
+    </source>
+        <target>
+      Si tiene una cuenta en esta instancia, puede conectarse:
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">32</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="afe0ad39fee662489f1033e53aea3e16a7e89228">
+        <source>login to comment</source>
+        <target>conectarse para comentar</target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a5a3f17c9b4876952d78363834d57280c8684e7c">
+        <source>
+      Otherwise you can comment using an account on any ActivityPub-compatible instance.
+      On most platforms, you can find the video by typing its URL in the search bar and then comment it
+      from within the software's interface.
+    </source>
+        <target>
+      Sino, puede comentar usando una cuenta de cualquier instancia compatible con ActivityPub.
+      En la mayoría de las plataformas, puede encontrar el vídeo colocando su URL en el campo de búsqueda y luego comentarlo
+      desde la interfaz del software.
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">36</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="968b02fbc645be799727de0d1ec3c6f9b11b20eb">
+        <source>
+      If you have an account on Mastodon or Pleroma, you can open it directly in their interface:
+    </source>
+        <target>
+      Si tiene una cuenta en Mastodon o Pleroma, puede abrirlo directamente en su interfaz:
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">41</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a607fab03e11b0e07c1640e11a1b02d7af06b285">
         <source>Highlighted comment</source>
         <target>Comentario resaltado</target>
@@ -2750,9 +3791,23 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Error al obtener información del servidor</target>
+      <trans-unit id="e0e3a472479c8ce1b78f682ffadbe59daf04d331">
+        <source>Cannot get about information from server</source>
+        <target>No se puede obtener información del servidor</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9e601a3b227bb70afbb9b59cd43547b710af1e10">
+        <source>Your message has been sent.</source>
+        <target>Su mensaje ha sido remitido</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8d6d4f48dae547bb32e0669cda5a665dc8db536c">
+        <source>You already sent this form recently</source>
+        <target>Ya envió este formulario recientemente</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2778,16 +3833,44 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Error</target>
+      <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
+        <source>240p</source>
+        <target>240p</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Correcto</target>
+      <trans-unit id="c8cfad7e7a16c57c42535331b65cb7de40d8402e">
+        <source>360p</source>
+        <target>360p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="48f0af5a0d0bea4e84b27eaf41b19c85a531c2a5">
+        <source>480p</source>
+        <target>480p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6f06138daf6363746ff26bfc0cb2491c09cdfdf2">
+        <source>720p</source>
+        <target>720p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="65c94f9beb6fe957808c40060da280cc7ace7ab9">
+        <source>1080p</source>
+        <target>1080p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="421a937491f19774d17eefa1d24816dae1a9f111">
+        <source>Auto (via ffmpeg)</source>
+        <target>Auto (vía ffmpeg)</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2806,6 +3889,69 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="54adc67482fdaa0d361a2992bc91e064dc61cc9a">
+        <source>100MB</source>
+        <target>100MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cd34ef1f476d5422f49f6ed429f61fc1cfcb1174">
+        <source>500MB</source>
+        <target>500MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a47b4beea31cac6e5970b6bc522902f545acc8b">
+        <source>1GB</source>
+        <target>1GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b26d0cac75638623098ab7e06e16b096d1f55cc8">
+        <source>5GB</source>
+        <target>5GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f9fc4e7ec6743cb6f69bea2d0859a655ed44ffae">
+        <source>20GB</source>
+        <target>20GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a56e3f92fe16d97ee4f05051ea61c466ecb51d5e">
+        <source>50GB</source>
+        <target>50GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="31dcc0c63f6234ace8caa84ae1abc33d4022122d">
+        <source>10MB</source>
+        <target>10MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f2f968b6f2199b919f567702c6f23b43e5ea71af">
+        <source>50MB</source>
+        <target>50MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c31575424fe1b2a57064413f3eda7ce657c46c8a">
+        <source>2GB</source>
+        <target>2GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="fc5731a28a99b25c62d43333ceebb250d60aff84">
         <source><x id="INTERPOLATION" equiv-text="{{host}}"/> is not valid</source>
         <target><x id="INTERPOLATION" equiv-text="{{host}}"/> no es válido</target>
@@ -2869,6 +4015,97 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4d8f527638f3e0b518a96e07d41d886bcce01246">
+        <source>enabled</source>
+        <target>habilitada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="795733aac948794cadeb3be6386882efac2c38ad">
+        <source>disabled</source>
+        <target>deshabilitada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1123807fc813c816404598147173403d00117557">
+        <source>Redundancy for <x id="INTERPOLATION" equiv-text="{{host}}"/> is <x id="INTERPOLATION_1" equiv-text="{{stateLabel}}"/></source>
+        <target>La redundancia para <x id="INTERPOLATION" equiv-text="{{host}}"/> está <x id="INTERPOLATION_1" equiv-text="{{stateLabel}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="53cc0f4a4566c4139c65f93b5dce2fe8302e78da">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source>
+        <target>La cuenta <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> no está silenciada por su instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="468b52e3c04fb9a3d8c8213555dfcad0cbcae330">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</source>
+        <target>La instancia <x id="INTERPOLATION" equiv-text="{{host}}"/> no está silenciada por su instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="800cd3cdf47751b576587259ba3a1bc0a7f435b6">
+        <source>Comment updated.</source>
+        <target>Comentario actualizado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="586bee8c27a761611eb05661524cc7ca944b5978">
+        <source>Delete this report</source>
+        <target>Eliminar este reporte</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cf3b28ba29a907b334ab0e6dccd080a60ba23321">
+        <source>Update moderation comment</source>
+        <target>Actualizar el comentario de moderación</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d512430037b6580ba970c80cfc1687b6bdc221a3">
+        <source>Mark as accepted</source>
+        <target>Marcar como aceptado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d895b090c054bfc0ad3aba816af0615a1997f5a3">
+        <source>Mark as rejected</source>
+        <target>Marcar como rechazado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73b70e37cddaa6494d8a666b6cba90dc80595599">
+        <source>Do you really want to delete this abuse report?</source>
+        <target>¿Confirma la eliminación del reporte de abuso?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6a7938b8780c27540ea70cc0f8f4d928c8916cf9">
+        <source>Abuse deleted.</source>
+        <target>Reporte de abuso eliminado.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="652845b2b32b2e117b9b02879b1af07859b0e223">
+        <source>Do you really want to remove this video from the blacklist? It will be available again in the videos list.</source>
+        <target>¿Confirmar el desbloqueo de este vídeo? Estará disponible de nuevo en la lista de vídeos.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1585babc36806e20e225ac27dbba0e7c7cd09e0f">
         <source>Video <x id="INTERPOLATION" equiv-text="{{name}}"/> removed from the blacklist.</source>
         <target>Vídeo <x id="INTERPOLATION" equiv-text="{{name}}"/> eliminado de la lista negra.</target>
@@ -2897,6 +4134,41 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="50dc7afa2305131cdbdb384cfc1f2a5f0f4647d8">
+        <source>Unban</source>
+        <target>Dejar sin efecto la expulsión</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>Establecer la dirección de correo electrónico como Verificada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
+        <source>You cannot ban root.</source>
+        <target>No puede expulsar al root.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98119091712a8ca72905e3b4c1cf60649af7565e">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{num}}"/> users?</source>
+        <target>¿Confirma dejar sin efecto la expulsión de <x id="INTERPOLATION" equiv-text="{{num}}"/> usuarios?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6121be086a51c4c73bbdd8aebdddd9744c8f1ffd">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users unbanned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> usuarios expulsados.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="911fc197949e47aa5f0541627bc319f59edd9d11">
         <source>You cannot delete root.</source>
         <target>No puedes eliminar al root.</target>
@@ -2904,6 +4176,90 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="9de914fe915cc730efc57e81c987188a24d3ac51">
+        <source>If you remove these users, you will not be able to create others with the same username!</source>
+        <target>¡Si elimina estos usuarios, no será posible crear otros con el mismo nombre de usuario!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b708d332e3f89b24745e749fa530210f0bdea329">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> usuarios eliminados.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> direcciones de correo electrónico de usuarios establecidas como verificadas.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
+        <target>La cuenta <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ya no está silenciada.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c6af80b42938d4a49e6f6c4f60ce26228916994c">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</source>
+        <target>La instancia <x id="INTERPOLATION" equiv-text="{{host}}"/> ya no está silenciada.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="80057baa3b97a4349304bdaa0a880e6f4778561f">
+        <source>My videos history</source>
+        <target>Mi historial de vídeos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="05f6dda1754741495451b8658bd2248856765d95">
+        <source>Videos history is enabled</source>
+        <target>El historial de vídeos está habilitado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6bb9ade8637c5e35fb5cb36cf7dbec71c65d4013">
+        <source>Videos history is disabled</source>
+        <target>El historial de vídeos está deshabilitado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8453a7a55b8b23bbbc293cd0939fb59a73307de8">
+        <source>Delete videos history</source>
+        <target>Eliminar el historial de vídeos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f8f86df8a1ae711944c3ab819bb19bf360dfa7a4">
+        <source>Are you sure you want to delete all your videos history?</source>
+        <target>¿Confirma la eliminación de todo su historial de vídeos?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="195d5ba6c8bd05762d9318d0afd0b094fd776164">
+        <source>Videos history deleted</source>
+        <target>Historial de vídeos eliminado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e">
+        <source>Ownership accepted</source>
+        <target>Titularidad aceptada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="19508af0dfbc685cbf10cf02061bb5a0f423b6fc">
         <source>Password updated.</source>
         <target>Contraseña actualizada.</target>
@@ -2911,6 +4267,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="466fc8cf56fd4e4e90fec4b900ef083d52bec38c">
+        <source>You current password is invalid.</source>
+        <target>Su contraseña actual es invalida.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ca8e8cf0f1686604db3b6a2ebadab7f7b426a047">
         <source>Are you sure you want to delete your account? This will delete all you data, including channels, videos etc.</source>
         <target>Estás seguro de querer eliminar tu cuenta? Todos tus datos serán eliminados, incluyendo canales, vídeos, etc.</target>
@@ -2939,6 +4302,76 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7c193bf704577e514b63497c4f366511afdb6585">
+        <source>New video from your subscriptions</source>
+        <target>Nuevo vídeo desde sus suscripciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ba897defa2e6c34d5ee3d10edf8d797a35e7e3e5">
+        <source>New comment on your video</source>
+        <target>Nuevo comentario en su vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0a9650640ddd1dfadfe456891d6d4f6093ad428e">
+        <source>New video abuse on local video</source>
+        <target>Nueva denuncia de abuso sobre un vídeo local</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="abac8b7629cfcd85bff25770f83ea229f646f996">
+        <source>One of your video is blacklisted/unblacklisted</source>
+        <target>Uno de sus vídeos ha sido bloqueado/desbloqueado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f3eff4df9e4aa9dab411e6eb83833a33016a88bc">
+        <source>Video published (after transcoding/scheduled update)</source>
+        <target>Vídeo publicado (después de una transcodificación / actualización programada)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ec7ddc265da1df78011ae7677d62a2ae10aef7a4">
+        <source>Video import finished</source>
+        <target>Importación de vídeo terminada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c327bbac87cca61f5c52f5825d564878e98b9034">
+        <source>A new user registered on your instance</source>
+        <target>Un nuevo usuario se registró en su instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f407b90e99a04e2e0d1872c02f01eadbf53e08e2">
+        <source>You or your channel(s) has a new follower</source>
+        <target>Usted o su(s) canal(es) tiene un nuevo seguidor</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14c3050a9da4c1bc49d555c45d5660804d08e83b">
+        <source>Someone mentioned you in video comments</source>
+        <target>Alguien le mencionó en comentarios de vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a0f04081717f5f00c0a2c723903c3a2d4c296401">
+        <source>Preferences saved</source>
+        <target>Preferencias guardadas</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="db4ff52375f6a25ad0472e92754c8c265ae47c6b">
         <source>Profile updated.</source>
         <target>Perfil actualizado.</target>
@@ -2967,6 +4400,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f359f6adf6cccca7770019f947ed594169ee7d47">
+        <source>This name already exists on this instance.</source>
+        <target>El nombre ya existe en esta instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="70a67e04629f6d412db0a12d51820b480788d795">
         <source>Create</source>
         <target>Crear</target>
@@ -2981,23 +4421,16 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>¿De verdad quieres eliminar <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Esto eliminará también todos los vídeos subidos a este canal.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Por favor escribe el nombre del canal de vídeo para confirmar</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Canal de vídeo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> eliminado.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Canal de vídeo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> eliminado.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Mis vídeos</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3065,6 +4498,76 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="740c53a50a618bf5c7a5bd5c3f7321f0bd1840dd">
+        <source>Ownership change request sent.</source>
+        <target>Solicitud de cambio de titularidad enviada.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Mi biblioteca</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Mis canales</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Mis suscripciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4f953496ca94b4f83af049ff715172df2729fb79">
+        <source>My history</source>
+        <target>Mi historial</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Diversos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Cambios de titularidad</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Mis ajustes</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0e2434e7d84145c4e8a930ccc4c26c3cb2887e0d">
+        <source>My notifications</source>
+        <target>Mis notificaciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="af55337b4032d675ab6b2081af797ca9c979b706">
+        <source>An email with verification link will be sent to <x id="INTERPOLATION" equiv-text="{{email}}"/>.</source>
+        <target>Un correo electrónico con un vínculo de verificación será enviado a <x id="INTERPOLATION" equiv-text="{{email}}"/>.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ccbf0490fb6b60d21e03bb2c9003df0ce1a58752">
         <source>Unable to find user id or verification string.</source>
         <target>No se pudo encontrar el id de usuario o la cadena de verificación.</target>
@@ -3072,6 +4575,97 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ff6becacbce7fc0943b0af0df4dd67e5e11bf598">
+        <source>Subscribe to the account</source>
+        <target>Suscribirse a la cuenta</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c95cc372311830f936b39f73c5d6d20c0b16013">
+        <source>Focus the search bar</source>
+        <target>Enfocar la barra de búsqueda</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b19ee83cbd2b735fd081b9aa483a890578019099">
+        <source>Toggle the left menu</source>
+        <target>Conmutar el menú de la izquierda</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b54759e30f7c1983940cdacb8eb03f102a869084">
+        <source>Go to the videos overview page</source>
+        <target>Ir a la página general de vídeos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e919c88a3f889d6659288e69d3e178da0ea7ab0">
+        <source>Go to the trending videos page</source>
+        <target>Ir a la página de vídeos populares</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="249618dcdd7fbdc863c0714e2eb9e8940bc9c37d">
+        <source>Go to the recently added videos page</source>
+        <target>Ir a la página de vídeos recientes</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7e194daef3a3509128c4300d4c7c292c49ebf3f5">
+        <source>Go to the local videos page</source>
+        <target>Ir a la página de vídeos locales</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1fb6204f39a7338e5110b2f113643c9288496ba">
+        <source>Go to the videos upload page</source>
+        <target>Ir a la página de subida de vídeos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0ed7b40c11da9d4565af9c041df20c15bc6be97e">
+        <source>Toggle Dark theme</source>
+        <target>Conmutar el tema Oscuro</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="badd4b24618ccc8a34620acb9053fc654b9612b2">
+        <source>Go to my subscriptions</source>
+        <target>Ir a mis suscripciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b7184b5a236618e8edd747529869c392ab6dace1">
+        <source>Go to my videos</source>
+        <target>Ir a mis vídeos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="acf985bd42886b9b3030b5f68f0e8417c39b40a7">
+        <source>Go to my imports</source>
+        <target>Ir a mis importaciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cfe3c51f0ae9385dc2ce6df740d87e5514aa9390">
+        <source>Go to my channels</source>
+        <target>Ir a mis canales</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="edeaa933b09690523e46977e11064e9c655d77d7">
         <source>Cannot retrieve OAuth Client credentials: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
 </source>
@@ -3088,6 +4682,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Error</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Tienes que reconectar.</target>
@@ -3102,6 +4703,41 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5c0c574151dc8671d9199980ee04bf65aec3b452">
+        <source>Keyboard Shortcuts:</source>
+        <target>Atajos de teclado:</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Info</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Correcto</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
+        <source>Incorrect username or password.</source>
+        <target>Nombre de usuario o contraseña incorrecta</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="39980cc1cf8df621d43f5480d001bdf5d4139338">
+        <source>You account is blocked.</source>
+        <target>Su cuenta ha sido bloqueada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7701e3762dc4a2b2e302c24f17820bc8dd7cacc1">
         <source>An email with the reset password instructions will be sent to <x id="INTERPOLATION" equiv-text="{{email}}"/>.</source>
         <target>Un correo con las instrucciones para restablecer la contraseña será enviado a <x id="INTERPOLATION" equiv-text="{{email}}"/>.</target>
@@ -3270,30 +4906,93 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="58c2f66ba74f1400914031ef4ed635938e9e8ced">
-        <source>Signup limit must be a number.</source>
-        <target>El límite de registro debe ser un número.</target>
+      <trans-unit id="58c2f66ba74f1400914031ef4ed635938e9e8ced">
+        <source>Signup limit must be a number.</source>
+        <target>El límite de registro debe ser un número.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1245841647f9b42d3e7554903c1c50bdd80ab021">
+        <source>Admin email is required.</source>
+        <target>Se requiere un correo de administrador.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3fd2feb77dfe57fe82573e3cdf996105e2fafc66">
+        <source>Admin email must be valid.</source>
+        <target>El correo de adminstrador ha de ser válido.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f15f2e02b1f6a96553e98ea4a969045d17ec1400">
+        <source>Transcoding threads is required.</source>
+        <target>Se requieren hilos de transcodificación.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4166cc066b963a23829b48a09e394f73b453fabd">
+        <source>Transcoding threads must be greater or equal to 0.</source>
+        <target>El número de subprocesos de transcodificación tiene que ser superior o igual a 0.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>Se requiere un correo electrónico.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>El correo electrónico ha de ser válido.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac451f128840b34804ea69c820dc3566f476fb33">
+        <source>Your name is required.</source>
+        <target>Su nombre es requerido.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1fc4633008a2431fdec891d58efcc8b865d7de1a">
+        <source>Your name must be at least 1 character long.</source>
+        <target>Su nombre tiene que contener por lo menos 1 carácter.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c7b44b92c0ce3ccd2f804d001e13da399524e11b">
+        <source>Your name cannot be more than 120 characters long.</source>
+        <target>Su nombre no puede contener más de 120 caracteres.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1245841647f9b42d3e7554903c1c50bdd80ab021">
-        <source>Admin email is required.</source>
-        <target>Se requiere un correo de administrador.</target>
+      <trans-unit id="40b35cf927f9f9a59404a6c914ec4632690b69b2">
+        <source>A message is required.</source>
+        <target>Se tiene que colocar un mensaje.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="3fd2feb77dfe57fe82573e3cdf996105e2fafc66">
-        <source>Admin email must be valid.</source>
-        <target>El correo de adminstrador ha de ser válido.</target>
+      <trans-unit id="d8d4a23f467ee3e93ca0edb1198c233ed633cf64">
+        <source>The message must be at least 3 characters long.</source>
+        <target>El mensaje tiene que contener por lo menos 3 caracteres.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="f15f2e02b1f6a96553e98ea4a969045d17ec1400">
-        <source>Transcoding threads is required.</source>
-        <target>Se requieren hilos de transcodificación.</target>
+      <trans-unit id="07422f6141cfcabaf3c2ce77e3e063222849ef60">
+        <source>The message cannot be more than 5000 characters long.</source>
+        <target>El mensaje no puede contener más de 5.000 caracteres.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3319,37 +5018,23 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>El nombre de usuario ha de ocupar más de 3 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>El nombre de usuario no puede ocupar más de 20 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>El nombre de usuario debe utilizar únicamente caracteres alfanuméricos en minúscula.</target>
+      <trans-unit id="6330d25a3bc6f55dfd5177da6e681d1d3b1a2b1a">
+        <source>Username must be at least 1 character long.</source>
+        <target>El nombre de usuario tiene que contener por lo menos un carácter.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Se requiere un correo electrónico.</target>
+      <trans-unit id="aaaf3d00c35f809eebc7fd68a3f7b8b0230b197a">
+        <source>Username cannot be more than 50 characters long.</source>
+        <target>El nombre de usuario no puede contener más de 50 caracteres.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>El correo electrónico ha de ser válido.</target>
+      <trans-unit id="6f3e95be2538a22da07beaefc39bb2195683990c">
+        <source>Username should be lowercase alphanumeric; dots and underscores are allowed.</source>
+        <target>El nombre de usuario puede contener minúsculas, cifras, puntos y barras bajas.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3389,6 +5074,20 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7e58d1fb4e86af94f5199660ef349d55811888bb">
+        <source>Daily upload limit is required.</source>
+        <target>Se requiere colocar un límite diario de subida.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e283cbc4469959ea664f9d545f15278e089a6f1e">
+        <source>Daily upload limit must be greater than -1.</source>
+        <target>El límite diario de subida tiene que ser superior a -1.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="545e77fd5d9526228a2133109447c23225ed9c85">
         <source>User role is required.</source>
         <target>Se requiere un rol de usuario.</target>
@@ -3403,16 +5102,16 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>El nombre para mostrar debe ocupar como mínimo 3 caracteres.</target>
+      <trans-unit id="085b2d6f79819a72a2b56cada4ef5085ba51d90c">
+        <source>Display name must be at least 1 character long.</source>
+        <target>El nombre mostrado tiene que contener por lo menos 1 carácter.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>El nombre a mostrar no puede ocupar más de 120 caracteres.</target>
+      <trans-unit id="5a920575b8e1067f5b11c66a4a36d3ced87756f1">
+        <source>Display name cannot be more than 50 characters long.</source>
+        <target>El nombre mostrado no puede contener más de 50 caracteres.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3424,6 +5123,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="a4179e366d4aa335f1ddd0a13e9109c71a9338d0">
+        <source>Description cannot be more than 1000 characters long.</source>
+        <target>La descripción no puede contener más de 1.000 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="4a3ebc6ddb6b6677aed7b04eb503f9ddd0cfe561">
         <source>You must to agree with the instance terms in order to registering on it.</source>
         <target>Debes aceptar los términos de uso del nodo para poder registrarte en él.</target>
@@ -3431,6 +5137,20 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6d2c3ebffd49b8933200a6d4e5b74712be49bf00">
+        <source>Ban reason must be at least 3 characters long.</source>
+        <target>La razón de la expulsión tiene que contener por lo menos 3 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="be32ff1dd6e464c5c085dd7d128316f476d2e0fd">
+        <source>Ban reason cannot be more than 250 characters long.</source>
+        <target>La razón de la expulsión no puede contener más de 250 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b3cf1889d2fdd6b15e697c270c9b80772fe2cae6">
         <source>Report reason is required.</source>
         <target>Se requiere un motivo para reportar.</target>
@@ -3445,9 +5165,37 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>El motivo del reporte no puede ocupar más de 300 caracteres.</target>
+      <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
+        <source>Moderation comment is required.</source>
+        <target>Se requiere llenar el comentario de moderación.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82e31d0837eaa69a4364e7434d253ce138b3c5c2">
+        <source>Moderation comment must be at least 2 characters long.</source>
+        <target>El comentario de moderación tiene que contener por lo menos 2 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
+        <source>The channel is required.</source>
+        <target>Se requiere llenar el canal.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0776b05d442a0a16f083a5eefa52a166b9d514ca">
+        <source>Blacklist reason must be at least 2 characters long.</source>
+        <target>La razón del bloqueo tiene que contener por lo menos 2 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5009443905b0b152915247799492bf5e164e7626">
+        <source>Blacklist reason cannot be more than 300 characters long.</source>
+        <target>La razón del bloqueo no puede contener más de 300 caracteres.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3466,6 +5214,48 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="bd7fc070c728dc6dbf3959d49fe5bb27ce15d294">
+        <source>The username is required.</source>
+        <target>Se requiere llenar el nombre de usuario.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c8465c3773699dd075e0147e264d2e232f605803">
+        <source>You can only transfer ownership to a local account</source>
+        <target>Solo puede transferir la titularidad a una cuenta local</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="541087322c34e8b26954fd67ff4fc80d1a6c1b33">
+        <source>Name is required.</source>
+        <target>Se requiere llenar el nombre.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b8b59b6284a14fc71268cf722ed98c62c5af4a76">
+        <source>Name must be at least 1 character long.</source>
+        <target>El nombre tiene que contener por lo menos 1 carácter.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e14cd37d29f13eac7384c339e4f1df58d96e4e3d">
+        <source>Name cannot be more than 50 characters long.</source>
+        <target>El nombre no puede contener más de 50 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="135185da003b14cbb69521f570fa617a00bbbe18">
+        <source>Name should be lowercase alphanumeric; dots and underscores are allowed.</source>
+        <target>El nombre puede contener minúsculas, cifras, puntos y barras bajas.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>El texto para el apoyo ha de ocupar como mínimo 3 caracteres.</target>
@@ -3473,6 +5263,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="15ec53d9ee65cb930c5f5d10ae2e8dd3fd44fc85">
+        <source>Support text cannot be more than 1000 characters long.</source>
+        <target>El texto de apoyo no puede contener más de 1.000 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="6ca60e0f6dfbc0073b0514bce7d273150b0b9e79">
         <source>Comment is required.</source>
         <target>Se requiere comentario.</target>
@@ -3564,6 +5361,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f17de746af56840511cae11559539b6d8b6955ad">
+        <source>Video support cannot be more than 1000 characters long.</source>
+        <target>El soporte de vídeo no puede contener más de 1.000 caracteres.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="453413bf387dea681958871319bab489dd5e6ec0">
         <source>A date is required to schedule video update.</source>
         <target>Se requiere una fecha para actualizar la programación del vídeo.</target>
@@ -3914,6 +5718,27 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="a0fdb831d4557925dbaa4f8aff7e5035f7506411">
+        <source>Transcode your videos in multiple resolutions</source>
+        <target>Transcodificar sus vídeos en múltiples resoluciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="590fc27fcbd7dd680da2bb2da644a183338f6bd1">
+        <source>HTTP import (YouTube, Vimeo, direct URL...)</source>
+        <target>Importación HTTP (YouTube, Vimeo, URL directo...)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4e231a74ad4739e7b0606e8e66d5a656f5855a5a">
+        <source>Torrent import</source>
+        <target>Importación de torrent</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7296e9f7cc4956b6d57c541728b0826e76d108ba">
         <source>~ <x id="INTERPOLATION" equiv-text="{{minutes}}"/> <x id="ICU" equiv-text="{minutes, plural, =1 {...} other {...}}"/></source>
         <target>~ <x id="INTERPOLATION" equiv-text="{{minutes}}"/> <x id="ICU" equiv-text="{minutes, plural, =1 {...} other {...}}"/></target>
@@ -4068,6 +5893,41 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f9b4f2d8146c789cd40314f640ec4e88efbaf681">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users banned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> usuarios bloqueados.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3ab99e62550869aebc85661fca2faf46785263dd">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> banned.</source>
+        <target>Usuario <x id="INTERPOLATION" equiv-text="{{username}}"/> bloqueado.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="faafee0c03ad25c8a43aa91bd5d98185b67ff734">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{username}}"/>?</source>
+        <target>¿Confirma el desbloqueo de <x id="INTERPOLATION" equiv-text="{{username}}"/>?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="925ba9946b7b256a586f0fcbe3e04fa7a0dee7bd">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> unbanned.</source>
+        <target>El usuario <x id="INTERPOLATION" equiv-text="{{username}}"/> ha sido desbloqueado.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ad07d34d4aadfe03c964cec02ca1d3a921e6b603">
+        <source>If you remove this user, you will not be able to create another with the same username!</source>
+        <target>¡Si elimina este usuario, no podrá crear otro con el mismo nombre de usuario!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="28220fae6799ab98ef6b41af449aa9680082357a">
         <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> deleted.</source>
         <target>Usuario <x id="INTERPOLATION" equiv-text="{{username}}"/> eliminado.</target>
@@ -4075,6 +5935,111 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>El correo electrónico del usuario <x id="INTERPOLATION" equiv-text="{{username}}"/> establecido como verificado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
+        <target>Cuenta <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> silenciada.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="086eda792aeb1b0d131d633b50fdd1792f5f24c6">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</source>
+        <target>Instancia <x id="INTERPOLATION" equiv-text="{{host}}"/> silenciada.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb72d6d1219e89d182e9fd09d853d83baf8d6499">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</source>
+        <target>Cuenta <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> silenciada por la instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8686834bc4afe42c1991c6c18f0bce174a0e17a6">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</source>
+        <target>Cuenta <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ya no silenciada por la instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="35d3509161861a610b0895bf084c781e56ba2830">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</source>
+        <target>Instancia <x id="INTERPOLATION" equiv-text="{{host}}"/> silenciada por la instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="978aeec5613fa97e8a5336d3599cebb23ee5a90f">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</source>
+        <target>La instancia <x id="INTERPOLATION" equiv-text="{{host}}"/> ya no es silenciada por la instancia.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a09bf8724e7659fbb5ec33647529cdef7614bdc">
+        <source>Mute this account</source>
+        <target>Silenciar esta cuenta</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d666ca3261aef72b2ddcd649d7b32af488f59952">
+        <source>Unmute this account</source>
+        <target>Dejar de silenciar esta cuenta</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e17218983b1de76e5a920b04e1c2ecbdb6e3e06d">
+        <source>Mute the instance</source>
+        <target>Silenciar esta instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a23514d8aca2f8633622dda0e86b399dc576a2b9">
+        <source>Unmute the instance</source>
+        <target>Dejar de silenciar esta instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4e4107055b44eee44b6954c41120de1cb4d46432">
+        <source>Mute this account by your instance</source>
+        <target>Silenciar esta cuenta por su instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a51c59cb5ecb7004a6a8ddd2855b5c52266ad957">
+        <source>Unmute this account by your instance</source>
+        <target>Dejar de silenciar esta cuenta por su instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="588073e831cec240d6bb0db0b133e45dab69f178">
+        <source>Mute the instance by your instance</source>
+        <target>Silenciar esta instancia por su instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="676221cdabd4805901343976988c028dbf71b20a">
+        <source>Unmute the instance by your instance</source>
+        <target>Dejar de silenciar esta instancia por su instancia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0c0f5bbcd2386018ec057877f9d3c5c2c9880cac">
         <source>Request is too large for the server. Please contact you administrator if you want to increase the limit size.</source>
         <target>La petición es demasiado grande para el servidor. Por favor contacta con tu administrador si quieres aumentar el límite de tamaño.</target>
@@ -4103,6 +6068,76 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
+        <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target>Suscrito a <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Suscrito</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3e7735fa326fcdc9e1188b6d9ff4b4329312fc26">
+        <source>Unsubscribed from <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target>Ya no está suscrito a <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Ya no está suscrito</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
+        <source>Moderator</source>
+        <target>Moderador</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d4195053fd38eacf6dee1fc507296928978cc8fb">
+        <source>Only I can see this video</source>
+        <target>Soy el único que pueda ver este vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="17b62592e5fcabb5235bb25c4883a827ab37cf70">
+        <source>Only people with the private link can see this video</source>
+        <target>Solo las personas que tengan el vínculo privado pueden ver este vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="15be15cbdc6e960f57e801f457c19165ab39632b">
+        <source>Anyone can see this video</source>
+        <target>Todos pueden ver este vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>¡Le damos la bienvenida! Ahora revise sus correos electrónicos para verificar su cuenta y terminar el proceso de registro. </target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>¡Está conectado como <x id="INTERPOLATION" equiv-text="{{username}}"/>!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>Video to import updated.</target>
@@ -4131,23 +6166,23 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Info</target>
+      <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
+        <source>Upload cancelled</source>
+        <target>Subida cancelada</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
-        <source>Upload cancelled</source>
-        <target>Subida cancelada</target>
+      <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
+        <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
+        <target>Con este vídeo, está pasando su cuota de espacio (tamaño del vídeo: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, espacio utilizado: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, cuota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Lo sentimos pero PeerTube no puede manejar vídeos &gt; 8 GB</target>
+      <trans-unit id="c980896ac8e08e9751545db1b7ef0e93fb8a52cd">
+        <source>Your daily video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{quotaUsedDaily}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{quotaDaily}}"/>)</source>
+        <target>Con este vídeo, su cuota de espacio diario ha sido excedido (tamaño del vídeo: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, espacio usado: <x id="INTERPOLATION_1" equiv-text="{{quotaUsedDaily}}"/>, cuota: <x id="INTERPOLATION_2" equiv-text="{{quotaDaily}}"/>)</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4173,6 +6208,13 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="73c33d602da89a33d353d686f36c2fff39f0aee3">
+        <source>Video blacklisted.</source>
+        <target>Vídeo bloqueado</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ef90545bc832876c0d7f9a10363c75137472bbb5">
         <source>Copied</source>
         <target>Copiado</target>
@@ -4187,6 +6229,27 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="aca77c42f255d4bc6e95c12c5d656070726c6c2f">
+        <source>Start at <x id="INTERPOLATION" equiv-text="{{timestamp}}"/></source>
+        <target>Iniciar a <x id="INTERPOLATION" equiv-text="{{timestamp}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0e65067fdcc9d8725a41896cb1e229d1415a45f6">
+        <source>Like the video</source>
+        <target>Colocar Me gusta a este vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1a999e06e1aca0a70cd7d0e3e5c2c63d0e1885c8">
+        <source>Dislike the video</source>
+        <target>Eliminar Me gusta de este vídeo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="f1abd89c9280323209e939fa9c30f6e5cda20c95">
         <source>Do you really want to delete this video?</source>
         <target>¿De verdad quieres eliminar este vídeo?</target>
@@ -4215,5 +6278,12 @@ Enhorabuena, el vídeo sera importado con BitTorrent! Ya puedes añadir informac
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1b157e15c434469d91e56d027b78bf69c9983165">
+        <source>Videos from your subscriptions</source>
+        <target>Vídeos desde sus suscripciones</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
     </body>
   </file></xliff>
\ No newline at end of file
index d30351a353e8395c768f1a759d53aefe662d5ca9..0393ab85c5219859a3b6603ede007a750c7db0d7 100644 (file)
         <source>Password</source>
         <target>Pasahitza</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Hasi saioa</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Bidali e-mail bat nire pasahitza berrezartzeko</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
           <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7fe213724c4c0a4112c40c673884acb98a0a3b92">
+        <source>I am at least 16 years old and agree to the &lt;a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'&gt;Terms&lt;/a&gt; of this instance</source>
+        <target>16 urte edo gehiago ditut eta onartzen ditut instantzia honen &lt;a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'&gt;erabilera badintzak&lt;/a&gt;</target>
+        <context-group name="null">
+          <context context-type="linenumber">55</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="717a5e3574fec754fbeb348c2d5561c4d81facc4">
         <source>Signup</source>
         <target>Eman izena</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7c603b9ed878097782e2b8908f662e2344b46061">
+        <source>
+          Filters
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </source>
+        <target>
+          Iragazkiak
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e2dbf0426cbb0b573faf49dffeb7d5bdf16eda5d">
         <source>
     No results found
         <source>Change the language</source>
         <target>Aldatu hizkuntza</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Nire profil publikoa
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Nire kontua
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Nire bideoak
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Amaitu saioa
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Sortu kontu bat</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Harpidetzak</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Gainbegirada</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Joerak</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Gehitutako azkenak</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Tokikoa</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Gehiago</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administrazioa</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>Erakutsi teklatu-lasterbideak</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>Txandakatu interfaze iluna</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>Bistaratu zerrendatu gabeko bideoak eta bideo pribatuak</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Emaitzarik ez.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instantziari buruz
-</target>
+          Utzi
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Bidali</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Baldintzak</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Erabiltzaile berriek izena ematea onartzen da eta</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      instantzia honek <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> eskaintzen ditu erabiltzaileen bideoetarako oinarrizko kuota gisa.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      instantzia honek mugagabeko espazioa eskaintzen du bere erabiltzaileen bideoetarako.     </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
@@ -1239,6 +1235,19 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
           <context context-type="linenumber">83</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b1372cb61ca791a0f7f95bf31c86c97df142adc4">
+        <source>
+    PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released.
+    In the meantime, we want to test different ideas related to this issue:
+  </source>
+        <target>
+    PeerTube oso berria da oraindik, eta egonkortzen denerako babes-neurri egokienak ezarri nahi ditugu.
+    Bitartean, hainbat ideia saiatu nahi ditugu gai honen inguruan:
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">85</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="d32608aba08c6bb3cc4e4e8ec6223e5f4e78ca19">
         <source>Set a limit to the number of peers sent by the tracker</source>
         <target>Tracker-ak bidaltzen dituen berdin kopurua mugatzea</target>
@@ -1362,49 +1371,49 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Short description</source>
         <target>Deskripzio laburra</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Lehenetsitako bezeroaren ibilbidea</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Bideoen gainbegirada</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Joera diren bideoak</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Azkenaldian gehitutako bidoeak</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Tokiko bideoak</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Eduki hunkigarria duten bideoen politika</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
@@ -1439,23 +1448,44 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Signup enabled</source>
         <target>Izena ematea gaituta</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Izena emateko e-mail helbidea baieztatu behar da</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Izena emateko muga</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Erabiltzaileak</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Erabiltzailearen lehenetsitako bideo-kuota</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Erabiltzailearentzat lehenetsitako eguneko igoera muga</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Inportatu</target>
@@ -1467,49 +1497,28 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>HTTP URL bidezko bideoen inportazioa gaituta (adibidez YouTube)</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Bideoa torrent fitxategia edo magnet URL bidez inportatzea gaituta</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administratzailea</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Administratzailearen e-maila</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Erabiltzaileak</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Erabiltzailearen lehenetsitako bideo-kuota</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Erabiltzailearentzat lehenetsitako eguneko igoera muga</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
@@ -1530,21 +1539,21 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Your Twitter username</source>
         <target>Zure Twitter erabiltzaile-izena</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Edukia argitaratuko den webgune edo plataformarentzat Twitter kontua adierazten du.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Twitter-ek onartutako instantzia</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
@@ -1558,35 +1567,35 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Transcoding</source>
         <target>Transkodeketa</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transkodeketa gaituta</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Transkodeketa desgaitzen baduzu, erabiltzaileen bideo askok ez dute funtzionatuko!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Transkodetze hariak</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target><x id="INTERPOLATION" equiv-text="{{resolution}}"/> bereizmena gaituta</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
@@ -1601,82 +1610,47 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Fitxategi batzuk ez dira federatzen (aurrebistak, azpitituluak). Zuzenean jatorrizko instantziatik jasotzen ditugu eta cachean gorde.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Aurrebisten cachearen tamaina</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Bideoaren azpitituluen cachearen tamaina</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Pertsonalizazioak</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>IdatziJavaScript kodea zuzenean.&lt;br /&gt;Adibidez: &lt;pre&gt;console.log('nire instantzia zoragarria da');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                Idatzi CSS kodea zuzenean. Adibidez:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Idatzi aurretik &lt;em&gt;#custom-css&lt;/em&gt; estiloak gainidazteko. Adibidez:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
@@ -1685,21 +1659,21 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Advanced configuration</source>
         <target>Konfigurazio aurreratua</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Eguneratu konfigurazioa</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Konfigurazioa baliogabea dela dirudi. Bilatu zer egon daitekeen gaizki fitxa desberdinetan begiratuz.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
@@ -1984,11 +1958,25 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>Erabiltzailearen e-mail helbidea baieztatu behar da saioa hasi aurretik</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>Erabiltzailearen e-mail helbidea baieztatuta dago / Erabiltzaileak e-mail helbidea baieztatu gabe saioa hasi dezake</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
         <source>Ban reason:</source>
         <target>Debekatzeko arrazoia:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
@@ -2055,7 +2043,7 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Actions</source>
         <target>Ekintzak</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
@@ -2090,14 +2078,14 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Data <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Zerrenda beltzean sartzeko arrazoia:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
@@ -2149,134 +2137,64 @@ Erabiltzaile berriek izena ematea ez da onartzen orain.</target>
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Nire ezarpenak</target>
+      <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
+        <source>Video quota:</source>
+        <target>Bideo-kuota:</target>
         <context-group name="null">
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Nire liburutegia</target>
+      <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
+        <source>Profile</source>
+        <target>Profila</target>
         <context-group name="null">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Nire kanalak</target>
+      <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
+        <source>Video settings</source>
+        <target>Bideo ezarpenak</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Nire bideoak</target>
+      <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
+        <source>Danger zone</source>
+        <target>Eremu arriskutsua</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Nire harpidetzak</target>
+      <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
+        <source>Change ownership</source>
+        <target>Aldatu jabetza</target>
         <context-group name="null">
-          <context context-type="linenumber">16</context>
+          <context context-type="linenumber">46</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Nire inportazioak</target>
+      <trans-unit id="046c4fa30411e6b1aa46dc51bf82d07b1adf14d4">
+        <source>Select the next owner</source>
+        <target>Hautatu hurrengo jabea</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>Denetarik</target>
+      <trans-unit id="a5433ae2324496bea9537caa5e8a2719d8e958d8">
+        <source>
+        Cancel
+      </source>
+        <target>
+        Utzi
+      </target>
         <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>Mutututako instantziak</target>
+      <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
+        <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> ikustaldi</target>
         <context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Jabetza aldaketak</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
-        <source>Video quota:</source>
-        <target>Bideo-kuota:</target>
-        <context-group name="null">
-          <context context-type="linenumber">4</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
-        <source>Profile</source>
-        <target>Profila</target>
-        <context-group name="null">
-          <context context-type="linenumber">8</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
-        <source>Video settings</source>
-        <target>Bideo ezarpenak</target>
-        <context-group name="null">
-          <context context-type="linenumber">15</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
-        <source>Danger zone</source>
-        <target>Eremu arriskutsua</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
-        <source>Change ownership</source>
-        <target>Aldatu jabetza</target>
-        <context-group name="null">
-          <context context-type="linenumber">46</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="046c4fa30411e6b1aa46dc51bf82d07b1adf14d4">
-        <source>Select the next owner</source>
-        <target>Hautatu hurrengo jabea</target>
-        <context-group name="null">
-          <context context-type="linenumber">9</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a5433ae2324496bea9537caa5e8a2719d8e958d8">
-        <source>
-        Cancel
-      </source>
-        <target>
-        Utzi
-      </target>
-        <context-group name="null">
-          <context context-type="linenumber">35</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Bidali</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
-        <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
-        <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> ikustaldi</target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4a806761798181e907e28ed1af053d466526800d">
@@ -2430,6 +2348,13 @@ Kanal honetara bideo bat igotzen duzunean, bideoa babesteko eremua testu honekin
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Mutututako instantziak</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Aldatu pasahitza</target>
@@ -2465,6 +2390,13 @@ Kanal honetara bideo bat igotzen duzunean, bideoa babesteko eremua testu honekin
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d044c51156e295824813a866dba9545bdb59466b">
+        <source>Use WebTorrent to exchange parts of the video with others</source>
+        <target>Erabili WebTorrent bideoaren zatiak besteekin partekatzeko</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="fb17c44abac2d1ed2a54cdd28bae289dc0b9a1c2">
         <source>Automatically plays video</source>
         <target>Automatikoki abiatzen du bideoa</target>
@@ -2507,6 +2439,13 @@ Kanal honetara bideo bat igotzen duzunean, bideoa babesteko eremua testu honekin
           <context context-type="linenumber">18</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d1a04ba05116499d4cf59a48a282a8bcbf5b622d">
+        <source>Once you delete your account, there is no going back. Please be certain.</source>
+        <target>Behin kontua ezabatuta ez dago atzera egiterik. Ziurtatu hau dela nahi duzuna.</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="9a2f889dde4574a6883c853d1034e75891b28c45">
         <source>Delete your account</source>
         <target>Ezabatu zure kontua</target>
@@ -2621,6 +2560,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>Akatsen bat egon da</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2657,14 +2603,14 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
         <source>Publish will be available when upload is finished</source>
         <target>Argitaratzea behin igoera bukatzean egongo da erabilgarri</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Argitaratu</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2847,14 +2793,14 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
         <source>Wait transcoding before publishing the video</source>
         <target>Itxaron transkodetzeari bideoa argitaratu aurretik</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Bideoa argitaratu aurretik ez baduzu transkodetzea bukatu arte itxaroten, bideoa transkodetzea bukatu arte ezin ikustea gerta daiteke.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2868,56 +2814,81 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
         <source>Add another caption</source>
         <target>Gehitu beste azpititulu bat</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Ikusi azpitituluen fitxategia</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>Jadanik igota  ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
+        <source>Will be created on update</source>
+        <target>Eguneratzean sortuko da</target>
+        <context-group name="null">
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Ezeztatu sorkuntza</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
+        <source>Will be deleted on update</source>
+        <target>Eguneratzean ezabatuko da</target>
+        <context-group name="null">
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Ezeztatu ezabaketa</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
+        <source>
+            No captions for now.
+          </source>
+        <target>
+            Azpititulurik ez oraingoz.
+          </target>
+        <context-group name="null">
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Azpitituluak</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Igo irudia</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Igo aurrebista</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2931,14 +2902,14 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Jendeari zu nola babestu azaltzeko testu labur bat (kidetza plataforma...).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Ezarpen aurreratuak</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3005,17 +2976,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Utzi
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Partekatu</target>
@@ -3337,6 +3297,24 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="8b2bb53dfb5f059f2b68cc4ac00661a865909135">
+        <source>You are one step away from commenting</source>
+        <target>Iruzkina egitetik urrats batera zaude</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7984a44ce86b961f4f18c9a58c638f5e8f07a225">
+        <source>
+      If you have an account on this instance, you can login:
+    </source>
+        <target>
+      Instantzia honetan kontua baduzu, saioa hasi dezakezu:
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">32</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="afe0ad39fee662489f1033e53aea3e16a7e89228">
         <source>login to comment</source>
         <target>hasi saioa iruzkinak egiteko</target>
@@ -3344,6 +3322,15 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="968b02fbc645be799727de0d1ec3c6f9b11b20eb">
+        <source>
+      If you have an account on Mastodon or Pleroma, you can open it directly in their interface:
+    </source>
+        <target>Mastodon edo Pleroma sareetan kontua baduzu, zuzenean ireki dezakezu hango interfazean:</target>
+        <context-group name="null">
+          <context context-type="linenumber">41</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a607fab03e11b0e07c1640e11a1b02d7af06b285">
         <source>Highlighted comment</source>
         <target>Nabarmendutako iruzkina</target>
@@ -3358,13 +3345,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Errorea informazioa zerbitzaritik jasotzean</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Deskripziorik ez</target>
@@ -3386,13 +3366,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Errorea</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3435,13 +3408,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Arrakasta</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Konfigurazioa eguneratuta.</target>
@@ -3596,6 +3562,20 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="53cc0f4a4566c4139c65f93b5dce2fe8302e78da">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> kontua zure instantziak desmutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="468b52e3c04fb9a3d8c8213555dfcad0cbcae330">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{host}}"/> instantzia zure instantziak demutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="800cd3cdf47751b576587259ba3a1bc0a7f435b6">
         <source>Comment updated.</source>
         <target>Iruzkina eguneratua.</target>
@@ -3687,6 +3667,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>Ezarri e-maila baieztatua gisa</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
         <source>You cannot ban root.</source>
         <target>Ezin duzu root debekatu</target>
@@ -3694,6 +3681,20 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="98119091712a8ca72905e3b4c1cf60649af7565e">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{num}}"/> users?</source>
+        <target>Ziur <x id="INTERPOLATION" equiv-text="{{num}}"/> erabiltzaileei debekua kendu nahi diozula?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6121be086a51c4c73bbdd8aebdddd9744c8f1ffd">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users unbanned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> erabiltzaileei debekua kendu zaie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="911fc197949e47aa5f0541627bc319f59edd9d11">
         <source>You cannot delete root.</source>
         <target>Ezin duzu erroa ezabatu.</target>
@@ -3708,6 +3709,34 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b708d332e3f89b24745e749fa530210f0bdea329">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> erabiltzaile ezabatuta.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> erabiltzailearen e-mail helbidea baieztatua gisa ezarri da.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> kontua desmutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c6af80b42938d4a49e6f6c4f60ce26228916994c">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{host}}"/> instantzia desmutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e">
         <source>Ownership accepted</source>
         <target>Jabetza onartuta</target>
@@ -3722,6 +3751,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="466fc8cf56fd4e4e90fec4b900ef083d52bec38c">
+        <source>You current password is invalid.</source>
+        <target>Zure uneko pasahitza baliogabea da.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ca8e8cf0f1686604db3b6a2ebadab7f7b426a047">
         <source>Are you sure you want to delete your account? This will delete all you data, including channels, videos etc.</source>
         <target>Ziur kontua ezabatu nahi duzula? Honek zure datu guztiak ezabatuko ditu, kanalak, bideoak eta abar barne.</target>
@@ -3799,23 +3835,16 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Ziur <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> ezabatu nahi duzula? Kanalera igotako bideo guztiak ezabatuko dira ere.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Idatzi bideo kanalaren izena berresteko</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> bideo kanala ezabatuta.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target><x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> bideo kanala ezabatuta.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Nire bideoak</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3890,16 +3919,44 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Kanalak</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Nire liburutegia</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Bideo inportazioak</target>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Nire kanalak</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Nire harpidetzak</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Denetarik</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Jabetza aldaketak</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Nire ezarpenak</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3932,6 +3989,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b19ee83cbd2b735fd081b9aa483a890578019099">
+        <source>Toggle the left menu</source>
+        <target>Txandakatu ezkerreko menua</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b54759e30f7c1983940cdacb8eb03f102a869084">
         <source>Go to the videos overview page</source>
         <target>Joan bideoen ikuspegi orokorraren orrira </target>
@@ -3939,6 +4003,69 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1e919c88a3f889d6659288e69d3e178da0ea7ab0">
+        <source>Go to the trending videos page</source>
+        <target>Joan puri-purian dauden bideoen orrira</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="249618dcdd7fbdc863c0714e2eb9e8940bc9c37d">
+        <source>Go to the recently added videos page</source>
+        <target>Joan gehitutako azken bideoen orrira</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7e194daef3a3509128c4300d4c7c292c49ebf3f5">
+        <source>Go to the local videos page</source>
+        <target>Joan bideo lokalen orrira</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1fb6204f39a7338e5110b2f113643c9288496ba">
+        <source>Go to the videos upload page</source>
+        <target>Joan bideoak igotzeko orrira</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0ed7b40c11da9d4565af9c041df20c15bc6be97e">
+        <source>Toggle Dark theme</source>
+        <target>Txandakatu gai iluna</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="badd4b24618ccc8a34620acb9053fc654b9612b2">
+        <source>Go to my subscriptions</source>
+        <target>Joan nire harpidetzetara</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b7184b5a236618e8edd747529869c392ab6dace1">
+        <source>Go to my videos</source>
+        <target>Joan nire bideoetara</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="acf985bd42886b9b3030b5f68f0e8417c39b40a7">
+        <source>Go to my imports</source>
+        <target>Joan nire inportazioetara</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cfe3c51f0ae9385dc2ce6df740d87e5514aa9390">
+        <source>Go to my channels</source>
+        <target>Joan nire kanaletara</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="edeaa933b09690523e46977e11064e9c655d77d7">
         <source>Cannot retrieve OAuth Client credentials: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
 </source>
@@ -3955,6 +4082,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Errorea</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Berriro konektatu behar duzu.</target>
@@ -3969,6 +4103,27 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5c0c574151dc8671d9199980ee04bf65aec3b452">
+        <source>Keyboard Shortcuts:</source>
+        <target>Teklatu laster-bideak:</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Informazioa</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Arrakasta</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Erabiltzaile-izen edo pasahitz okerra.</target>
@@ -4186,6 +4341,20 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>E-maila behar da.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>E-maila baliozkoa izan behar da.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Erabiltzaile izena beha da.</target>
@@ -4207,41 +4376,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Erabiltzaile-izenak gutxienez 3 karaktere izan behar ditu.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Erabiltzaile-izenak ezin ditu 20 karaktere baino gehiago izan.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Erabiltzaile-izena minuskulaz dauden karaktere alfanumerikoak besterik ezin ditu izan.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>E-maila behar da.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>E-maila baliozkoa izan behar da.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Pasahitza gutxienez 6 karaktere luze izan behar da.</target>
@@ -4305,23 +4439,16 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Pantaila-izena gutxienez 3 karaktere luze izan behar da.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Pantaila-izena ezin da 120 karaktere baino luzeagoa izan.</target>
+      <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
+        <source>Description must be at least 3 characters long.</source>
+        <target>Deskripzioa gutxienez 3 karaktere luze izan behar da.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
-        <source>Description must be at least 3 characters long.</source>
-        <target>Deskripzioa gutxienez 3 karaktere luze izan behar da.</target>
+      <trans-unit id="a4179e366d4aa335f1ddd0a13e9109c71a9338d0">
+        <source>Description cannot be more than 1000 characters long.</source>
+        <target>Deskripzioa ezin da 1000 karaktere baino luzeagoa izan.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4361,13 +4488,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Salatzeko arrazoia ezin da 300 karaktere baino luzeagoa izan.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Moderazio iruzkina derrigorrezkoa da.</target>
@@ -4382,13 +4502,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Moderazio iruzkina ezin da 300 karaktere baino luzeagoa izan.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>Kanala derrigorrezkoa da.</target>
@@ -4445,27 +4558,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Izena gutxienez 3 karakterekoa izan behar da</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Izena ezin da20 karaktere baino luzeagoa izan</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Izenak karaktere alfanumerikoak minuskulan besterik ezin ditu izan.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>Babes testua gutxienez 3 karaktere luze izan behar da</target>
@@ -5103,6 +5195,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f9b4f2d8146c789cd40314f640ec4e88efbaf681">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users banned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> erabiltzaile debekatuta.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="3ab99e62550869aebc85661fca2faf46785263dd">
         <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> banned.</source>
         <target><x id="INTERPOLATION" equiv-text="{{username}}"/> erabiltzailea debekatuta.</target>
@@ -5124,6 +5223,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ad07d34d4aadfe03c964cec02ca1d3a921e6b603">
+        <source>If you remove this user, you will not be able to create another with the same username!</source>
+        <target>Erabiltzaile hau kentzen baduzu, ezin izango duzu erabiltzaile-izen bera duen beste bat sortu gero!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="28220fae6799ab98ef6b41af449aa9680082357a">
         <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> deleted.</source>
         <target><x id="INTERPOLATION" equiv-text="{{username}}"/> erabiltzailea ezabatuta.</target>
@@ -5131,6 +5237,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target><x id="INTERPOLATION" equiv-text="{{username}}"/> erabiltzailearen e-mail helbidea baieztatua gisa ezarri da</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
         <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> kontua mutututa.</target>
@@ -5138,6 +5251,41 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="086eda792aeb1b0d131d633b50fdd1792f5f24c6">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{host}}"/> instantzia mutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb72d6d1219e89d182e9fd09d853d83baf8d6499">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> kontua instantziak mutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8686834bc4afe42c1991c6c18f0bce174a0e17a6">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> kontua instantziak desmutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="35d3509161861a610b0895bf084c781e56ba2830">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{host}}"/> instantzia instantziak mutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="978aeec5613fa97e8a5336d3599cebb23ee5a90f">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{host}}"/> kontua instantziak desmutututa.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="4a09bf8724e7659fbb5ec33647529cdef7614bdc">
         <source>Mute this account</source>
         <target>Mututu kontu hau</target>
@@ -5222,13 +5370,6 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Harpidetuta</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/>(e)ra harpidetuta</target>
@@ -5236,9 +5377,9 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Harpidetza kenduta</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Harpidetuta</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5250,6 +5391,13 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Harpidetza kenduta</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>Moderatzailea</target>
@@ -5278,6 +5426,20 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>Ongi etorri! Egiaztatu zure e-maila kontua baieztatzeko eta izen ematea osatzeko.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target><x id="INTERPOLATION" equiv-text="{{username}}"/> gisa hasi duzu saioa!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>Inportatzeko bideoa eguneratuta.</target>
@@ -5306,23 +5468,23 @@ Ezin izan dugu bilatzen duzun orria aurkitu.
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Informazioa</target>
+      <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
+        <source>Upload cancelled</source>
+        <target>Igoera ezeztatuta</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
-        <source>Upload cancelled</source>
-        <target>Igoera ezeztatuta</target>
+      <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
+        <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
+        <target>Zure bideo-kuota bideo honekin gainditzen da (bideoaren tamaina: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, erabilita: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, kuota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Sentitzen dugu, PeerTubek ezin du 8GB baino gehiagoko bideorik kudeatu</target>
+      <trans-unit id="c980896ac8e08e9751545db1b7ef0e93fb8a52cd">
+        <source>Your daily video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{quotaUsedDaily}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{quotaDaily}}"/>)</source>
+        <target>Zure eguneko bideo-kuota bideo honekin gainditzen da (bideoaren tamaina: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, erabilita: <x id="INTERPOLATION_1" equiv-text="{{quotaUsedDaily}}"/>, kuota: <x id="INTERPOLATION_2" equiv-text="{{quotaDaily}}"/>)</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
index fe2d0b0cf913ec971b374211091ab139b75670b8..2a59cfc345b0ebf5f3ac2c554470ee51c7c55790 100644 (file)
         <source>Password</source>
         <target>گذرواژه</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>ورود</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>یک رایانامه برای بازنشانی گذرواژه برای من بفرست</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>ثبت‌نام</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e2dbf0426cbb0b573faf49dffeb7d5bdf16eda5d">
         <source>Change the language</source>
         <target>تغییر زبان</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              نمایه‌ عمومی من
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               حساب کاربری من
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               ویدئو‌های من
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               خروج
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>ساخت حساب</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>اشتراک</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>نمای‌کلی</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>مورد بحث</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>به تازگی اضافه شده</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>محلی</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>دیگر</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>مدیریت</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source><target>Toggle dark interface</target><context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>No results.</source>
         <target>بدون نتیجه.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6385c357c1de58ce92c0cf618ecf9cf74b917390">
         <source>Videos Overview</source>
         <target>نمای‌کلی ویدئو‌ها</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>ویدئو‌های محلی</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="010d24ef3c43b2d8f45a4d6cba7d73e12ee1557e">
         <source>Signup enabled</source>
         <target>ثبت‌نام فعال است</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>کاربران</target>
+        <context-group name="null">
+          <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Administrator</source>
         <target>مدیر</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>کاربران</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="99cb827741e93125476a0f5b676372d85d15b5fc">
         <source>Your Twitter username</source>
         <target>نام‌کاربری توییتر شما</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>جاوااکسریپت</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>ویديو‌های من</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
         <source>My settings</source>
         <target>تنظیمات من</target>
         <context-group name="null">
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>ویديو‌های من</target>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>راهنما</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e7815f1c4a6d3cc157a16407a48865023cc35ec0">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>راهنما</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>بارگزاری لغوشد</target>
index 55f323009663712d39969429c52c39518646f05d..23cfec618fc8279e88947edb87cc7b0fb22787c3 100644 (file)
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f3e63578c50546530daf6050d2ba6f8226040f2c">
+        <source>You don't have notifications.</source>
+        <target>Vous n'avez pas de notifications.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f79d1d9ecaab3deb3d44e23017f8283a04d2a0f3">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/> published a <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>new video<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/> a publié une <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>nouvelle vidéo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="04f2cb4c88c17d5f3e5ce969479b4eba9db114cb">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been unblacklisted
+      </source>
+        <target>
+        Votre vidéo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> a été débloquée
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="65514a0efdae3b173130166416700ddeb369f37f">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been blacklisted
+      </source>
+        <target>
+        Votre vidéo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoBlacklist.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> a été bloquée
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ea67498da562ab450950a69f4331b8c4ddfd431">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>A new video abuse<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been created on video <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Un nouveau signalement<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> a été créé sur la vidéo <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.videoAbuse.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="23b7d6f08c5c3b8722ecd627c3d54f4950923156">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> commented your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> a commenté votre vidéo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2d0ee93317d4daa301eee7fec775c21c2f7b5a4b">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been published
+      </source>
+        <target>
+        Votre vidéo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> a été publiée
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="371391b88724e5ee455582f07eb97728e371f24a">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Your video import<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> succeeded
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Votre vidéo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> a été importée
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="56e72a0a79d53e9ff8d5f92528664bcb2cf1363a">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Your video import<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> failed
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>L'importation de votre vidéo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> <x id="INTERPOLATION" equiv-text="{{ notification.videoImportIdentifier }}"/> a échoué
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d7f123ae20ca6bfb5ac0f897b90423fdc52d8e78">
+        <source>
+        User <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.account.name }}"/> registered<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> on your instance
+      </source>
+        <target>
+        L'utilisateur <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.account.name }}"/> a créé un compte<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> sur votre instance
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9a05dc5206104085b2b6654fb9137291194a72ef">
+        <source>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.actorFollow.follower.displayName }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> is following
+
+        <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>
+          your channel <x id="INTERPOLATION_1" equiv-text="{{ notification.actorFollow.following.displayName }}"/>
+        <x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+        <x id="START_TAG_NG-CONTAINER_1" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>your account<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+      </source>
+        <target>
+        <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.actorFollow.follower.displayName }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> suit
+
+        <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>
+          votre chaîne <x id="INTERPOLATION_1" equiv-text="{{ notification.actorFollow.following.displayName }}"/>
+        <x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+        <x id="START_TAG_NG-CONTAINER_1" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>votre compte<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98b174525a2c9b4de0a510fb6eae7bdf285c0c7f">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> mentioned you on <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>video <x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> vous a mentionné sur la vidéo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/> <x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">52</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="473117e02024f603dc2dbd24a0bf81f8722cf8dc">
+        <source>
+      <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+    </source>
+        <target>
+      <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="4b3963c6d0863118fe9e9e33447d12be3c2db081">
         <source>Unlisted</source>
         <target>Non répertoriée</target>
           <context context-type="linenumber">25</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="c078d4901a5fac169665947cc7a6108b94dd80c7">
+        <source><x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="12910217fdcdbca64bee06f511639b653d5428ea">
         <source>
     Login
         <source>Password</source>
         <target>Mot de passe</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Se connecter</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
           <context context-type="linenumber">57</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f876804a6725f7b950c8e4c56ca596206856e6a2">
+        <source>
+      We are sorry, you cannot recover you password because your instance administrator did not configure the PeerTube email system.
+    </source>
+        <target>
+      Désolé, vous ne pouvez pas récupérer votre mot de passe car l'administrateur de votre instance n'a pas configuré le système de mails de PeerTube.
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">63</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="244aae9346da82b0922506c2d2581373a15641cc">
         <source>Email</source>
         <target>Courriel</target>
         <source>Send me an email to reset my password</source>
         <target>M'envoyer un courriel pour réinitialiser mon mot de passe</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Créer un compte</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>Changer la langue</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c98d728375e7bd5b166d1aeb29485ef8b5d6e28">
+        <source>
+    Help to translate PeerTube!
+  </source>
+        <target>
+    Aidez à traduire PeerTube !
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Mon profil public
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Mon compte
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Mes vidéos
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Se déconnecter
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Créer un compte</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Abonnements</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Vue d'ensemble</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Tendances</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Récemment ajoutées</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Locales</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Plus</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administration</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>Montrer les raccourcis clavier</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>(Dés)activer le thème sombre</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2dc8a0a3763cd5c456c84630fc335398c9b86771">
+        <source>View your notifications</source>
+        <target>Voir vos notifications</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab">
+        <source>Notifications</source>
+        <target>Notifications</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="341e026e3f317aa3164916cc63a059c961a78b81">
+        <source>Update your notification preferences</source>
+        <target>Mettre à jour vos préférences de notification</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3d1b5c9cd76948c04fdb7bb3fe51b6c1242c1bd5">
+        <source>See all your notifications</source>
+        <target>Voir toutes vos notifications</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>Afficher les vidéos privées et non répertoriées</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Aucun résultat.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="5fea66be16da46ed7a0775e9a62b7b5e94b77473">
+        <source>Contact <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> administrator</source>
+        <target>Contacter l'administrateur de <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="533b2b9a76ee1335cb44c01f0bfd50d43e9400b0">
+        <source>Your name</source>
+        <target>Votre nom</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0b892c7805a1c5afc0b7c21c3449760860fe7f3d">
+        <source>Your email</source>
+        <target>Votre mail</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d2815c9b510b8172d8cac4008b9709df69d636df">
+        <source>Your message</source>
+        <target>Votre message</target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  À propos de l'instance : <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> </target>
+          Annuler
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Envoyer</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="89e55a86cb300f06139ff398c9c8bb7376f78b07">
+        <source>About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance</source>
+        <target>À propos de l'instance <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3c1aff50472b313c70a72ee02c081b8eeb1c616c">
+        <source>Contact administrator</source>
+        <target>Contact de l'administrateur</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Conditions d'utilisation</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>La création de comptes utilisateurs est autorisée et</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
+      <trans-unit id="7a0a7b5a5bc9ee7b7e415f87ecc404145fb51dff">
         <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
+          this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
+        </source>
         <target>
-      cette instance fournit un quota de base de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> pour les vidéos de ses utilisateurs.
-    </target>
+          cette instance propose un quota d'espace de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> pour les vidéos de ses utilisateurs.
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
+      <trans-unit id="7bee5dd41c0007820f150ee33b8257dc1aac281b">
         <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
+          this instance provides unlimited space for the videos of its users.
+        </source>
         <target>
-      cette instance met à disposition de ses utilisateurs un espace de stockage vidéo illimité.
-    </target>
+          cette instance propose un espace illimité pour les vidéos de ses utilisateurs.
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">31</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
+      <trans-unit id="b6e2ede24a2ee0f6ba2f1924ede2ae408ffc2574">
         <source>
-    User registration is currently not allowed.
-  </source>
+        User registration is currently not allowed.
+      </source>
         <target>
-    Vous ne pouvez pas créer de comptes utilisateurs pour le moment.
-  </target>
+        La création de nouveaux compte n'est pas autorisée pour l'instant.
+      </target>
         <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Courte description</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Route du client par défaut</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Vue d'ensemble des vidéos</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Vidéos tendance</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Vidéos récemment ajoutées</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Vidéos locales</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Politique concernant les vidéos ayant du contenu sensible</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Enregistrement activé</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>L'inscription requiert la vérification par courriel</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limitation des enregistrements</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Utilisateurs</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Quota de vidéos par défaut par utilisateur </target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>La limite journalière de téléversement est atteinte</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importer</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>Import de vidéo via une URL (YouTube par exemple) activé</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Import de vidéo avec un fichier torrent ou URL magnet activé</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administrateur</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Email de l'administrateur</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Utilisateurs</target>
+      <trans-unit id="f9bda6652199995a4bd4424f2e35b748eb0bda8a">
+        <source>Enable contact form</source>
+        <target>Activer le formulaire de contact</target>
         <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Quota de vidéos par défaut par utilisateur </target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>La limite journalière de téléversement est atteinte</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">169</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Votre identifiant Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indique le compte Twitter pour le site ou la plateforme sur laquelle le contenu a été publié.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instance sur la liste blanche de Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1276a50033dfc7a71290086d0f57d89e3438e6b">
+        <source>If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.&lt;br /&gt;
+        If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.&lt;br /&gt;&lt;br /&gt;
+        Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on &lt;a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'&gt;https://cards-dev.twitter.com/validator&lt;/a&gt; to see if you instance is whitelisted.</source>
+        <target>Si votre instance est autorisée par Twitter, un lecteur de vidéo sera inséré dans le flux Twitter pour les partages de vidéo depuis PeerTube.&lt;br /&gt;
+        Si votre instance n'est pas autorisée, une carte sera inséré avec une image et un lien vers votre instance PeerTube.&lt;br /&gt;&lt;br /&gt;
+        Selectionnez cette case, sauvegardez la configuration et pour tester si votre instance est autorisée par Twitter, insérez l'URL d'une vidéo de votre instance (https://example.com/videos/watch/blabla) sur &lt;a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'&gt;https://cards-dev.twitter.com/validator&lt;/a&gt;.</target>
+        <context-group name="null">
+          <context context-type="linenumber">200</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Encodage</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Encodage activé</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Si vous désactivez le transcodage, de nombreuses vidéos d'utilisateurs ne fonctionneront pas !</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0050a55afb9c565df1f9b3f750c2d4adb697698f">
+        <source>Allow additional extensions</source>
+        <target>Permettre des extensions additionnelles</target>
+        <context-group name="null">
+          <context context-type="linenumber">231</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9b82c3a407ee5a98c92483fbd987be8db8384c33">
+        <source>Allow your users to upload .mkv, .mov, .avi, .flv videos</source>
+        <target>Autoriser vos utilisateurs à publier des vidéos .mkv, .mov, .avi et .flv.</target>
+        <context-group name="null">
+          <context context-type="linenumber">232</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Nombre de threads pour l'encodage</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Résolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> activée</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Certain fichiers ne sont pas fédérés (miniature, sous-titre). Nous les récupérons directement depuis l'instance d'origine et nous les gardons en cache.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Taille du cache des prévisualisations </target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Taille du cache des sous-titres</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personnalisations</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Écrivez directement du code JavaScript.&lt;br /&gt;Exemple : &lt;pre&gt;console.log('mon instance est super géniale');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
+      <trans-unit id="d7caa08cd9b3119881bbaec3f5a3c5707f573dde">
         <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
+                    Write directly CSS code. Example:&lt;br /&gt;
+                    &lt;pre&gt;
+          body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            background-color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
 
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
+                    Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
+                    &lt;pre&gt;
+          #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
+                  </source>
         <target>
-                Écrivez directement du code CSS. Exemple :&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
+                    Écrivez directement du code CSS. Par exemple:&lt;br /&gt;
+                    &lt;pre&gt;
+          body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            background-color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
 
-                Ajoutez le préfixe &lt;em&gt;#custom-css&lt;/em&gt; pour remplacer les styles. Exemple:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
+                    Ajoutez le préfixe &lt;em&gt;#custom-css&lt;/em&gt; pour surcharger les styles. Par exemple:
+                    &lt;pre&gt;
+          #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
+            color: red;
+          <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
+                    &lt;/pre&gt;
+                  </target>
         <context-group name="null">
-          <context context-type="linenumber">297</context>
+          <context context-type="linenumber">311</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Configuration avancée</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Mettre à jour la configuration</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Il semblerait que la configuration soit invalide. Merci de chercher des erreurs potentielles dans les différents onglets.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>L'adresse mail de l'utilisateur doit être vérifiée afin de se connecter</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>L'adresse mail de l'utilisateur est vérifié / L'utilisateur peut se connecter sans vérification mail</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
         <source>Ban reason:</source>
         <target>Raison du bannissement :</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>Actions</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Raison de mise sur liste noire :</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Mes paramètres</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Ma bibliothèque</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Mes chaînes</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Mes vidéos</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Mes abonnements</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Mes imports</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>Divers</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>Instances muettes</target>
-        <context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Changements de propriétaires</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Quota de vidéos :</target>
         <source>Profile</source>
         <target>Profil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Paramètres de la vidéo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Zone dangereuse</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Envoyer</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> vues</target>
@@ -2480,6 +2712,55 @@ Quand vous mettrez en ligne une vidéo sur cette chaîne, la vidéo affichera au
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Instances muettes</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e8e93a7ae9a47c035bf5170b105c418b1deae530">
+        <source>History enabled</source>
+        <target>Historique activé</target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0f1fd6758625c6a39d796378d362cdcc2b092123">
+        <source>Delete history</source>
+        <target>Supprimer l'historique</target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6b4dc5732f1f2211833d4b5e76deb5985f3749af">
+        <source>You don't have videos history yet.</source>
+        <target>Vous n'avez pas d'historique de vidéos pour l'instant.</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6aec8cb024acc333218d72f279caa8ea623bb628">
+        <source><x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ video.views | myNumberFormatter }}"/> vues</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3a6903ba6b8cf2d828d0c86fd1feb09a27be4105">
+        <source>Notification preferences</source>
+        <target>Préférences de notification</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1da23f4068fd3796fbcb24d0c42bb62f92c96829">
+        <source>Mark all as read</source>
+        <target>Tout marqué comme lu</target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Changer le mot de passe</target>
@@ -2578,6 +2859,20 @@ Quand vous mettrez en ligne une vidéo sur cette chaîne, la vidéo affichera au
           <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="dd3b6c367381ddfa8f317b8e9b31c55368c65136">
+        <source>Activities</source>
+        <target>Activités</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="847dffd493abbb2a5c71f3313f0eb730dd88a355">
+        <source>Web</source>
+        <target>Web</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e242e3e8608a3c4a944327eb3d5c221dc6e4e3cd">
         <source>
   Sorry, but we couldn't find the page you were looking for.
@@ -2686,6 +2981,13 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>Désolé, mais quelque chose s'est mal passé</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2722,14 +3024,14 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
         <source>Publish will be available when upload is finished</source>
         <target>Vous pourrez publier cette vidéo lorsque l'envoi sera terminé</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publier</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2912,14 +3214,14 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
         <source>Wait transcoding before publishing the video</source>
         <target>Attendre l'encodage avant de publier la vidéo</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Si vous décidez de ne pas attendre la fin du traitement avant la publication de la vidéo, elle pourrait bien être injouable.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2933,49 +3235,49 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
         <source>Add another caption</source>
         <target>Ajouter un nouveau sous-titre</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Voir le fichier de sous-titres</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>Déjà téléversé    ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
         <source>Will be created on update</source>
         <target>Sera créé après la mise à jour</target>
         <context-group name="null">
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Annuler la création</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
         <source>Will be deleted on update</source>
         <target>Sera supprimé après la mise à jour</target>
         <context-group name="null">
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Annuler la suppression</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
@@ -2986,28 +3288,28 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
             Pas de sous-titres pour le moment.
           </target>
         <context-group name="null">
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Sous-titres</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Téléverser une vignette</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Téléverser un aperçu</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -3021,14 +3323,14 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Courte description des moyens qu'ont les utilisateurs de vous soutenir (financement participatif, etc.).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Paramétrage avancé</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3095,15 +3397,17 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
+      <trans-unit id="827b1376aa35c7a7de90f7724d6a51ccfa20c908">
         <source>
-          Cancel
-        </source>
+      Your report will be sent to moderators of <x id="INTERPOLATION" equiv-text="{{ currentHost }}"/>.
+      <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/> It will be forwarded to origin instance <x id="INTERPOLATION_1" equiv-text="{{ originHost }}"/> too.<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+    </source>
         <target>
-          Annuler
-        </target>
+      Votre signalement sera envoyé aux modérateurs de <x id="INTERPOLATION" equiv-text="{{ currentHost }}"/>.
+      <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/> Il sera également transmis à l'instance d'origine <x id="INTERPOLATION_1" equiv-text="{{ originHost }}"/><x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+    </target>
         <context-group name="null">
-          <context context-type="linenumber">19</context>
+          <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
@@ -3491,9 +3795,23 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Erreur lors de la récupération des informations 'à propos' du serveur</target>
+      <trans-unit id="e0e3a472479c8ce1b78f682ffadbe59daf04d331">
+        <source>Cannot get about information from server</source>
+        <target>Impossible d'obtenir la description du serveur</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9e601a3b227bb70afbb9b59cd43547b710af1e10">
+        <source>Your message has been sent.</source>
+        <target>Votre message a été envoyé</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8d6d4f48dae547bb32e0669cda5a665dc8db536c">
+        <source>You already sent this form recently</source>
+        <target>Vous avez déjà rempli ce formulaire récemment</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3519,13 +3837,6 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Erreur</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3559,18 +3870,11 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
         <target>1080p</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="421a937491f19774d17eefa1d24816dae1a9f111">
-        <source>Auto (via ffmpeg)</source>
-        <target>Auto (avec ffmpeg)</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Réussite</target>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="421a937491f19774d17eefa1d24816dae1a9f111">
+        <source>Auto (via ffmpeg)</source>
+        <target>Auto (avec ffmpeg)</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3841,6 +4145,13 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>Définir l'adresse mail comme vérifiée</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
         <source>You cannot ban root.</source>
         <target>Vous ne pouvez pas bannir root.</target>
@@ -3883,6 +4194,13 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> adresses mail d'utilisateurs ont été vérifiées.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
         <target>Compte <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> réactivé.</target>
@@ -3897,6 +4215,48 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="80057baa3b97a4349304bdaa0a880e6f4778561f">
+        <source>My videos history</source>
+        <target>Mon historique de vidéos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="05f6dda1754741495451b8658bd2248856765d95">
+        <source>Videos history is enabled</source>
+        <target>Historique de vidéos activé</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6bb9ade8637c5e35fb5cb36cf7dbec71c65d4013">
+        <source>Videos history is disabled</source>
+        <target>Historique de vidéos désactivé</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8453a7a55b8b23bbbc293cd0939fb59a73307de8">
+        <source>Delete videos history</source>
+        <target>Supprimer l'historique de vidéos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f8f86df8a1ae711944c3ab819bb19bf360dfa7a4">
+        <source>Are you sure you want to delete all your videos history?</source>
+        <target>Êtes vous sur de vouloir supprimer toutes les vidéos de votre historique ?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="195d5ba6c8bd05762d9318d0afd0b094fd776164">
+        <source>Videos history deleted</source>
+        <target>Historique de vidéos supprimé</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e">
         <source>Ownership accepted</source>
         <target>Changement de propriété accepté</target>
@@ -3946,6 +4306,76 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7c193bf704577e514b63497c4f366511afdb6585">
+        <source>New video from your subscriptions</source>
+        <target>Nouvelle vidéo depuis vos souscriptions</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ba897defa2e6c34d5ee3d10edf8d797a35e7e3e5">
+        <source>New comment on your video</source>
+        <target>Nouveau commentaire sur votre vidéo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0a9650640ddd1dfadfe456891d6d4f6093ad428e">
+        <source>New video abuse on local video</source>
+        <target>Nouveau signalement sur une vidéo locale</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="abac8b7629cfcd85bff25770f83ea229f646f996">
+        <source>One of your video is blacklisted/unblacklisted</source>
+        <target>Une de vos vidéos a été bloquée/débloquée</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f3eff4df9e4aa9dab411e6eb83833a33016a88bc">
+        <source>Video published (after transcoding/scheduled update)</source>
+        <target>Vidéo publiée (après transcodage / mise à jour programmée)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ec7ddc265da1df78011ae7677d62a2ae10aef7a4">
+        <source>Video import finished</source>
+        <target>Import de vidéo terminé</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c327bbac87cca61f5c52f5825d564878e98b9034">
+        <source>A new user registered on your instance</source>
+        <target>Nouveau compte créé sur votre instance</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f407b90e99a04e2e0d1872c02f01eadbf53e08e2">
+        <source>You or your channel(s) has a new follower</source>
+        <target>Vous (ou votre chaîne) avez un nouveau suiveur</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14c3050a9da4c1bc49d555c45d5660804d08e83b">
+        <source>Someone mentioned you in video comments</source>
+        <target>Quelqu'un vous a mentionné dans les commentaires d'une vidéo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a0f04081717f5f00c0a2c723903c3a2d4c296401">
+        <source>Preferences saved</source>
+        <target>Préférences sauvegardées</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="db4ff52375f6a25ad0472e92754c8c265ae47c6b">
         <source>Profile updated.</source>
         <target>Profil mis à jour.</target>
@@ -3995,23 +4425,16 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Voulez-vous vraiment supprimer <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> ? Ceci supprimera aussi toutes les vidéos téléversées dans cette chaîne.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Merci de confirmer le nom de la chaîne</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Chaîne vidéo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> supprimée.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Chaîne vidéo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> supprimée.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Mes vidéos</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4086,16 +4509,58 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Chaînes</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Ma bibliothèque</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Mes chaînes</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Mes abonnements</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4f953496ca94b4f83af049ff715172df2729fb79">
+        <source>My history</source>
+        <target>Mon historique</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Divers</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Changements de propriétaires</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Mes paramètres</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Importations de vidéos</target>
+      <trans-unit id="0e2434e7d84145c4e8a930ccc4c26c3cb2887e0d">
+        <source>My notifications</source>
+        <target>Mes notifications</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4221,6 +4686,13 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Erreur</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Vous devez vous reconnecter.</target>
@@ -4242,6 +4714,20 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Info</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Réussite</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Nom d'utilisateur ou mot de passe incorrects.</target>
@@ -4459,6 +4945,62 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>Le courriel est requis.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>Le courriel doit être valide.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac451f128840b34804ea69c820dc3566f476fb33">
+        <source>Your name is required.</source>
+        <target>Votre nom doit être rempli.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1fc4633008a2431fdec891d58efcc8b865d7de1a">
+        <source>Your name must be at least 1 character long.</source>
+        <target>Votre nom doit contenir au moins un caractère.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c7b44b92c0ce3ccd2f804d001e13da399524e11b">
+        <source>Your name cannot be more than 120 characters long.</source>
+        <target>Votre nom ne peut pas contenir plus de 120 caractères.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="40b35cf927f9f9a59404a6c914ec4632690b69b2">
+        <source>A message is required.</source>
+        <target>Votre message doit être rempli.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d8d4a23f467ee3e93ca0edb1198c233ed633cf64">
+        <source>The message must be at least 3 characters long.</source>
+        <target>Votre message doit contenir au moins 3 caractères.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="07422f6141cfcabaf3c2ce77e3e063222849ef60">
+        <source>The message cannot be more than 5000 characters long.</source>
+        <target>Le message ne peut pas contenir plus de 5000 caractères.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Le nom d'utilisateur est requis.</target>
@@ -4480,37 +5022,23 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Le nom d'utilisateur doit être composé d'au moins 3 caractères.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Le nom d'utilisateur ne peut pas faire plus de 20 caractères.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Le nom d'utilisateur ne doit être composé que de caractères alphanumériques en minuscule.</target>
+      <trans-unit id="6330d25a3bc6f55dfd5177da6e681d1d3b1a2b1a">
+        <source>Username must be at least 1 character long.</source>
+        <target>Votre nom d'utilisateur doit contenir au moins un caractère.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Le courriel est requis.</target>
+      <trans-unit id="aaaf3d00c35f809eebc7fd68a3f7b8b0230b197a">
+        <source>Username cannot be more than 50 characters long.</source>
+        <target>Votre nom d'utilisateur ne peut pas contenir plus de 50 caractères.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>Le courriel doit être valide.</target>
+      <trans-unit id="6f3e95be2538a22da07beaefc39bb2195683990c">
+        <source>Username should be lowercase alphanumeric; dots and underscores are allowed.</source>
+        <target>Le nom d'utilisateur peut contenir des minuscules, des chiffres, des points et des tirets bas.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4578,16 +5106,16 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Le nom d'affichage doit être composé d'au moins 3 caractères.</target>
+      <trans-unit id="085b2d6f79819a72a2b56cada4ef5085ba51d90c">
+        <source>Display name must be at least 1 character long.</source>
+        <target>Votre nom affiché doit contenir au moins un caractère.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Le nom d'affichage ne peut pas faire plus de 120 caractères.</target>
+      <trans-unit id="5a920575b8e1067f5b11c66a4a36d3ced87756f1">
+        <source>Display name cannot be more than 50 characters long.</source>
+        <target>Votre nom affiché ne peut pas contenir plus de 50 caractères.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4641,13 +5169,6 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>La raison du signalement ne peut pas dépasser 300 caractères.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Un commentaire de modération est requis.</target>
@@ -4662,13 +5183,6 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Le commentaire de modération doit faire au plus 300 caractères.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>La chaîne est requise.</target>
@@ -4725,23 +5239,23 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Le nom doit faire au moins 3 caractères.</target>
+      <trans-unit id="b8b59b6284a14fc71268cf722ed98c62c5af4a76">
+        <source>Name must be at least 1 character long.</source>
+        <target>Le nom doit contenir au moins un caractère.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Le nom doit faire au plus 20 caractères.</target>
+      <trans-unit id="e14cd37d29f13eac7384c339e4f1df58d96e4e3d">
+        <source>Name cannot be more than 50 characters long.</source>
+        <target>Le nom ne peut pas contenir plus de 50 caractères.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Le nom doit contenir seulement des caractères alphanumériques minuscules.</target>
+      <trans-unit id="135185da003b14cbb69521f570fa617a00bbbe18">
+        <source>Name should be lowercase alphanumeric; dots and underscores are allowed.</source>
+        <target>Le nom peut contenir des minuscules, des chiffres, des points et des tirets bas.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5425,6 +5939,13 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>L'adresse mail de l'utilisateur <x id="INTERPOLATION" equiv-text="{{username}}"/> a été vérifiée</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
         <target>Comptes <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muets.</target>
@@ -5551,13 +6072,6 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Abonné</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>Abonné à <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -5565,9 +6079,9 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Désabonné</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Abonné</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5579,6 +6093,13 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Désabonné</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>Modérateur</target>
@@ -5607,6 +6128,20 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>Bienvenue ! Veuillez maintenant consulter vos mails afin de vérifier votre compte et compéter l'inscription. </target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>Vous êtes maintenant connecté en tant que <x id="INTERPOLATION" equiv-text="{{username}}"/> !</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>Les vidéos à importer ont été mises à jour</target>
@@ -5635,13 +6170,6 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Info</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Mise en ligne annulée</target>
@@ -5649,13 +6177,6 @@ Assurez-vous d'avoir les droits de diffusion de ce contenu afin d'éviter toute
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Désolé, mais PeerTube ne gère pas les vidéos d'une taille &gt; 8 Go</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>Votre quota est dépassé avec cette vidéo (taille de la vidéo : <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index e2695304ba7c9b0082505b9f4a5c4d38e94f2a1e..ab07180b2717aaf324ba51d8959e25098d47ac34 100644 (file)
         <source>Password</source>
         <target>Contrasinal</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Conectar</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Envíenme un correo para restablecer o contrasinal</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Abrir conta</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
         <source>Change the language</source>
         <target>Cambiar o idioma</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Crear unha conta</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Trending</source>
         <target>En voga</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Últimos engadidos</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Local</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Máis</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administración</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>No results.</source>
         <target>Sin resultados.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ff78f059449d44322f627d0f66df07abe476962b">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
-        <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
-  Acerca da instancia <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Description</source>
         <target>Descrición</target>
         <source>Terms</source>
         <target>Termos</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>O rexistro está aberto e</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      esta instancia proporciona unha cota inicial de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> para os vídeos das súas usuarias.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      esta instancia non limita o espazo para os videos das súas usuarias.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    En este momento non está aberto o rexistro de novas usuarias.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Descrición curta</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Ruta ao cliente por omisión</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Vídeos de moda</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Vídeos engadidos recentemente</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Vídeos en local</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Política para os vídeos con contido sensible</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Rexistro activado</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Rexistro limitado</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Usuarias</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Cota de vídeo por omisión para a usuaria</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importar</target>
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Importación de vídeo con un ficheiro torrent ou URI magnet activada</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administración</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Correo-e da Admin</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Usuarias</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Cota de vídeo por omisión para a usuaria</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>O seu alcume na Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indica a conta na Twitter para o sitio web ou plataforma para a cal o contido foi publicado.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instancia na lista blanca por Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Recodificando</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Recodificación activada</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Si desactiva a recodificación moitos vídeos das súas usuarias non funcionarán!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Fíos de recodificación</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Resolución <x id="INTERPOLATION" equiv-text="{{resolution}}"/> activada</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Algúns ficheiros non se federan (vista previa, comentarios). Recollémolos directamente desde a instancia de orixe e almacenámolos.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Tamaño da caché de vista previa</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Tamaño da caché de comentarios no vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalizacións</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Escribir código JavaScript directamente.&lt;br /&gt;Exemplo: &lt;pre&gt;console.log('a miña instancia é tremenda');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Configuración avanzada</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Actualizar configuración</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Semella que a configuración non é válida. Por favor busque os erros potenciais nas diferentes pestanas.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
index dea48b4a792dbf910604417509afa664baef35d3..c3bcc704c54b746a91353eeb12f13670378dfd82 100644 (file)
         <source>Password</source>
         <target>Password</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Accedi</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Inviami un email per resettare la password</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Registrati</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7c603b9ed878097782e2b8908f662e2344b46061">
+        <source>
+          Filters
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </source>
+        <target>
+          Filtri
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e2dbf0426cbb0b573faf49dffeb7d5bdf16eda5d">
         <source>
     No results found
         <source>Change the language</source>
         <target>Cambia lingua</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Il mio profilo pubblico
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Il mio account
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               I miei video
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Esci
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Crea un account</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Iscrizioni</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Panoramica</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Popolari</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Aggiunti di recente</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Locali</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Altro</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Amministrazione</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>Mostra scorciatoie della tastiera</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>(Dis)attiva l'interfaccia sicura</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>Mostra video privati e non elencati</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Nessun risultato.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  Riguardo all'istanza <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Annulla
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Invia</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Termini</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>È permessa la registrazione di nuovi utenti e</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      questa istanza fornisce una quota base di <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> per i video dei suoi utenti.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      questa istanza fornisce spazio illimitato per i video dei suoi utenti.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    La registrazione di nuovi utenti al momento non è permessa.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Breve descrizione</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Percorso predefinito del client</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Panoramica dei video</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Video popolari</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Video aggiunti di recente</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Video locali</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Policy su video che contengono contenuti sensibili</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Registrazione abilitata</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>La registrazione richiede una verifica via email</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limite registrazioni</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Utenti</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Quota standard per i video dell'utente</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Limite giornaliero per il caricamento</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Carica</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>Importazione video con indirizzo HTTP (es. YouTube) abilitata</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Carica video con un file torrent o un URI magnete attivo</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Amministratore</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Email Amministratore</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Utenti</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Quota standard per i video dell'utente</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Limite giornaliero per il caricamento</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Il tuo username Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indica l'account Twitter per il sito web o la piattaforma in cui il contenuto e' stato pubblicato.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Istanza inserita in white list da Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Trascrizione</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Trascrizione attivata</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Se disatitvi la trascrizione, molti video dai tuoi utenti non funzioneranno.</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Trascrizione thread</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Risoluzione <x id="INTERPOLATION" equiv-text="{{resolution}}"/> abilitata</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Alcuni file non sono federati (anteprime, sottotitoli). Li recuperiamo direttamente dall'istanza di origine e li mettiamo in cache.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Dimensione del cache per la previsualizzazione</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Dimensione </target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalizzazioni</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Scrivi direttamente  codice JavaScript .&lt;br /&gt;Esempio: &lt;pre&gt;console.log('La mia istanza spacca!');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>Configurazione avanzata</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Aggiorna configurazione</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Sembra che la configurazione sia valida. Per favore cerca potenziali errori nelle altre tab</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
       Transcoding is enabled on server. The video quota only take in account <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>original<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> video. <x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/>
       At most, this user could use ~ <x id="INTERPOLATION" equiv-text="{{ computeQuotaWithTranscoding() | bytes: 0 }}"/>.
     </source>
+        <target>
+      Il Transcoding e abilitato sul server. La quota video considera solo <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>originale<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> video. <x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/>
+      In totale, questo utente potrebbe usare ~ <x id="INTERPOLATION" equiv-text="{{ computeQuotaWithTranscoding() | bytes: 0 }}"/>.
+    </target>
         <context-group name="null">
           <context context-type="linenumber">65</context>
         </context-group>
         <source>Ban reason:</source>
         <target>Motivo ban:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>Azioni</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Data <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>motivo per essere in Blacklist:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Le mie impostazioni</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>La mia libreria</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>I miei canali</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>I miei video</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Le mie sottoscrizioni</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Le mie importazioni</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>Altro</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>Istanze silenziate</target>
-        <context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Cambi di proprietario</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Quota video:</target>
         <source>Profile</source>
         <target>Profilo</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Impostazione video</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Zona pericolosa</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Invia</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> visualizzazioni</target>
       <trans-unit id="74728de5289ea2ff3f553bc2b48f1811680b931a">
         <source>Short text to tell people how they can support your channel (membership platform...).&lt;br /&gt;&lt;br /&gt;
 When you will upload a video in this channel, the video support field will be automatically filled by this text.</source>
+        <target>Breve testo per dire alla gente come possono supportare il tuo canale (iscrizione piattaforma...).&lt;br /&gt;&lt;br /&gt;
+Quando tu carichi un video su questo canale. il campo di supporto per il video verra riempito con questo testo.</target>
         <context-group name="null">
           <context context-type="linenumber">52</context>
         </context-group>
@@ -2386,6 +2289,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Istanze silenziate</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Cambia password</target>
@@ -2616,14 +2526,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Publish will be available when upload is finished</source>
         <target>La pubblicazione sarà disponibile quando il caricamento sarà completato</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Pubblica</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2802,12 +2712,12 @@ When you will upload a video in this channel, the video support field will be au
       </trans-unit>
       <trans-unit id="7e549f41b715552ffe69b85c14a690d9d81c85f0">
         <source>Wait transcoding before publishing the video</source><target>Wait transcoding before publishing the video</target><context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source><target>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</target><context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2821,45 +2731,45 @@ When you will upload a video in this channel, the video support field will be au
         <source>Add another caption</source>
         <target>Aggiungi un'altra descrizione</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Guarda il file dei sottotitoli</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Annulla creazione</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Annulla creazione</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source><target>Captions</target><context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Carica miniatura</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source><target>Upload preview</target><context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2871,14 +2781,14 @@ When you will upload a video in this channel, the video support field will be au
       </trans-unit>
       <trans-unit id="f61f989de6fc12f99369a90800e4b5462d3f10a0">
         <source>Short text to tell people how they can support you (membership platform...).</source><target>Short text to tell people how they can support you (membership platform...).</target><context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Impostazioni avanzate</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -2938,17 +2848,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Annulla
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Condividi</target>
@@ -2970,6 +2869,9 @@ When you will upload a video in this channel, the video support field will be au
         <source>
       The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
     </source>
+        <target>
+      L'url non è sicuro (no HTTPS), quindi il video "incluso" non funzionerà su siti HTTPS  (il  browser blocca richieste verso siti HTTP su siti in cui HTTPS  è abilitato).
+    </target>
         <context-group name="null">
           <context context-type="linenumber">45</context>
         </context-group>
@@ -2992,6 +2894,9 @@ When you will upload a video in this channel, the video support field will be au
         <source>
     The video is being imported, it will be available when the import is finished.
   </source>
+        <target>
+    Il video è nella fase di import, sarà disponibile quando l'import sarà completato.
+  </target>
         <context-group name="null">
           <context context-type="linenumber">11</context>
         </context-group>
@@ -3028,6 +2933,9 @@ When you will upload a video in this channel, the video support field will be au
         <source>
                 Published <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
               </source>
+        <target>
+                Pubblicato <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> visioni
+              </target>
         <context-group name="null">
           <context context-type="linenumber">37</context>
         </context-group>
@@ -3262,13 +3170,6 @@ Altri video</target>
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Errore durante la comunicazione con il server</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Nessuna  descrizione</target>
@@ -3290,15 +3191,17 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Errore</target>
+      <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
+        <source>240p</source>
+        <target>240p</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source><target>Success</target><context-group name="null">
+      <trans-unit id="c8cfad7e7a16c57c42535331b65cb7de40d8402e">
+        <source>360p</source>
+        <target>360p</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -3316,6 +3219,48 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="54adc67482fdaa0d361a2992bc91e064dc61cc9a">
+        <source>100MB</source>
+        <target>100MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cd34ef1f476d5422f49f6ed429f61fc1cfcb1174">
+        <source>500MB</source>
+        <target>500MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a47b4beea31cac6e5970b6bc522902f545acc8b">
+        <source>1GB</source>
+        <target>1GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b26d0cac75638623098ab7e06e16b096d1f55cc8">
+        <source>5GB</source>
+        <target>5GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f9fc4e7ec6743cb6f69bea2d0859a655ed44ffae">
+        <source>20GB</source>
+        <target>20GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a56e3f92fe16d97ee4f05051ea61c466ecb51d5e">
+        <source>50GB</source>
+        <target>50GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="31dcc0c63f6234ace8caa84ae1abc33d4022122d">
         <source>10MB</source>
         <target>10MB</target>
@@ -3400,6 +3345,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4d8f527638f3e0b518a96e07d41d886bcce01246">
+        <source>enabled</source>
+        <target>attivato</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="795733aac948794cadeb3be6386882efac2c38ad">
         <source>disabled</source>
         <target>disabilitato</target>
@@ -3421,6 +3373,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="586bee8c27a761611eb05661524cc7ca944b5978">
+        <source>Delete this report</source>
+        <target>Elimina questa segnalazione</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="cf3b28ba29a907b334ab0e6dccd080a60ba23321">
         <source>Update moderation comment</source>
         <target>Modifica commento di moderazione</target>
@@ -3442,6 +3401,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="73b70e37cddaa6494d8a666b6cba90dc80595599">
+        <source>Do you really want to delete this abuse report?</source>
+        <target>Vuoi veramente eliminare questa segnalazione di abuso?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="6a7938b8780c27540ea70cc0f8f4d928c8916cf9">
         <source>Abuse deleted.</source><target>Abuse deleted.</target><context-group name="null">
           <context context-type="linenumber">1</context>
@@ -3501,6 +3467,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b708d332e3f89b24745e749fa530210f0bdea329">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> utenti eliminati.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e">
         <source>Ownership accepted</source><target>Ownership accepted</target><context-group name="null">
           <context context-type="linenumber">1</context>
@@ -3576,6 +3549,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f359f6adf6cccca7770019f947ed594169ee7d47">
+        <source>This name already exists on this instance.</source>
+        <target>Questo nome esiste già nell'istanza.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="70a67e04629f6d412db0a12d51820b480788d795">
         <source>Create</source>
         <target>Crea</target>
@@ -3590,23 +3570,16 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Vuoi veramente eliminare <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Questo eliminerá anche tutti i video caricati su questo canale.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Per favore digita il nome del canale video per confermare</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Il canale video <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> è stato cancellato.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Il canale video <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> è stato cancellato.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>I miei video</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3677,16 +3650,44 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Canali</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>La mia libreria</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>I miei canali</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Le mie sottoscrizioni</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Altro</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Cambi di proprietario</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Caricamenti video</target>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Le mie impostazioni</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3705,6 +3706,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b54759e30f7c1983940cdacb8eb03f102a869084">
+        <source>Go to the videos overview page</source>
+        <target>Vai alla pagina di anteprima dei video</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="edeaa933b09690523e46977e11064e9c655d77d7">
         <source>Cannot retrieve OAuth Client credentials: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
 </source>
@@ -3721,6 +3729,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Errore</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Devi riconnetterti.</target>
@@ -3742,6 +3757,18 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Informazioni</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source><target>Success</target><context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Username or password non corretti</target>
@@ -3959,6 +3986,20 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>L'email è richiesta.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>L'email deve essere valida.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>L'username è necessario.</target>
@@ -3980,41 +4021,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>L'username deve essere almeno di 3 caratteri.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>L'username non può essere più di 20 caratteri.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>L'username dovrebbe essere solo in  minuscolo e contenere solo alfanumerici.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>L'email è richiesta.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>L'email deve essere valida.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>La password deve essere lunga almeno  6 caratteri.</target>
@@ -4078,16 +4084,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source><target>Display name must be at least 3 characters long.</target><context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source><target>Display name cannot be more than 120 characters long.</target><context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>La descrizione deve avere al minino 3 caratteri.</target>
@@ -4130,13 +4126,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Il motivo per la segnalazione non può essere più lungo di 300 caratteri.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Il commento di moderazione è richiesto.</target>
@@ -4151,13 +4140,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Il commento di moderazione non può essere più lungo di 300 caratteri.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>Il canale è richiesto.</target>
@@ -4179,27 +4161,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Il nome deve essere al minimo lunguo di tre caratteri.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Il nome non deve superare i 20 caratteri.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Il nome deve contenire solo caratteri minuscoli ed alfanumerichi;</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="6ca60e0f6dfbc0073b0514bce7d273150b0b9e79">
         <source>Comment is required.</source>
         <target>Un commento  è necessario.</target>
@@ -4859,13 +4820,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Iscritto</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>Iscritto a <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -4873,9 +4827,9 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Disiscritto</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Iscritto</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4887,6 +4841,13 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Disiscritto</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>Moderatore</target>
@@ -4943,13 +4904,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Informazioni</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Caricamento annullato.</target>
@@ -4957,13 +4911,6 @@ Altri video</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Ci dispiace ma PeerTube non può gestire video di dimensioni &gt; 8GB</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>La tua quota è stata superata con questo video (dimensione del video: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, stai utilizzando: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index d0b592098470c96dfabd3535a3b1c28258bc8042..04bd5089a922c70db898938ff6b9f7f72bef0488 100644 (file)
         <source>Password</source>
         <target>パスワード</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>ログイン</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>新しいパスワードをメールで送る</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>サインアップ</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
       </trans-unit>
       <trans-unit id="aef5c45fb9c725573d20a6283492e6b80fd2ae96">
         <source>Change the language</source><target>Change the language</target><context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
             </source>
         <target>マイアカウント</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
             </source>
         <target>マイビデオ</target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
             </source>
         <target>ログアウト</target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>アカウントを作成する</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>サブスクリプション</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>調査</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source><target>Trending</target><context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>最近追加された</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>地元</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>多くの</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>運営</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>No results.</source>
         <target>結果がありません。</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
+        <source>
+          Cancel
+        </source>
+        <target>
+      キャンセル
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>差し出す</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Description</source>
         <target>説明</target>
         <source>Terms</source>
         <target>条項</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>ユーザー登録が可能です</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>ユーザー登録は現在受け付けておりません</target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>簡単な説明</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>ビデオの概要</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aaa900149c2ca1575ac1918d1ded33fb69830ab2">
         <source>Signup enabled</source>
         <target>サインアップが有効</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>サインアップには電子メールの確認が必要です</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>サインアップの制限</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>ユーザー</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Torrent ファイル または magnet リンクを使用して動画をインポートする</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>管理者</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>管理者の電子メール</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>ユーザー</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>あなたのTwitterユーザー名</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>トランスコード</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>トランスコードが有効になっています</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source><target>Transcoding threads</target><context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>カスタマイズ</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>高度な構成</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>設定を更新する</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
         <source>Ban reason:</source>
         <target>禁止理由:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>行動</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>私の輸入品</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>私の購読</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>所有権の変更</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
         <source>Profile</source>
         <target>プロフィール</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>ビデオ設定</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>危険区域</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">46</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>差し出す</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="17a9d3860d9ad593dd09a9f934e03999d9e76a7a">
         <source>
             Cancel
         <source>Publish</source>
         <target>出す</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0d6558176587662e9bb3b79cca57d42591cf82f9">
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-      キャンセル
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>シェア</target>
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>サーバーからのエラー</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>説明はありません</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>エラー</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>成功</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>構成が更新されました。</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>チャンネル</target>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>私の輸入品</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>所有権の変更</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>エラー</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>再接続する必要があります。</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>情報</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>成功</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>ユーザーネームまたはパスワードが違います。</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>電子メールが必要です。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>電子メールは有効である必要があります。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>ユーザー名は必須です。</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>ユーザー名は3文字以上でなければなりません。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>ユーザー名の長さは20文字を超えることはできません。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>ユーザー名は小文字の英数字でなければなりません。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>電子メールが必要です。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>電子メールは有効である必要があります。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>パスワードは6文字以上でなければなりません。</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>購読する</target>
+      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
+        <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> の登録が完了しました。</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
-        <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
-        <target><x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> の登録が完了しました。</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>購読する</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>情報</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>アップロードのキャンセル</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>申し訳ありませんが、PeerTube は動画を処理出来ません。 &gt; 8GB</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="972fc644f847cf84e4732ec012915c4cdaf865ce">
         <source>Video published.</source>
         <target>ビデオが公開されました。</target>
index 83e61a714c1f7a20944a18379f1ba9a338a6e8c2..6478b002c5ce2a9b7e1088dcff38c0455f630c39 100644 (file)
@@ -33,7 +33,7 @@
       </trans-unit>
       <trans-unit id="ngb.datepicker.next-month">
         <source>Next month</source>
-        <target>la bavla'ima'i</target>
+        <target>lo bavla'ima'i</target>
         <context-group name="null">
           <context context-type="linenumber">27</context>
         </context-group>
@@ -53,9 +53,7 @@
         </context-group>
       </trans-unit>
       <trans-unit id="ngb.pagination.first">
-        <source>««</source>
-        <target>««</target>
-        <context-group name="null">
+        <source>««</source><target>««</target><context-group name="null">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
@@ -67,9 +65,7 @@
         </context-group>
       </trans-unit>
       <trans-unit id="ngb.pagination.previous">
-        <source>«</source>
-        <target>«</target>
-        <context-group name="null">
+        <source>«</source><target>«</target><context-group name="null">
           <context context-type="linenumber">15</context>
         </context-group>
       </trans-unit>
@@ -81,9 +77,7 @@
         </context-group>
       </trans-unit>
       <trans-unit id="ngb.pagination.next">
-        <source>»</source>
-        <target>»</target>
-        <context-group name="null">
+        <source>»</source><target>»</target><context-group name="null">
           <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
@@ -95,9 +89,7 @@
         </context-group>
       </trans-unit>
       <trans-unit id="ngb.pagination.last">
-        <source>»»</source>
-        <target>»»</target>
-        <context-group name="null">
+        <source>»»</source><target>»»</target><context-group name="null">
           <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
@@ -237,6 +229,48 @@ sisti lo nu jersi pe'a</target>
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="76e1f485e6ead4c84b606f46d413878881d66ad3">
+        <source>User registration is not allowed on this instance, but you can register on many others!</source>
+        <target>.i le samtcise'u cu curmi no nu cmiveigau .i ku'i do cmeveigau fo so'i drata ka'e</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c32ef07f8803a223a83ed17024b38e8d82292407">
+        <source>Password</source>
+        <target>lo lerpoijaspu</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
+        <source>I forgot my password</source>
+        <target>.i mi nalmo'i le mi lerpoijaspu</target>
+        <context-group name="null">
+          <context context-type="linenumber">44</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6765b4c916060f6bc42d9bb69e80377dbcb5e4e9">
+        <source>Login</source>
+        <target>co'a cmisau</target>
+        <context-group name="null">
+          <context context-type="linenumber">36</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
+        <source>Forgot your password</source>
+        <target>.i mi nalmo'i le mi lerpoijaspu</target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="244aae9346da82b0922506c2d2581373a15641cc">
+        <source>Email</source>
+        <target>lo ve samymri</target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="69b6ac577a19acc39fc0c22342092f327fff2529">
         <source>Email address</source>
         <target>lo ve samymri</target>
@@ -248,7 +282,7 @@ sisti lo nu jersi pe'a</target>
         <source>Send me an email to reset my password</source>
         <target>samymri fi mi te zu'e lo nu galfi le mi japyvla</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
@@ -299,6 +333,13 @@ zbasu lo pilno</target>
           <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="717a5e3574fec754fbeb348c2d5561c4d81facc4">
+        <source>Signup</source>
+        <target>cmiveigau</target>
+        <context-group name="null">
+          <context context-type="linenumber">78</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
         <source><x id="INTERPOLATION" equiv-text="{{ pagination.totalItems | myNumberFormatter }}"/> results</source>
         <target><x id="INTERPOLATION" equiv-text="{{ pagination.totalItems | myNumberFormatter }}"/> lo te facki</target>
@@ -327,7 +368,7 @@ zbasu lo pilno</target>
         <source>Change the language</source>
         <target>galfi lo bangu</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
@@ -337,7 +378,7 @@ zbasu lo pilno</target>
         <target>
 lo predatni be mi be'o poi gubni</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
@@ -347,7 +388,7 @@ lo predatni be mi be'o poi gubni</target>
         <target>
 lo mi pilno</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
@@ -357,14 +398,14 @@ lo mi pilno</target>
         <target>
 lo mi vidvi</target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>zbasu lo pilno</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
@@ -378,28 +419,28 @@ lo mi vidvi</target>
         <source>Subscriptions</source>
         <target>lo se jersi pe'a</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>lo cabna misno</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>lo diklo</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>lo drata</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
@@ -476,7 +517,7 @@ lo mi vidvi</target>
         <source>No results.</source>
         <target>.i facki fi no da</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ff78f059449d44322f627d0f66df07abe476962b">
@@ -498,26 +539,6 @@ lo mi vidvi</target>
           <context context-type="linenumber">27</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-    .i le vi samtcise'u cu sabji lo na'e se jimte ke vidvi datni canlu lo pilno be sy.</target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    .i ca na'e curmi lo nu zbasu lo pilno</target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>
   About PeerTube
@@ -638,28 +659,28 @@ lo mi vidvi</target>
         <source>Short description</source>
         <target>lo cmalu ve skicu</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>lo diklo vidvi</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>loi javni be tu'a lo vidvi poi vasru lo ganvi poi te kajde</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>la .djavascript.</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
@@ -686,53 +707,18 @@ lo pilno</target>
           <context context-type="linenumber">12</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>lo mi se cuxna</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>lo mi te tivni</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>lo mi vidvi</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>lo se jersi pe'a be mi</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>lo se nerbei be mi</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
         <source>Profile</source>
         <target>lo predatni</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>lo se cuxna pe lo vidvi</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="73c1cefc348a6f361497210dea1ed79499fd1260">
@@ -942,14 +928,14 @@ lo pilno</target>
         <source>Cancel create</source>
         <target>co'u zbasu</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>co'u vimcu</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -963,7 +949,7 @@ lo pilno</target>
         <source>Advanced settings</source>
         <target>lo certu se cuxna</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9aafb2a928664aa7a9375fd37c533f0375f8b611">
@@ -1104,13 +1090,6 @@ lo pilno</target>
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>.i srera</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="54adc67482fdaa0d361a2992bc91e064dc61cc9a">
         <source>100MB</source>
         <target>pa no no lo megbivysamsle</target>
@@ -1195,6 +1174,27 @@ lo pilno</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="586bee8c27a761611eb05661524cc7ca944b5978">
+        <source>Delete this report</source>
+        <target>vimcu le notci</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73b70e37cddaa6494d8a666b6cba90dc80595599">
+        <source>Do you really want to delete this abuse report?</source>
+        <target>.i .au ju'o pei vimcu le malpli notci</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6a7938b8780c27540ea70cc0f8f4d928c8916cf9">
+        <source>Abuse deleted.</source>
+        <target>.i mo'u vimcu le malpli notci</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="19508af0dfbc685cbf10cf02061bb5a0f423b6fc">
         <source>Password updated.</source>
         <target>.i mo'u galfi lo japyvla</target>
@@ -1237,23 +1237,30 @@ lo pilno</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="70a67e04629f6d412db0a12d51820b480788d795">
-        <source>Create</source>
-        <target>zbasu</target>
+      <trans-unit id="3ef8bf973a9a481a08c6f0aaa875f0259b3ea645">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> created.</source>
+        <target>.i mo'u zbasu la'o ly. <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> .ly. noi vidvi te tivni</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f359f6adf6cccca7770019f947ed594169ee7d47">
+        <source>This name already exists on this instance.</source>
+        <target>.i le cmene xa'o zasti ci'e le mupli</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>.i .au ju'o pei do vimcu la'o ly. <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> .ly. .i la'e di'u vimcu ro lo vidvi ji'a poi se kibdu'a fi le vi te tivni</target>
+      <trans-unit id="70a67e04629f6d412db0a12d51820b480788d795">
+        <source>Create</source>
+        <target>zbasu</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>.i .e'o ko ciska le cmene be le vidvi te zu'e lo nu birti</target>
+      <trans-unit id="98ab64f0af924a60a48b40835c1b655bd17c6559">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> updated.</source>
+        <target>.i mo'u galfi la'o ly. <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> .ly. noi vidvi te tivni</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -1265,6 +1272,13 @@ lo pilno</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>lo mi vidvi</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="00e16d1f1c5cc936ec0881cd02cbf66aa1b4cddd">
         <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{deleteLength}}"/> videos?</source>
         <target>.i .au ju'o pei do vimcu <x id="INTERPOLATION" equiv-text="{{deleteLength}}"/> lo vidvi</target>
@@ -1279,16 +1293,23 @@ lo pilno</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>lo te tivni</target>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>lo mi te tivni</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>lo vidvi poi se nerbei</target>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>lo se jersi pe'a be mi</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>lo mi se cuxna</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -1335,6 +1356,13 @@ lo pilno</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>.i srera</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b0f24b7136e551a0deba831f1525711245b31a26">
         <source>Your password has been successfully reset!</source>
         <target>.i snada lo nu mo'u galfi le do japyvla</target>
@@ -1342,5 +1370,19 @@ lo pilno</target>
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
+        <source>The channel is required.</source>
+        <target>.i le vidvi te tivni cu sarcu</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="97afb789c1ab09074495d49aaadb92a1c3e71a16">
+        <source>Video channel is required.</source>
+        <target>.i le vidvi te tivni cu sarcu</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
     </body>
   </file></xliff>
\ No newline at end of file
index 3db9f05049c04ba80eebe209122e9e7dd93fdff8..7e7a6abdd398e04353046cddf30ee211cfcfed4f 100644 (file)
@@ -3,6 +3,244 @@
 <xliff xmlns="urn:oasis:names:tc:xliff:document:1.1" xmlns:xyz="urn:appInfo:Items" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.1 http://www.oasis-open.org/committees/xliff/documents/xliff-core-1.1.xsd" version="1.1">
   <file source-language="en-US" datatype="plaintext" original="" target-language="nl-NL">
     <body>
+      <trans-unit id="ngb.alert.close">
+        <source>Close</source>
+        <target>Sluiten</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.carousel.previous">
+        <source>Previous</source>
+        <target>Vorige</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.carousel.next">
+        <source>Next</source>
+        <target>Volgende</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.datepicker.previous-month">
+        <source>Previous month</source>
+        <target>Vorige maand</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.datepicker.next-month">
+        <source>Next month</source>
+        <target>Volgende maand</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.datepicker.select-month">
+        <source>Select month</source>
+        <target>Selecteer maand</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.datepicker.select-year">
+        <source>Select year</source>
+        <target>Selecteer jaar</target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.first">
+        <source>««</source>
+        <target>««</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.first-aria">
+        <source>First</source>
+        <target>Eerste</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.previous">
+        <source>«</source>
+        <target>«</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.previous-aria">
+        <source>Previous</source>
+        <target>Vorige</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.next">
+        <source>»</source>
+        <target>»</target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.next-aria">
+        <source>Next</source>
+        <target>Volgende</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.last">
+        <source>»»</source>
+        <target>»»</target>
+        <context-group name="null">
+          <context context-type="linenumber">36</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.pagination.last-aria">
+        <source>Last</source>
+        <target>Laatste</target>
+        <context-group name="null">
+          <context context-type="linenumber">34</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.progressbar.value">
+        <source><x id="INTERPOLATION" equiv-text="{{getPercentValue()}}"/>%</source>
+        <target><x id="INTERPOLATION" equiv-text="{{getPercentValue()}}"/>%</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.increment-hours">
+        <source>Increment hours</source>
+        <target>Verhoog uren</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.HH">
+        <source>HH</source>
+        <target>HH</target>
+        <context-group name="null">
+          <context context-type="linenumber">12</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.hours">
+        <source>Hours</source>
+        <target>Uren</target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.decrement-hours">
+        <source>Decrement hours</source>
+        <target>Verlaag uren</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.increment-minutes">
+        <source>Increment minutes</source>
+        <target>Verhoog minuten</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.MM">
+        <source>MM</source>
+        <target>MM</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.minutes">
+        <source>Minutes</source>
+        <target>Minuten</target>
+        <context-group name="null">
+          <context context-type="linenumber">33</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.decrement-minutes">
+        <source>Decrement minutes</source>
+        <target>Verlaag minuten</target>
+        <context-group name="null">
+          <context context-type="linenumber">38</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.increment-seconds">
+        <source>Increment seconds</source>
+        <target>Verhoog seconden</target>
+        <context-group name="null">
+          <context context-type="linenumber">47</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.SS">
+        <source>SS</source>
+        <target>SS</target>
+        <context-group name="null">
+          <context context-type="linenumber">50</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.seconds">
+        <source>Seconds</source>
+        <target>Seconden</target>
+        <context-group name="null">
+          <context context-type="linenumber">52</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.decrement-seconds">
+        <source>Decrement seconds</source>
+        <target>Verlaag seconden</target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.PM">
+        <source>PM</source>
+        <target>PM</target>
+        <context-group name="null">
+          <context context-type="linenumber">65</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ngb.timepicker.AM">
+        <source>AM</source>
+        <target>AM</target>
+        <context-group name="null">
+          <context context-type="linenumber">66</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d7b35c384aecd25a516200d6921836374613dfe7">
+        <source>Cancel</source>
+        <target>Annuleren</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1d19634967b06f93fd7f20c0663742f8254e6d46">
+        <source>(extensions: <x id="INTERPOLATION" equiv-text="{{ allowedExtensionsMessage }}"/>, max size: <x id="INTERPOLATION_1" equiv-text="{{ maxFileSize | bytes }}"/>)</source>
+        <target>(extensies: <x id="INTERPOLATION" equiv-text="{{ allowedExtensionsMessage }}"/>, max grootte: <x id="INTERPOLATION_1" equiv-text="{{ maxFileSize | bytes }}"/>)</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4b3963c6d0863118fe9e9e33447d12be3c2db081">
+        <source>Unlisted</source>
+        <target>Onvermeld</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ddd8a4986d2d1717a274a5a0fbed04988a819e69">
+        <source>Private</source>
+        <target>Privé</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="9d5f16f0233b39fa2cd843321407a7358c323ad8">
         <source><x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> keer bekeken</target>
           <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="450025269732888db1f04cfe6033843110ab65ee">
+        <source>
+    <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/>
+      Subscribe
+    <x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+    <x id="START_TAG_SPAN_1" ctype="x-span" equiv-text="&lt;span&gt;"/>
+      <x id="INTERPOLATION" equiv-text="{{ videoChannel.followersCount | myNumberFormatter }}"/>
+    <x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+  </source>
+        <target>
+      <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/>
+      Abonneer
+    <x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+    <x id="START_TAG_SPAN_1" ctype="x-span" equiv-text="&lt;span&gt;"/>
+      <x id="INTERPOLATION" equiv-text="{{ videoChannel.followersCount | myNumberFormatter }}"/>
+    <x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c374edf3b9228d3df6d761bdc8a289e7df0096e8">
+        <source>
+    Unsubscribe
+  </source>
+        <target>
+Abonnement opzeggen</target>
+        <context-group name="null">
+          <context context-type="linenumber">18</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9b3287f52c239cad05ec98391553e5052ba1aa66">
+        <source>Using an ActivityPub account</source>
+        <target>Een ActivityPub-account gebruiken</target>
+        <context-group name="null">
+          <context context-type="linenumber">36</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="60251958d9e05c8cc00abf9645bb0026ebbe4dc3">
+        <source>Subscribe with an account on <x id="INTERPOLATION" equiv-text="{{ videoChannel.host }}"/></source>
+        <target>Abonneer met een account op <x id="INTERPOLATION" equiv-text="{{ videoChannel.host }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e7adf422424a61b71465d183f9d44bf956482ef0">
+        <source>Subscribe with your local account</source>
+        <target>Abonneer met je lokale account</target>
+        <context-group name="null">
+          <context context-type="linenumber">40</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5047522cc670b1f4a288bce07f9b1c5061e913ed">
+        <source>Subscribe with a Mastodon account:</source>
+        <target>Abonneer met een Mastodon-account</target>
+        <context-group name="null">
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d8758664cadd6452256ca25ca0c7259074f427c1">
+        <source>Using a syndication feed</source>
+        <target>Een syndicaatfeed gebruiken</target>
+        <context-group name="null">
+          <context context-type="linenumber">48</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d5e5bc7d213694fc0414a76f0ff3085bae44268a">
+        <source>Subscribe via RSS</source>
+        <target>Abonneren met RSS</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4913054c95f5ba14c351ab1b787f7abac97bfdd3">
+        <source>
+    <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/>Remote subscribe<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+    <x id="START_TAG_SPAN_1" ctype="x-span" equiv-text="&lt;span&gt;"/>Remote interact<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+  </source>
+        <target>
+<x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/>Extern abonneren<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+    <x id="START_TAG_SPAN_1" ctype="x-span" equiv-text="&lt;span&gt;"/>Externe interactie<x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="319933e1af77ca2e35b75a5e9270a3c90e83dd4b">
+        <source>You can subscribe to the channel via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there.</source>
+        <target>Je kan op het kanaal abonneren via elke ActivityPub-mogelijke fediverse instantie. Bijvoorbeeld met Mastodon of Pleroma kan je de URL van het kanaal in de zoekbalk invullen en daar abonneren.</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2767d5461b6c622ccdeb868df8becf26bc16b99a">
+        <source>You can interact with this via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there.</source>
+        <target>Je kan hiermee interactie hebben via elke ActivityPub-mogelijke fediverse instantie. Bijvoorbeeld met Mastodon of Pleroma kan je de huidige URL in de zoekbalk typen en er daar interactie mee hebben.</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="15f046007e4fca2e8477966745e2ec4e3e81bc3b">
         <source>Video quota</source>
         <target>Videoquotum</target>
           <context context-type="linenumber">42</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="9270dfd4606fb45a991fe7716e640b6efa28ba85">
+        <source>
+          Unlimited <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>(<x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/> per day)<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+        </source>
+        <target>
+Oneindig <x id="START_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;ng-container&gt;"/>(<x id="INTERPOLATION" equiv-text="{{ dailyUserVideoQuota | bytes: 0 }}"/> per dag)<x id="CLOSE_TAG_NG-CONTAINER" ctype="x-ng-container" equiv-text="&lt;/ng-container&gt;"/>
+        </target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6a323f80f9d90a32db8ce52cc82075938c3c36f0">
+        <source>Ban</source>
+        <target>Verbannen</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb44873ad8d4c5dbad0ac2a6a50e0ceee9119125">
+        <source>Reason...</source>
+        <target>Reden...</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f21428bd564d1cacdbc737f87a8def2e2ad42251">
+        <source>
+        A banned user will no longer be able to login.
+      </source>
+        <target>
+Een verbannen gebruiker kan niet langer inloggen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="35fdca47605de8113a0db7f587f7c099abec8020">
+        <source>Ban this user</source>
+        <target>Verban deze gebruiker</target>
+        <context-group name="null">
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="12910217fdcdbca64bee06f511639b653d5428ea">
         <source>
     Login
   </source>
-        <target>Aanmelden</target>
+        <target>
+Aanmelden</target>
         <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ae3cb52bf2dee3101ee654812b5d16e8665a9453">
+        <source>Request new verification email.</source>
+        <target>Een nieuwe verificatie e-mail aanvragen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">12</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e08a77594f3d89311cdf6da5090044270909c194">
         <source>User</source>
         <target>Gebruiker</target>
         <source>
           or create an account
         </source>
-        <target>of maak een account</target>
+        <target>
+of maak een account</target>
         <context-group name="null">
           <context context-type="linenumber">18</context>
         </context-group>
         <source>
           or create an account on another instance
         </source>
-        <target>of maak een account aan op een andere server</target>
+        <target>
+of maak een account aan op een andere instantie</target>
         <context-group name="null">
           <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
       <trans-unit id="76e1f485e6ead4c84b606f46d413878881d66ad3">
         <source>User registration is not allowed on this instance, but you can register on many others!</source>
-        <target>Registratie is niet mogelijk op deze instantie, maar je kan een account maken op een van de vele anderen!</target>
+        <target>Registratie is niet mogelijk op deze instantie, maar je kan wel registreren op een van de vele anderen!</target>
         <context-group name="null">
           <context context-type="linenumber">28</context>
         </context-group>
         <source>Password</source>
         <target>Wachtwoord</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>I forgot my password</source>
-        <target>Wachtwoord vergeten</target>
+        <target>Ik ben mijn wachtwoord vergeten</target>
         <context-group name="null">
           <context context-type="linenumber">44</context>
         </context-group>
         <source>Login</source>
         <target>Aanmelden</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Forgot your password</source>
-        <target>Wachtwoord vergeten</target>
+        <target>Jouw wachtwoord vergeten</target>
         <context-group name="null">
           <context context-type="linenumber">57</context>
         </context-group>
         <source>Send me an email to reset my password</source>
         <target>Zend me een e-mail om een nieuw wachtwoord in te stellen</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>
     Reset my password
   </source>
-        <target>Wachtwoord opnieuw instellen</target>
+        <target>
+Wachtwoord opnieuw instellen</target>
         <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
         <source>
     Create an account
   </source>
-        <target>Account maken</target>
+        <target>
+Account aanmaken</target>
         <context-group name="null">
           <context context-type="linenumber">3</context>
         </context-group>
           <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="717a5e3574fec754fbeb348c2d5561c4d81facc4">
-        <source>Signup</source>
-        <target>Registratie</target>
+      <trans-unit id="26025b8081241cf85eb6516431b596df11fa66b3">
+        <source>Example: jane_doe</source>
+        <target>Bijvoorbeeld: jane-doe</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="aef5c45fb9c725573d20a6283492e6b80fd2ae96">
-        <source>Change the language</source>
-        <target>Taal veranderen</target>
+      <trans-unit id="7fe213724c4c0a4112c40c673884acb98a0a3b92">
+        <source>I am at least 16 years old and agree to the &lt;a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'&gt;Terms&lt;/a&gt; of this instance</source>
+        <target>Ik ben minstens 16 jaar oud en accepteer de &lt;a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'&gt;Voorwaarden&lt;/a&gt; van deze instantie</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
-        <source>Create an account</source>
-        <target>Account maken</target>
+      <trans-unit id="717a5e3574fec754fbeb348c2d5561c4d81facc4">
+        <source>Signup</source>
+        <target>Registratie</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
-        <source>Videos</source>
-        <target>Video's</target>
+      <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
+        <source>Features found on this instance</source>
+        <target>Kenmerken van deze instantie</target>
         <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">67</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
-        <source>Trending</source>
-        <target>Populair</target>
+      <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
+        <source><x id="INTERPOLATION" equiv-text="{{ pagination.totalItems | myNumberFormatter }}"/> results</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ pagination.totalItems | myNumberFormatter }}"/> resultaten</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">5</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
-        <source>Recently added</source>
-        <target>Recent toegevoegd</target>
+      <trans-unit id="4c3960fb1d9b07d1db3b5bda3ee40019211830dc">
+        <source>
+          for <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ currentSearch }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </source>
+        <target>
+voor <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ currentSearch }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7c603b9ed878097782e2b8908f662e2344b46061">
+        <source>
+          Filters
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/>
+        </source>
+        <target>
+          Filters
+          <x id="START_TAG_SPAN" ctype="x-span" equiv-text="&lt;span&gt;"/><x id="INTERPOLATION" equiv-text="{{ numberOfFilters() }}"/><x id="CLOSE_TAG_SPAN" ctype="x-span" equiv-text="&lt;/span&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e2dbf0426cbb0b573faf49dffeb7d5bdf16eda5d">
+        <source>
+    No results found
+  </source>
+        <target>
+Geen resultaten gevonden</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="10341623e991a4185990a0c3c76ac2bc3543cc4a">
+        <source><x id="INTERPOLATION" equiv-text="{{ result.followersCount }}"/> subscribers</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ result.followersCount }}"/> abonnees</target>
+        <context-group name="null">
+          <context context-type="linenumber">44</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="602281e45fe8b79748e3fbf21c432379fcb58883">
+        <source><x id="INTERPOLATION" equiv-text="{{ result.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ result.views | myNumberFormatter }}"/> views</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ result.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ result.views | myNumberFormatter }}"/> weergaven</target>
+        <context-group name="null">
+          <context context-type="linenumber">55</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="aef5c45fb9c725573d20a6283492e6b80fd2ae96">
+        <source>Change the language</source>
+        <target>Taal veranderen</target>
+        <context-group name="null">
+          <context context-type="linenumber">86</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
+        <source>
+             My public profile
+            </source>
+        <target>
+Mijn openbare profiel</target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
+        <source>
+              My account
+            </source>
+        <target>
+Mijn account</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
+        <source>
+              My videos
+            </source>
+        <target>
+Mijn video's</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
+        <source>
+              Log out
+            </source>
+        <target>
+Uitloggen</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
+        <source>Create an account</source>
+        <target>Account maken</target>
+        <context-group name="null">
+          <context context-type="linenumber">37</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
+        <source>Videos</source>
+        <target>Video's</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="357064ca9d9ac859eb618e28e8126fa32be049e2">
+        <source>Subscriptions</source>
+        <target>Abonnementen</target>
+        <context-group name="null">
+          <context context-type="linenumber">45</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
+        <source>Overview</source>
+        <target>Overzicht</target>
+        <context-group name="null">
+          <context context-type="linenumber">50</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
+        <source>Trending</source>
+        <target>Populair</target>
+        <context-group name="null">
+          <context context-type="linenumber">55</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
+        <source>Recently added</source>
+        <target>Recent toegevoegd</target>
+        <context-group name="null">
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Lokaal</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
+        <source>More</source>
+        <target>Meer</target>
+        <context-group name="null">
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administratie</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
           <context context-type="linenumber">25</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4752e5e33da1c3396d3248eb8fef59bca5d00cb3">
+        <source>Show keyboard shortcuts</source>
+        <target>Laat keyboard shortcuts zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">89</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
+        <source>Toggle dark interface</source>
+        <target>Schakel donkere interface aan of uit</target>
+        <context-group name="null">
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Search...</source>
         <target>Zoeken …</target>
           <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5d43539fc358c3a548b9d487be821db73e2702ff">
+        <source>Sort</source>
+        <target>Sorteren</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98acac685fc4b2d35e5d0cf3cd224d247a756c3e">
+        <source>Published date</source>
+        <target>Datum van publicatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a02ea1d4e7424ca989929da5e598f379940fdbf2">
+        <source>Duration</source>
+        <target>Duratie</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dc67060f94f0f2b58549f54a5c07925dffd20238">
+        <source>Display sensitive content</source>
+        <target>Laat gevoelige inhoud zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">33</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4f20f2d5a6882190892e58b85f6ccbedfa737952">
+        <source>Yes</source>
+        <target>Ja</target>
+        <context-group name="null">
+          <context context-type="linenumber">37</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3d3ae7deebc5949b0c1c78b9847886a94321d9fd">
+        <source>No</source>
+        <target>Nee</target>
+        <context-group name="null">
+          <context context-type="linenumber">42</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="607de17c2a755f65775881c19e276e7c933bcf94">
         <source>Category</source>
         <target>Categorie</target>
           <context context-type="linenumber">182</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="c8d58c4fbe23e51af3dc8f58cb4a81eac20739e8">
+        <source>All of these tags</source>
+        <target>Al deze tags</target>
+        <context-group name="null">
+          <context context-type="linenumber">82</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="492d2bd18db0cba03f6d9e3b0c42b8639fbe51ab">
+        <source>One of these tags</source>
+        <target>Een van deze tags</target>
+        <context-group name="null">
+          <context context-type="linenumber">87</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5ca707824ab93066c7d9b44e1b8bf216725c2c22">
+        <source>Filter</source>
+        <target>Filter</target>
+        <context-group name="null">
+          <context context-type="linenumber">94</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="41ed53a3f1d4dfc57011d0aba13b8b074e8b41b6">
+        <source>Display unlisted and private videos</source>
+        <target>Laat onvermelde en privé-video's zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Geen resultaten.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
+        <source>
+      <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ object.category.label }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+    </source>
+        <target>
+<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ object.category.label }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="48a5d0af93b94c4575b7f76a47fb3cdee58e6919">
+        <source>
+      <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>#<x id="INTERPOLATION" equiv-text="{{ object.tag }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+    </source>
+        <target>
+<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>#<x id="INTERPOLATION" equiv-text="{{ object.tag }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e093a5a83045ff283f992a93699abb7cb9dd3c1b">
+        <source>
+      <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>
+        <x id="TAG_IMG" ctype="image" equiv-text="&lt;img/&gt;"/>
+
+        <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="INTERPOLATION" equiv-text="{{ object.channel.displayName }}"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+      <x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+    </source>
+        <target>
+<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>
+        <x id="TAG_IMG" ctype="image" equiv-text="&lt;img/&gt;"/>
+
+        <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="INTERPOLATION" equiv-text="{{ object.channel.displayName }}"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+      <x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ff78f059449d44322f627d0f66df07abe476962b">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>Over de instantie <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
+          Cancel
+        </source>
+        <target>
+Annuleer</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Voorleggen</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Voorwaarden</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Een account aanmaken is mogelijk en</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>deze instantie voorziet een basis-opslagquotum van <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> voor de video's van haar gebruikers.</target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>deze instantie voorziet onbeperkte opslagruimte voor de video's van haar gebruikers.</target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>Een account maken is momenteel niet toegelaten op deze instantie.</target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>
   About PeerTube
 </source>
-        <target>Over PeerTube</target>
+        <target>
+Over PeerTube
+
+</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
         <source>
     It is a free and open-source software, under the <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>AGPLv3 licence<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>.
   </source>
-        <target>Het is vrije en open-source software, beschikbaar onder de <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>AGPLv3<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>.</target>
+        <target>
+Het is vrije en open-source software, beschikbaar onder de <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>AGPLv3 licentie<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>.</target>
         <context-group name="null">
           <context context-type="linenumber">8</context>
         </context-group>
         <source>
     For more information, please visit <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>joinpeertube.org<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>.
   </source>
-        <target>Kijk voor meer informatie op <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>joinpeertube.org<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>.</target>
+        <target>
+Kijk voor meer informatie op <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>joinpeertube.org<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>.</target>
         <context-group name="null">
           <context context-type="linenumber">12</context>
         </context-group>
     PeerTube uses the BitTorrent protocol to share bandwidth between users.
     This implies that your IP address is stored in the instance's BitTorrent tracker as long as you download or watch the video.
   </source>
-        <target>PeerTube gebruikt het BitTorrent-protocol om bandbreedte tussen gebruikers te delen. Dat betekent ook dat jouw IP-adres bijgehouden wordt in de BitTorrent-tracker van de PeerTube-instantie zolang je de video aan het bekijken bent.</target>
+        <target>
+PeerTube gebruikt het BitTorrent-protocol om bandbreedte tussen gebruikers te delen. Dat betekent ook dat jouw IP-adres bijgehouden wordt in de BitTorrent-tracker van de PeerTube-instantie zolang je de video aan het bekijken bent.</target>
         <context-group name="null">
           <context context-type="linenumber">20</context>
         </context-group>
     In theory, someone with enough technical skills could create a script that tracks which IP is downloading which video.
     In practice, this is much more difficult because:
   </source>
-        <target>In theorie kan iemand met technische kennis een script maken dat bijhoudt welk IP-adres welke video aan het downloaden is. In de praktijk is dat wat moeilijker:</target>
+        <target>
+In theorie kan iemand met technische kennis een script maken dat bijhoudt welk IP-adres welke video aan het downloaden is. In de praktijk is dat wat moeilijker omdat:</target>
         <context-group name="null">
           <context context-type="linenumber">27</context>
         </context-group>
       An HTTP request has to be sent on each tracker for each video to spy.
       If we want to spy all PeerTube's videos, we have to send as many requests as there are videos (so potentially a lot)
     </source>
-        <target>Voor elke video waarvan hij de kijkers wil bespioneren, moet hij een apart HTTP-request sturen. Om dat voor alle PeerTube-videos te doen, moeten er dus evenveel HTTP-requests als video's gebruikt worden (dat kan hoog oplopen).</target>
+        <target>
+Voor elke video waarvan hij de kijkers wil bespioneren, moet hij een apart HTTP-request sturen. Om dat voor alle PeerTube-videos te doen, moeten er dus evenveel HTTP-requests als video's gebruikt worden (dat kan hoog oplopen).</target>
         <context-group name="null">
           <context context-type="linenumber">33</context>
         </context-group>
       For each request sent, the tracker returns random peers at a limited number.
       For instance, if there are 1000 peers in the swarm and the tracker sends only 20 peers for each request, there must be at least 50 requests sent to know every peers in the swarm
     </source>
-        <target>Voor elk request geeft de tracker een beperkt aantal willekeurige peers terug. Als er bijvoorbeeld 1000 peers beschikbaar zijn en de tracker steeds 20 peers teruggeeft, moeten er op zijn minst 50 requests gestuurd worden om alle peers te weten te komen.</target>
+        <target>
+Voor elk request geeft de tracker een beperkt aantal willekeurige peers terug. Als er bijvoorbeeld 1000 peers beschikbaar zijn en de tracker steeds 20 peers teruggeeft, moeten er op zijn minst 50 requests gestuurd worden om alle peers te weten te komen.</target>
         <context-group name="null">
           <context context-type="linenumber">38</context>
         </context-group>
         <source>
       Those requests have to be sent regularly to know who starts/stops watching a video. It is easy to detect that kind of behaviour
     </source>
-        <target>Die requests moeten regelmatig herhaald worden om te kunnen achterhalen wie begint of stopt met een video te bekijken. Het is gemakkelijk om zulk gedrag te detecteren.</target>
+        <target>
+Die requests moeten regelmatig herhaald worden om te kunnen achterhalen wie begint of stopt met een video te  kijken. Het is gemakkelijk om zulk gedrag te detecteren.</target>
         <context-group name="null">
           <context context-type="linenumber">43</context>
         </context-group>
         <source>
       If an IP address is stored in the tracker, it doesn't mean that the person behind the IP (if this person exists) has watched the video
     </source>
-        <target>Als een IP-adres opgeslagen is in de tracker, betekent dat niet dat de persoon achter dat adres (als die persoon bestaat) de video bekeken heeft</target>
+        <target>
+Als een IP-adres opgeslagen is in de tracker, betekent dat niet dat de persoon achter dat adres (als die persoon bestaat) de video bekeken heeft</target>
         <context-group name="null">
           <context context-type="linenumber">47</context>
         </context-group>
         <source>
       The IP address is a vague information : usually, it regularly changes and can represent many persons or entities
     </source>
-        <target>Een IP-adres is vage </target>
+        <target>
+Een IP-adres is vage informatie: Meestal veranderd het regelmatig en kan het meerdere personen en identiteiten representeren</target>
         <context-group name="null">
           <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b4c2ef0143270626106b26196d40baf3439aa7b0">
+        <source>
+      Web peers are not publicly accessible: because we use WebRTC inside the web browser (<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>with the WebTorrent library<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>), the protocol is different from classic BitTorrent.
+      When you are in a web browser, you send a signal containing your IP address to the tracker that will randomly choose other peers to forward the information to.
+      See <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/>this document<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> for more information
+    </source>
+        <target>
+Web peers zijn niet openbaar bereikbaar: omdat we WebRTC in de webbrowser gebruiken (<x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>met de WebTorrent-bibliotheek<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>), is het protocol anders dan in klassieke BitTorrent.
+      Wanneer je in een webbrowser zit, verstuur je een signaal die je IP adres bevat naar de tracker die willekeurig andere peers kiest om de informatie heen te sturen.
+      See <x id="START_LINK_1" ctype="x-a" equiv-text="&lt;a&gt;"/>dit document<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> voor meer informatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">55</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="50d8e8388f5ceab292850ed828f306c9f2cab389">
+        <source>
+    The worst-case scenario of an average person spying on their friends is quite unlikely.
+    There are much more effective ways to get that kind of information.
+  </source>
+        <target>
+Het worst-case scenario dat kan gebeuren van een gemiddeld persoon die hun vrienden bespioneert is erg onwaarschijnlijk.
+Er zijn veel effectievere manieren om dat soort informatie te verkrijgen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">62</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4bf47a1ae952bf42a4682a5ecddb0bfb8c9adfaf">
+        <source>How does PeerTube compare with YouTube?</source>
+        <target>Hoe is PeerTube vergeleken met YouTube?</target>
+        <context-group name="null">
+          <context context-type="linenumber">67</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2432705cbabcb92a8677338901dd5d655383ef4c">
+        <source>
+    The threats to privacy in YouTube are different from PeerTube's.
+    In YouTube's case, the platform gathers a huge amount of your personal information (not only your IP) to analyze them and track you.
+    Moreover, YouTube is owned by Google/Alphabet, a company that tracks you across many websites (via AdSense or Google Analytics).
+  </source>
+        <target>
+De bedreigingen tegen privacy in YouTube zijn verschillend dan in PeerTube's geval.
+In YouTube's geval, verzamelt het platform een gigantisch aantal persoonlijke informatie (niet alleen je IP) om te analyseren en om je te tracken.
+Verder nog, YouTube is eigendom van Google/Alphabet, een bedrijf die je trackt over meerdere websites (via AdSense of Google Analytics).</target>
+        <context-group name="null">
+          <context context-type="linenumber">69</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3c2990d5e452bdf2317ff23745db70705d848d99">
+        <source>What can I do to limit the exposure of my IP address?</source>
+        <target>Wat kan ik doen om de blootstelling van mijn IP adress te verminderen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">75</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a545356de272b955258c2a2432b08ec637b65f7e">
         <source>
     Your IP address is public so every time you consult a website, there is a number of actors (in addition to the final website) seeing your IP in their connection logs: ISP/routers/trackers/CDN and more.
     PeerTube is transparent about it: we warn you that if you want to keep your IP private, you must use a VPN or Tor Browser.
     Thinking that removing P2P from PeerTube will give you back anonymity doesn't make sense.
   </source>
-        <target>Je IP-adres is geen privé-gegeven. Elke keer je een website bezoekt, is er een aantal entiteiten (bovenop de website die je effectief bezoekt) die je IP zien in hun logs: ISP's, routers, trackers, CDN's en meer.
-PeerTube is transparant: we waarschuwen je dat je, als je je IP-adres privé wil afschermen, je een VPN of de Tor-Browser moet gebruiken.
-Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit geven.</target>
+        <target>
+Je IP-adres is openbaar dus elke keer dat je een website bezoekt, zijn er een aantal actoren (bovenop de website die je effectief bezoekt) die je IP zien in hun logs: ISP/routers/trackers/CDN en meer.    
+PeerTube is daarover transparant: we waarschuwen je dat je, als je je IP-adres wil afschermen, je een VPN of de Tor-Browser moet gebruiken.
+Denken dat het P2P-mechanisme uit PeerTube halen je anonimiteit terug zou geven is onlogisch.</target>
         <context-group name="null">
           <context context-type="linenumber">77</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a835d8a12e14eb96919245a0bbafd8069c146578">
-        <source><x id="INTERPOLATION" equiv-text="{{ account.followersCount }}"/> subscribers</source>
-        <target><x id="INTERPOLATION" equiv-text="{{ account.followersCount }}"/> abonnees</target>
+      <trans-unit id="8ce78dd287b9a9dde5079916425ea66466530e41">
+        <source>What will be done to mitigate this problem?</source>
+        <target>Wat zal worden gedaan om dit probleem te verminderen?</target>
         <context-group name="null">
-          <context context-type="linenumber">24</context>
+          <context context-type="linenumber">83</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6f5a458f827503ac7b8697688ecf3e0490818ee8">
-        <source>Video channels</source>
-        <target>Videokanalen</target>
+      <trans-unit id="b1372cb61ca791a0f7f95bf31c86c97df142adc4">
+        <source>
+    PeerTube is in its early stages, and want to deliver the best countermeasures possible by the time the stable is released.
+    In the meantime, we want to test different ideas related to this issue:
+  </source>
+        <target>
+PeerTube is in haar ontwikkelingsfasen, en wilt de beste tegenmaatregelen mogelijk geven tegen de tijd dat de stabiele versie is gereleased.
+Ondertussen willen we verschillende ideeën testen die gerelateerd zijn aan dit probleem:</target>
         <context-group name="null">
-          <context context-type="linenumber">31</context>
+          <context context-type="linenumber">85</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="299f97b8ee9c62d45f2cc01961aa1e5101d6d05a">
-        <source>Stats</source>
-        <target>Statistieken</target>
+      <trans-unit id="d32608aba08c6bb3cc4e4e8ec6223e5f4e78ca19">
+        <source>Set a limit to the number of peers sent by the tracker</source>
+        <target>Zet een limiet op het aantal peers verzonden door de tracker</target>
         <context-group name="null">
-          <context context-type="linenumber">16</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8bc634cd9d8c9b684dbfaaf17a522f894bedbffc">
-        <source>Joined <x id="INTERPOLATION" equiv-text="{{ account.createdAt | date }}"/></source>
-        <target>Account aangemaakt op <x id="INTERPOLATION" equiv-text="{{ account.createdAt | date }}"/></target>
+      <trans-unit id="a6d732b614143f862e69798046dc0868716547e5">
+        <source>Set a limit on the request frequency received by the tracker (being tested)</source>
+        <target>Zet een limiet op de verzoekfrequentie verkregen door de tracker (wordt getest)</target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8fef247fd0c5bf790151f7661cafc4b7fd0397f3">
-        <source><x id="INTERPOLATION" equiv-text="{{ videoChannel.followersCount }}"/> subscribers</source>
-        <target><x id="INTERPOLATION" equiv-text="{{ videoChannel.followersCount }}"/> abonnees</target>
+      <trans-unit id="ba77e356eaa5c06caaf5c8734c361d1a5415fe1c">
+        <source>Ring a bell if there are unusual requests (being tested)</source>
+        <target>Laat iets horen als er ongebruikelijke requests zijn (wordt getest)</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">93</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="81861ff8a71c8a5881cdf66417f3bddb753f0e18">
+        <source>Disable P2P from the administration interface</source>
+        <target>Schakel P2P uit vanuit het administratieinterface</target>
+        <context-group name="null">
+          <context context-type="linenumber">94</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efde279863678ed95a8949a3712c99748bdabfe6">
+        <source>An automatic video redundancy program: we wouldn't know if the IP downloaded the video on purpose or if it was the automatized program</source>
+        <target>Een automatisch video-overbodigheidsprogramma: we zouden niet weten of het IP de video met opzet heeft gedownload, of als het een geautomatiseerd programma was.</target>
+        <context-group name="null">
+          <context context-type="linenumber">95</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd2edf99dd6562385ccec19a7ab2d1898e626605">
+        <source>Banned</source>
+        <target>Verbannen</target>
+        <context-group name="null">
+          <context context-type="linenumber">12</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="62a557fcfdbd25a31d1a0332294f94a466fee809">
+        <source>Muted</source>
+        <target>Gedempt</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="48bbf6dbdb22e0ef4bd257eae2ab356f2ea66c89">
+        <source>Muted by your instance</source>
+        <target>Gedempt door jouw instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="44bd08a7ec1e407356620967d65d8fe2d8639d0a">
+        <source>Instance muted</source>
+        <target>Instantie gedempt</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1a6443bb7ed01046dd83cf78806f795f1204ffa1">
+        <source>Instance muted by your instance</source>
+        <target>Instantie gedempt door jouw instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a835d8a12e14eb96919245a0bbafd8069c146578">
+        <source><x id="INTERPOLATION" equiv-text="{{ account.followersCount }}"/> subscribers</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ account.followersCount }}"/> abonnees</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6f5a458f827503ac7b8697688ecf3e0490818ee8">
+        <source>Video channels</source>
+        <target>Videokanalen</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="299f97b8ee9c62d45f2cc01961aa1e5101d6d05a">
+        <source>Stats</source>
+        <target>Statistieken</target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8bc634cd9d8c9b684dbfaaf17a522f894bedbffc">
+        <source>Joined <x id="INTERPOLATION" equiv-text="{{ account.createdAt | date }}"/></source>
+        <target>Account aangemaakt op <x id="INTERPOLATION" equiv-text="{{ account.createdAt | date }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8fef247fd0c5bf790151f7661cafc4b7fd0397f3">
+        <source><x id="INTERPOLATION" equiv-text="{{ videoChannel.followersCount }}"/> subscribers</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ videoChannel.followersCount }}"/> abonnees</target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="f36bd6a1570cb9b0a5023870f35160957cad2a8f">
@@ -552,42 +1337,49 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>Short description</source>
         <target>Korte omschrijving</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Startpagina</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
+        <source>Videos Overview</source>
+        <target>Video-overzicht</target>
+        <context-group name="null">
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Populaire video's</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Recent toegevoegde video's</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Video's op deze instantie</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Beleid rond video's met gevoelige inhoud</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
@@ -622,42 +1414,77 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>Signup enabled</source>
         <target>Registratie mogelijk</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
+        <source>Signup requires email verification</source>
+        <target>E-mailverificatie nodig bij registratie</target>
+        <context-group name="null">
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Registratielimiet</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Gebruikers</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
-        <source>Administrator</source>
-        <target>Beheerder</target>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Standaard video-quotum voor gebruikers</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">109</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
-        <source>Admin email</source>
-        <target>E-mail van beheerder</target>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Standaard dagelijks video-quotum voor gebruikers</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
+          <context context-type="linenumber">121</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Gebruikers</target>
+      <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
+        <source>Import</source>
+        <target>Importeren</target>
         <context-group name="null">
-          <context context-type="linenumber">144</context>
+          <context context-type="linenumber">42</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Standaard video-quotum voor gebruikers</target>
+      <trans-unit id="29aa67f13fd34a2421ff9d7de7d5142790676b9e">
+        <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
+        <target>Video-import met HTTP URL (d.w.z. YouTube) ingeschakeld</target>
         <context-group name="null">
-          <context context-type="linenumber">147</context>
+          <context context-type="linenumber">141</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
+        <source>Video import with a torrent file or a magnet URI enabled</source>
+        <target>Video-import met een torrentbestand of een magnet URL ingeschakeld</target>
+        <context-group name="null">
+          <context context-type="linenumber">148</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
+        <source>Administrator</source>
+        <target>Administrator</target>
+        <context-group name="null">
+          <context context-type="linenumber">155</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
+        <source>Admin email</source>
+        <target>E-mail van administrator</target>
+        <context-group name="null">
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
@@ -678,21 +1505,21 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>Your Twitter username</source>
         <target>Je Twitter-gebruikersnaam</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Geeft het Twitter-account aan voor de website of het platform waarop de inhoud gepubliceerd werd.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
-        <target>Instantie ge-whitelist door Twitter</target>
+        <target>Instantie gewhitelist door Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
@@ -706,77 +1533,120 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>Transcoding</source>
         <target>Transcoding</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transcoding ingeschakeld</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
-        <target>Als je transcoding niet inschakelt, zullen veel video's die je gebruikers uploaden niet overal werken!</target>
+        <target>Als je transcoding niet inschakelt, zullen veel video's die je gebruikers uploaden niet werken!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Threads gebruikt voor transcoding</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
+        <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
+        <target>Resolutie <x id="INTERPOLATION" equiv-text="{{resolution}}"/> ingeschakeld</target>
+        <context-group name="null">
+          <context context-type="linenumber">252</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
+        <source>
+          Cache
+
+          <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
+        </source>
+        <target>
+Cache
+
+          <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">260</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
+        <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
+        <target>Sommige bestanden zijn niet federaal (voorbeelden, ondertitelingen). We verkrijgen ze direct van hun afkomstige instantie en cachen ze.</target>
+        <context-group name="null">
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Cachegrootte voor previews</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
+        <source>Video captions cache size</source>
+        <target>Cachegrootte van video-ondertiteling</target>
+        <context-group name="null">
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Aanpassingen</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
-        <target>Schrijf JavaScriptcode.&lt;br /&gt;Voorbeeld: &lt;pre&gt;console.log('mijn instantie is fantastisch');&lt;/pre&gt;</target>
+        <target>Schrijf direct JavaScriptcode.&lt;br /&gt;Bijvoorbeeld: &lt;pre&gt;console.log('mijn instantie is fantastisch');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Geavanceerde configuratie</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
-        <target>Updateconfiguratie</target>
+        <target>Bijwerkingsconfiguratie</target>
+        <context-group name="null">
+          <context context-type="linenumber">340</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
+        <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
+        <target>Het lijkt erop dat de configuratie invalide is. Zoek alstublieft potentiële foutmeldingen op in andere tabbladen.</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
         <source>
       Users
     </source>
-        <target>Gebruikers</target>
+        <target>
+Gebruikers</target>
         <context-group name="null">
           <context context-type="linenumber">3</context>
         </context-group>
@@ -785,16 +1655,28 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>
       Manage follows
     </source>
-        <target>Volgers beheren</target>
+        <target>
+Volgers beheren</target>
         <context-group name="null">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="1a5c7f9b1bec1463728f44933f0e256de9c45154">
+        <source>
+      Moderation
+    </source>
+        <target>
+Moderatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7bea88c54fdccfdc9f687b0ffe9bf6a653d19368">
         <source>
       Jobs
     </source>
-        <target>Jobs</target>
+        <target>
+Banen</target>
         <context-group name="null">
           <context context-type="linenumber">15</context>
         </context-group>
@@ -803,7 +1685,8 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>
       Configuration
     </source>
-        <target>Configuratie</target>
+        <target>
+Configuratie</target>
         <context-group name="null">
           <context context-type="linenumber">19</context>
         </context-group>
@@ -819,18 +1702,26 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
         <source>
     It seems that you are not on a HTTPS server. Your webserver needs to have TLS activated in order to follow servers.
   </source>
-        <target>Het ziet ernaar uit dat je op een server bent zonder HTTPS. Op je webserver moet TLS geactiveerd zijn om servers te kunnen volgen.</target>
+        <target>
+Het ziet ernaar uit dat je op een server bent zonder HTTPS. Op je webserver moet TLS geactiveerd zijn om servers te kunnen volgen.</target>
         <context-group name="null">
           <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
       <trans-unit id="456c6383d8e7cd15aadbcdc196d4ae7a70092437">
         <source>Add following</source>
-        <target>Abonneren</target>
+        <target>Voeg volgend toe</target>
         <context-group name="null">
           <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="25925fc5826bc5b3eeae7c45b08b0ed74b9e2954">
+        <source>Filter...</source>
+        <target>Filtreren...</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="45cc8ca94b5a50842a9a8ef804a5ab089a38ae5c">
         <source>ID</source>
         <target>ID</target>
@@ -866,6 +1757,27 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7823909fb1d8d313382f6f4bd842f1a7ef6f08d1">
+        <source>Accepted</source>
+        <target>Geaccepteerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">32</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e6a27066251ca1e04c5be86ad758380856df2506">
+        <source>Pending</source>
+        <target>In behandeling</target>
+        <context-group name="null">
+          <context context-type="linenumber">33</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1d729bcbe3529d2fe2295b7a3a41282ee09de2c8">
+        <source>Redundancy allowed</source>
+        <target>Overtolligheid toegelaten</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5fccee488a9ea908c16d2ab9dbdaf264f1aac479">
         <source>Manage follows</source>
         <target>Abonnementen beheren</target>
@@ -873,9 +1785,30 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f995df052a1dfc675c2a21926420a707d9601936">
+        <source>Following</source>
+        <target>Volgend</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d29764bcbaad3ef69b6be92be35bdf25972ce246">
+        <source>Follow</source>
+        <target>Volg</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9bee670725966ed477b4c33a545c8b5436b0065e">
+        <source>Followers</source>
+        <target>Volgers</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9f2501fcb2ff71f1376c2d2fbbbd49f200e6c8f">
         <source>Jobs list</source>
-        <target>Lijst van jobs</target>
+        <target>Banenlijst</target>
         <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
@@ -887,6 +1820,20 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="74c8f69ec23f41a429e241126ab4d25b9d12348e">
+        <source>Processed on</source>
+        <target>Behandeld op</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4fa08915c99629d38c9da8a08b1985a7f4e38e40">
+        <source>Finished on</source>
+        <target>Voltooid op</target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="31cf824034489eb42f6a388d5980b98b8e1de015">
         <source>Create user</source>
         <target>Gebruiker aanmaken</target>
@@ -910,7 +1857,7 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
       </trans-unit>
       <trans-unit id="bb3542ff8e5defa6d0c773799e5c8fe399605d05">
         <source>mail@example.com</source>
-        <target>mail@example.org</target>
+        <target>mail@voorbeeld.org</target>
         <context-group name="null">
           <context context-type="linenumber">21</context>
         </context-group>
@@ -935,6 +1882,13 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6ded52553dd8720fd3698b8fbc3a6d037c07b496">
+        <source>Daily video quota</source>
+        <target>Dagelijks videoquotum</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5e8b4663c17c337a1f11160c0a683350936faa1f">
         <source>Users list</source>
         <target>Gebruikerslijst</target>
@@ -942,6 +1896,13 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="ea762ca1d74c96d8568ac68482778f52ca531cc4">
+        <source>Batch actions</source>
+        <target>Batchacties</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="08ea8692dc2a7050026df26fc39b22960bde9de5">
         <source>Username <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Gebruikersnaam <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
@@ -949,6 +1910,65 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">40</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="adba7c8b43e42581460fbe5d08b5cb5ab60eba4b">
+        <source>(banned)</source>
+        <target>(verbannen)</target>
+        <context-group name="null">
+          <context context-type="linenumber">65</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="be73b652c2707f42b5d780d0c7b8fc5ea0b1706c">
+        <source>Go to the account page</source>
+        <target>Ga naar accountpagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">133</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>Gebruiker's e-mail moet geverifieerd zijn om in te loggen</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>Gebruiker's e-mail is geverifieerd / Gebruiker kan inloggen zonder e-mailverificatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
+        <source>Ban reason:</source>
+        <target>Reden van verbanning:</target>
+        <context-group name="null">
+          <context context-type="linenumber">95</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
+        <source>Moderation comment</source>
+        <target>Beheerdersopmerking</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5731e5d5ac989bf08848b5a57a5586cf84d80964">
+        <source>
+        This comment can only be seen by you or the other moderators.
+      </source>
+        <target>
+Deze opmerking kan alleen door jou en andere beheerders gezien worden.</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0562e455c88234829f3c27a38f3039f027bfd5d2">
+        <source>Update this comment</source>
+        <target>Werk deze comment bij</target>
+        <context-group name="null">
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2bf5a31043ff476ca081a4080f3f3f17518dc6f2">
         <source>Reporter</source>
         <target>Melder</target>
@@ -963,6 +1983,13 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7e7ad19f1bcc2c33cdba4c1ad25e2b398ad453d9">
+        <source>State <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Status <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c6ab75e099e131d7a4f94e1732e7436d8fc386c7">
         <source>Go to the account</source>
         <target>Naar account gaan</target>
@@ -977,60 +2004,183 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">33</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="00ecde6001106fe7406a34cc3459cc5b88e4aec1">
-        <source>Blacklisted videos</source>
-        <target>Video's op zwarte lijst</target>
+      <trans-unit id="030b4423b92167200e39519599f9b863b4f7c62c">
+        <source>Actions</source>
+        <target>Acties</target>
         <context-group name="null">
-          <context context-type="linenumber">7</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Mijn instellingen</target>
+      <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
+        <source>Reason:</source>
+        <target>Reden:</target>
         <context-group name="null">
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Mijn video's</target>
+      <trans-unit id="018cbb63c7eda4b82d17dd9058cfaa0fd055c638">
+        <source>Moderation comment:</source>
+        <target>Beheerderopmerking:</target>
         <context-group name="null">
-          <context context-type="linenumber">14</context>
+          <context context-type="linenumber">57</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
-        <source>Video quota:</source>
-        <target>Videoquotum:</target>
+      <trans-unit id="b14fd2fc28c5eecd05554d2bcbc3a938c599e2bf">
+        <source>Video name <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Videonaam <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">4</context>
+          <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
-        <source>Profile</source>
-        <target>Profiel</target>
+      <trans-unit id="96dfa3efa02bfafc0bc6d4ab186ebef2813a9e8a">
+        <source>Sensitive</source>
+        <target>Gevoelig</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">9</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
-        <source>Video settings</source>
-        <target>Video-instellingen</target>
+      <trans-unit id="a7f42da3bb4eea0b71b0a20a2aff6612a82cab99">
+        <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Datum <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
+        <source>Blacklist reason:</source>
+        <target>Reden voor de zwarte lijst:</target>
+        <context-group name="null">
+          <context context-type="linenumber">43</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
+        <source>Moderation</source>
+        <target>Beheer</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="23a793ed0df2e10823dd469c5cea9b5c36be8f7e">
+        <source>Video abuses</source>
+        <target>Videomisbruik</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="00ecde6001106fe7406a34cc3459cc5b88e4aec1">
+        <source>Blacklisted videos</source>
+        <target>Video's op zwarte lijst</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b1ff109b26ae8f08650415454b9098c43eba2e2c">
+        <source>Muted accounts</source>
+        <target>Gedempte accounts</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd0611346af048015e0a1275091ef68ce98832d2">
+        <source>Muted servers</source>
+        <target>Gedempte servers</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29881a45dafbe5aa05cd9d0441a4c0c2fb06df92">
+        <source>Account</source>
+        <target>Account</target>
+        <context-group name="null">
+          <context context-type="linenumber">12</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="079e99cce11c87b142e80fdd14dae98a61012fc4">
+        <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Gedempt bij <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1f689fada9748a830117f5b429a88ef8629082a8">
+        <source>Unmute</source>
+        <target>Demping opheffen</target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
+        <source>Video quota:</source>
+        <target>Videoquotum:</target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="994363f08f9fbfa3b3994ff7b35c6904fdff18d8">
+        <source>Profile</source>
+        <target>Profiel</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
+        <source>Video settings</source>
+        <target>Video-instellingen</target>
+        <context-group name="null">
+          <context context-type="linenumber">16</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
+        <source>Danger zone</source>
+        <target>Gevarenzone</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
+        <source>Change ownership</source>
+        <target>Verander eigenaar</target>
+        <context-group name="null">
+          <context context-type="linenumber">46</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="046c4fa30411e6b1aa46dc51bf82d07b1adf14d4">
+        <source>Select the next owner</source>
+        <target>Selecteer de volgende eigenaar</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a5433ae2324496bea9537caa5e8a2719d8e958d8">
+        <source>
+        Cancel
+      </source>
+        <target>
+Annuleren</target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
-        <target>Video’s <x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> aantal keer bekeken</target>
+        <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> weergaven</target>
         <context-group name="null">
           <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4a806761798181e907e28ed1af053d466526800d">
+        <source>Blacklisted</source>
+        <target>Op de zwarte lijst</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="17a9d3860d9ad593dd09a9f934e03999d9e76a7a">
         <source>
             Cancel
           </source>
-        <target>Annuleren</target>
+        <target>
+Annuleren</target>
         <context-group name="null">
           <context context-type="linenumber">30</context>
         </context-group>
@@ -1056,6 +2206,13 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="915d4704e1649016512cbf5eeac55b4dbf933558">
+        <source>Example: my_channel</source>
+        <target>Bijvoorbeeld: mijn_kanaal</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="bc155f9fc3be3f32083f19b2c77d4ad3b696d9b9">
         <source>Display name</source>
         <target>Weergavenaam</target>
@@ -1066,12 +2223,26 @@ Het Peer-to-Peer-mechanisme uit PeerTube halen zou je niet méér anonimiteit ge
       <trans-unit id="74728de5289ea2ff3f553bc2b48f1811680b931a">
         <source>Short text to tell people how they can support your channel (membership platform...).&lt;br /&gt;&lt;br /&gt;
 When you will upload a video in this channel, the video support field will be automatically filled by this text.</source>
-        <target>Korte tekst om mensen te vertellen hoe ze je kanaal kunnen ondersteunen (lidmaatschap…).&lt;br/&gt;&lt;br/&gt;
-Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "ondersteun"-veld.</target>
+        <target>Korte tekst om mensen te vertellen hoe ze je kanaal kunnen ondersteunen (lidmaatschapsplatform…).&lt;br /&gt;&lt;br /&gt;
+Als je een video uploadt op dit kanaal, wordt deze tekst ingevuld in het "ondersteun"-veld.</target>
         <context-group name="null">
           <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="38baeb215c17af9d9e295e371a57f4a48ab4c191">
+        <source>Target</source>
+        <target>Doelwit</target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3a5d57052d13d2da1cbcffdbb8effb9874b1595a">
+        <source>You don't have any subscriptions yet.</source>
+        <target>Je hebt nog geen abonnementen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c65641c36859c328928e6b0f14c3f913886f8add">
         <source>Created by <x id="INTERPOLATION" equiv-text="{{ videoChannel.ownerBy }}"/></source>
         <target>Gemaakt door <x id="INTERPOLATION" equiv-text="{{ videoChannel.ownerBy }}"/></target>
@@ -1086,6 +2257,75 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="fbc450919a486e8ed311a7e91a41987d47d83804">
+        <source>Accept ownership</source>
+        <target>Accepteer eigenaar</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4570c754149df06f31096510abfc925968c35562">
+        <source>Select the target channel</source>
+        <target>Selecteer het doelwitkanaal</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e98239d8a6be1100119ff4b5630c822b82786740">
+        <source>Initiator</source>
+        <target>Initiatiefnemer</target>
+        <context-group name="null">
+          <context context-type="linenumber">13</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b08d67fe4e192ea8352bebdc6aabbd1bb7abed02">
+        <source>
+        Created
+        <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/>
+      </source>
+        <target>
+        Gecreëerd op
+        <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1">
+        <source>Status</source>
+        <target>Status</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1bd5e17c9582661e20763a7634ef07881e33bbd7">
+        <source>Action</source>
+        <target>Actie</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4212e793d36e1aaa6ee1b09881677f783b5feff">
+        <source><x id="INTERPOLATION" equiv-text="{{ videoChangeOwnership.status }}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{ videoChangeOwnership.status }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a5613f6b472c1ed863dff1be932913a251f27a2">
+        <source>Refuse</source>
+        <target>Weigeren</target>
+        <context-group name="null">
+          <context context-type="linenumber">47</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Gedempte instanties</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Wachtwoord veranderen</target>
@@ -1093,6 +2333,13 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">30</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="0dd390d056411e1709ec97ec51c46d78600e3f7b">
+        <source>Current password</source>
+        <target>Huidige wachtwoord</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e70e209561583f360b1e9cefd2cbb1fe434b6229">
         <source>New password</source>
         <target>Nieuw wachtwoord</target>
@@ -1114,8 +2361,17 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d044c51156e295824813a866dba9545bdb59466b">
+        <source>Use WebTorrent to exchange parts of the video with others</source>
+        <target>Gebruik WebTorrent om delen van de video over te maken naar anderen</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="fb17c44abac2d1ed2a54cdd28bae289dc0b9a1c2">
-        <source>Automatically plays video</source><target>Automatically plays video</target><context-group name="null">
+        <source>Automatically plays video</source>
+        <target>Video automatisch afspelen</target>
+        <context-group name="null">
           <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
@@ -1128,11 +2384,25 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
       </trans-unit>
       <trans-unit id="d2fa66a905b6b7f691c83be681d18188cbe4a8ba">
         <source>Update my profile</source>
-        <target>Update mijn profiel</target>
+        <target>Werk mijn profiel bij</target>
         <context-group name="null">
           <context context-type="linenumber">27</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4b50f2ef2e8b9a24e674d12012ee310f378a5503">
+        <source><x id="INTERPOLATION" equiv-text="{{ actor.followersCount }}"/> subscribers</source>
+        <target><x id="INTERPOLATION" equiv-text="{{ actor.followersCount }}"/> abonnees</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c4a959fc6349bd0793e1ad571d492052a07bdab5">
+        <source>Change the avatar</source>
+        <target>Verander de avatar</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="c860c88df9ad58b1187084251340b232cdf0a7f9">
         <source>(extensions: <x id="INTERPOLATION" equiv-text="{{ avatarExtensions }}"/>, max size: <x id="INTERPOLATION_1" equiv-text="{{ maxAvatarSize | bytes }}"/>)</source>
         <target>(extensies: <x id="INTERPOLATION" equiv-text="{{ avatarExtensions }}"/>, maximale grootte: <x id="INTERPOLATION_1" equiv-text="{{ maxAvatarSize | bytes }}"/>)</target>
@@ -1140,15 +2410,84 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">18</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d1a04ba05116499d4cf59a48a282a8bcbf5b622d">
+        <source>Once you delete your account, there is no going back. Please be certain.</source>
+        <target>Als je je account verwijdert, kan je niet meer terug. 
+Wees alstublieft zeker.</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9a2f889dde4574a6883c853d1034e75891b28c45">
+        <source>Delete your account</source>
+        <target>Verwijder jouw account</target>
+        <context-group name="null">
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e242e3e8608a3c4a944327eb3d5c221dc6e4e3cd">
         <source>
   Sorry, but we couldn't find the page you were looking for.
 </source>
-        <target>Sorry, maar die pagina kon niet gevonden worden.</target>
+        <target>
+Sorry, maar die pagina kon niet gevonden worden.
+</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="09a69cde5889927629e2ac9dc63a71b88252b530">
+        <source>
+    Verify account email confirmation
+  </source>
+        <target>
+Verifieer e-mailbevestiging van account</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="066569dd934e07e4a5f70c415692be17d5715b57">
+        <source>
+    Your email has been verified and you may now login. Redirecting...
+  </source>
+        <target>
+Jouw e-mail is geverifieerd en je mag nu inloggen.
+Je wordt doorverwezen...</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7ee8fad77b2664dabfb90ea03470f75a6f6d1d48">
+        <source>An error occurred. </source>
+        <target>Er is een probleem opgetreden.</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2d02841904de7f5f60e2618670ac1059f3abec97">
+        <source>
+    Request email for account verification
+  </source>
+        <target>
+Vraag e-mail voor accountverificatie aan</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="eb539ec6941044e284f237f5b40d6a0159afe7af">
+        <source>Send verification email</source>
+        <target>Verzend e-mail voor verificatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a08080316e052053fd20647731a6de826dc8072f">
+        <source>This instance does not require email verification.</source>
+        <target>Deze instantie heeft geen verificatie door e-mail nodig.</target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1380539d91f77f565de6e21ce210da891e6644b8">
         <source>Support this channel</source>
         <target>Ondersteun dit kanaal</target>
@@ -1163,6 +2502,20 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="801b98c6f02fe3b32f6afa3ee854c99ed83474e6">
+        <source>URL</source>
+        <target>URL</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bfe7f34fbd4c3afa5f84a5580e0fae942cad2333">
+        <source>You can import any URL &lt;a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'&gt;supported by youtube-dl&lt;/a&gt; or URL that points to a raw MP4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance.</source>
+        <target>Je kan elke URL importeren &lt;a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'&gt;ondersteunt door youtube-dl&lt;/a&gt; of elke URL die naar een rauw MP4 bestand wijst. Je moet zeker weten dat je de diffusierechten hebt over de inhoud waar het naar wijst, anders kan het wettelijke problemen aan jou of je instantie geven.</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="0cc554f4d7bb6a87515d2d95438e183b50702071">
         <source>Channel</source>
         <target>Kanaal</target>
@@ -1172,11 +2525,37 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
       </trans-unit>
       <trans-unit id="3c78b53bca33467190c0b7a01320bc093a2b1427">
         <source>Privacy</source>
-        <target>Zichtbaarheid</target>
+        <target>Privacy</target>
         <context-group name="null">
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>Sorry, er is iets fout gegaan</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
+        <source>
+  Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
+</source>
+        <target>
+Gefeliciteerd, de video achter<x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> zal worden geimporteerd! Je kan nu al informatie over deze video toevoegen.
+
+</target>
+        <context-group name="null">
+          <context context-type="linenumber">46</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="047f50bc5b5d17b5bec0196355953e1a5c590ddb">
+        <source>Update</source>
+        <target>Bijwerken</target>
+        <context-group name="null">
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="21add64f0f3ebbedf1150ca822c6e149494ab7a9">
         <source>Select the file to upload</source>
         <target>Selecteer het bestand om te uploaden</target>
@@ -1184,74 +2563,216 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="5e420747842373fa99a75a7a18df068cc81e46fb">
+        <source>Scheduled</source>
+        <target>Ingeroosterd</target>
+        <context-group name="null">
+          <context context-type="linenumber">25</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="f7ac2376749c7985f94f0fc89ba75ea624de1215">
         <source>Publish will be available when upload is finished</source>
         <target>Publiceren is mogelijk wanneer de upload voltooid is</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publiceren</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fdf7cbdc140d0aab0f0b6c06065a0fd448ed6a2e">
-        <source>Title</source>
-        <target>Titel</target>
+      <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
+        <source>Select the torrent to import</source>
+        <target>Selecteer de torrent om te importeren</target>
         <context-group name="null">
-          <context context-type="linenumber">9</context>
+          <context context-type="linenumber">6</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b">
-        <source>Tags</source>
-        <target>Tags</target>
+      <trans-unit id="1b518e7f8c067fa55ea797bb1b35b4a2d31dccbc">
+        <source>Or</source>
+        <target>Of</target>
         <context-group name="null">
-          <context context-type="linenumber">191</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="50f53834157770b8205ada0e7a6e235211e4765e">
-        <source>Video descriptions are truncated by default and require manual action to expand them.</source>
-        <target>Videobeschrijvingen worden standaard gedeeltelijk weergegeven, de kijker kan ze handmatig openvouwen.</target>
+      <trans-unit id="0d6558176587662e9bb3b79cca57d42591cf82f9">
+        <source>Paste magnet URI</source>
+        <target>Plak magnet URL</target>
         <context-group name="null">
-          <context context-type="linenumber">28</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d69f4fafc780cc7dbafb063ca5f11e6f7c91b0c5">
-        <source>Schedule publication (<x id="INTERPOLATION" equiv-text="{{ calendarTimezone }}"/>)</source>
-        <target>Publicatie uitstellen (<x id="INTERPOLATION" equiv-text="{{ calendarTimezone }}"/>)</target>
+      <trans-unit id="1ce18c12c809a738f05f2290f46df0677f27ed70">
+        <source>You can import any torrent file that points to a mp4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance.</source>
+        <target>Je kan elk torrentbestand importeren die wijst naar een mp4 bestand. Je moet zeker weten dat je de diffusierechten over de inhoud waar het naar wijst hebt, anders kan het wettelijke problemen aan jezelf en je instantie geven.</target>
         <context-group name="null">
-          <context context-type="linenumber">105</context>
+          <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5ef7108218e096d09f4ee8525a05a8c90d7b95ee">
-        <source>This video contains mature or explicit content</source>
-        <target>Deze video bevat expliciete inhoud of inhoud die enkel voor volwassenen geschikt is</target>
+      <trans-unit id="7cb3731472edd9edf6a6d036498c2c8388157266">
+        <source>
+  Congratulations, the video will be imported with BitTorrent! You can already add information about this video.
+</source>
+        <target>
+Gefeliciteerd, de video zal geimporteerd worden met BitTorrent! 
+Je kan nu al informatie toevoegen over deze video.
+
+</target>
         <context-group name="null">
-          <context context-type="linenumber">119</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="9daabdcaa2bbd83597099b10db22d056cf491644">
-        <source>Some instances do not list videos containing mature or explicit content by default.</source>
-        <target>Op sommige instanties worden expliciete video's standaard niet in zoekresultaten en in lijsten getoond.</target>
+      <trans-unit id="0b60d939cf0f1af9fe513f31164d198abf671860">
+        <source>Import <x id="INTERPOLATION" equiv-text="{{ videoName }}"/></source>
+        <target>Importeer <x id="INTERPOLATION" equiv-text="{{ videoName }}"/></target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="3549ee96125a43181f80712ed744ee223a0e645a">
-        <source>Enable video comments</source>
-        <target>Videoreacties toelaten</target>
+      <trans-unit id="e9cfe8bd050660077212af5c02f5be24821f28d5">
+        <source>Upload <x id="INTERPOLATION" equiv-text="{{ videoName }}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{ videoName }}"/> Uploaden</target>
         <context-group name="null">
-          <context context-type="linenumber">125</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4faf57baebf0fb754a91af0c39521a30cbb1def3">
+        <source>Upload a file</source>
+        <target>Upload een bestand</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fc865859d33eab6fa0a8015233e4686cd544d470">
+        <source>Import with URL</source>
+        <target>Importeer met URL</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="752c401d7dcd708944eef60e411187f71d882340">
+        <source>Import with torrent</source>
+        <target>Importeer met torrent</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="40fa23fe45af4ee2e72cdd3cc6bf6013f180aab0">
+        <source>Add caption</source>
+        <target>Voeg ondertiteling toe</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6bad752cfcac8f3572bdf2c619daec683d56d1a8">
+        <source>Select the caption file</source>
+        <target>Selecteer het ondertitelingsbestand</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c34c61401151c29fb3679638a7d0b95258145ec3">
+        <source>
+        This will replace an existing caption!
+      </source>
+        <target>
+Dit zal een bestaande ondertiteling vervangen!</target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="39702b643cfe3d5b96a4587c1b44a29fa665406c">
+        <source>Add this caption</source>
+        <target>Voeg deze ondertiteling toe</target>
+        <context-group name="null">
+          <context context-type="linenumber">40</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fdf7cbdc140d0aab0f0b6c06065a0fd448ed6a2e">
+        <source>Title</source>
+        <target>Titel</target>
+        <context-group name="null">
+          <context context-type="linenumber">9</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cafc87479686947e2590b9f588a88040aeaf660b">
+        <source>Tags</source>
+        <target>Tags</target>
+        <context-group name="null">
+          <context context-type="linenumber">191</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="457b1cff4d8d7fad0c8742f69c413ecf5e443851">
+        <source>Tags could be used to suggest relevant recommendations.&lt;/br&gt;Press Enter to add a new tag.</source>
+        <target>Tags kunnen gebruikt worden om relevante aanraders te suggesteren. &lt;/br&gt;Druk op Enter om een nieuwe tag toe te voegen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">18</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9bdd535a2817bf0b843a124bf65e4992625e7ecf">
+        <source>+ Tag</source>
+        <target>Tag</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8389e9cde2928cc27aaecbdee818a255bf7984b0">
+        <source>Enter a new tag</source>
+        <target>Vul een nieuwe tag in</target>
+        <context-group name="null">
+          <context context-type="linenumber">21</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="50f53834157770b8205ada0e7a6e235211e4765e">
+        <source>Video descriptions are truncated by default and require manual action to expand them.</source>
+        <target>Videobeschrijvingen worden standaard gedeeltelijk weergegeven en moeten handmatig opengevouwen worden.</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d69f4fafc780cc7dbafb063ca5f11e6f7c91b0c5">
+        <source>Schedule publication (<x id="INTERPOLATION" equiv-text="{{ calendarTimezone }}"/>)</source>
+        <target>Publicatie inroosteren op (<x id="INTERPOLATION" equiv-text="{{ calendarTimezone }}"/>)</target>
+        <context-group name="null">
+          <context context-type="linenumber">105</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5ef7108218e096d09f4ee8525a05a8c90d7b95ee">
+        <source>This video contains mature or explicit content</source>
+        <target>Deze video bevat expliciete inhoud of inhoud die enkel voor volwassenen geschikt is</target>
+        <context-group name="null">
+          <context context-type="linenumber">119</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9daabdcaa2bbd83597099b10db22d056cf491644">
+        <source>Some instances do not list videos containing mature or explicit content by default.</source>
+        <target>Op sommige instanties worden expliciete video's standaard niet in zoekresultaten en in lijsten getoond.</target>
+        <context-group name="null">
+          <context context-type="linenumber">120</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3549ee96125a43181f80712ed744ee223a0e645a">
+        <source>Enable video comments</source>
+        <target>Videoreacties inschakelen</target>
+        <context-group name="null">
+          <context context-type="linenumber">125</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7e549f41b715552ffe69b85c14a690d9d81c85f0">
         <source>Wait transcoding before publishing the video</source>
         <target>Wacht tot het transcoderen voltooid is om de video te publiceren</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
+        <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
+        <target>Als je beslist om niet te wachten totdat de transcoding compleet is voordat je de video publiceert, kan de video onspeelbaar zijn totdat de transcoding eindigt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -1261,18 +2782,84 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="92bcfd1d237a2bfe48dc9f46d074ed26abc8df22">
+        <source>Add another caption</source>
+        <target>Voeg nog een ondertiteling toe</target>
+        <context-group name="null">
+          <context context-type="linenumber">147</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
+        <source>See the subtitle file</source>
+        <target>Zie het ondertitelingsbestand</target>
+        <context-group name="null">
+          <context context-type="linenumber">156</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
+        <source>Already uploaded       ✔</source>
+        <target>Al geupload    ✔</target>
+        <context-group name="null">
+          <context context-type="linenumber">160</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
+        <source>Will be created on update</source>
+        <target>Wordt gecreëerd bij bijwerking</target>
+        <context-group name="null">
+          <context context-type="linenumber">168</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
+        <source>Cancel create</source>
+        <target>Annuleer creatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">170</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
+        <source>Will be deleted on update</source>
+        <target>Wordt verwijdert bij bijwerking</target>
+        <context-group name="null">
+          <context context-type="linenumber">176</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
+        <source>Cancel deletion</source>
+        <target>Annuleer verwijdering</target>
+        <context-group name="null">
+          <context context-type="linenumber">178</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
+        <source>
+            No captions for now.
+          </source>
+        <target>
+Geen ondertiteling voor nu.</target>
+        <context-group name="null">
+          <context context-type="linenumber">183</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
+        <source>Captions</source>
+        <target>Ondertiteling</target>
+        <context-group name="null">
+          <context context-type="linenumber">140</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Thumbnail uploaden</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Voorvertoning uploaden</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -1284,26 +2871,238 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
       </trans-unit>
       <trans-unit id="f61f989de6fc12f99369a90800e4b5462d3f10a0">
         <source>Short text to tell people how they can support you (membership platform...).</source>
-        <target>Korte tekst om mensen te vertellen hoe ze je kanaal kunnen ondersteunen (bv. door gelddonaties).</target>
+        <target>Korte tekst om mensen te vertellen hoe ze je kanaal kunnen ondersteunen (lidmaatschapsplatforms...).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Geavanceerde instellingen</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
         <source>
     Update <x id="INTERPOLATION" equiv-text="{{ video?.name }}"/>
   </source>
+        <target>
+<x id="INTERPOLATION" equiv-text="{{ video?.name }}"/> updaten</target>
         <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="9aafb2a928664aa7a9375fd37c533f0375f8b611">
+        <source>Download video</source>
+        <target>Download video</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8d6a41c2703bed3edfc76e1df0b1ca203404c17c">
+        <source>Direct download</source>
+        <target>Directe download</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac3a02ecd20f41278f1ef7c03f45c1117b4b796d">
+        <source>Torrent (.torrent file)</source>
+        <target>Torrent (.torrent bestand)</target>
+        <context-group name="null">
+          <context context-type="linenumber">32</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2db8d7cf6a3071f4c1519ef2b5e2713d9ff4e87f">
+        <source>Torrent (magnet link)</source>
+        <target>Torrent (magnet link)</target>
+        <context-group name="null">
+          <context context-type="linenumber">37</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="da44efc7b658c318651866454d258bbbe57ff21c">
+        <source>
+      Cancel
+    </source>
+        <target>
+Annuleren</target>
+        <context-group name="null">
+          <context context-type="linenumber">47</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dc75033a5238fdc4f462212c847a45ba8018a3fd">
+        <source>Download</source>
+        <target>Downloaden</target>
+        <context-group name="null">
+          <context context-type="linenumber">84</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="11749f4fc0aa1b5e37f38575e4d4e3b1b7e0e96b">
+        <source>Report video</source>
+        <target>Rapporteer video</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
+        <source>Share</source>
+        <target>Deel</target>
+        <context-group name="null">
+          <context context-type="linenumber">74</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e0cfbc8ea680e4527ebf094c035f3342e9146d9f">
+        <source>QR-Code</source>
+        <target>QR-Code</target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d3b15c3bf4a7ea38d6002d2d2c4781642d30e79c">
+        <source>Embed</source>
+        <target>Inbedden</target>
+        <context-group name="null">
+          <context context-type="linenumber">34</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="90e0a0a3da80b46e550c1395ff4e97c27259bef8">
+        <source>
+      The url is not secured (no HTTPS), so the embed video won't work on HTTPS websites (web browsers block non secured HTTP requests on HTTPS websites).
+    </source>
+        <target>
+Deze url is niet beveiligd (geen HTTPS), dus de ingebedde video werkt niet op HTTPS websites (web browsers blokkeren onbeveiligde HTTP verzoeken op HTTPS websites).</target>
+        <context-group name="null">
+          <context context-type="linenumber">45</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4e529ae5ffd73001d1ff4bbdeeb0a72e342e5c8">
+        <source>Close</source>
+        <target>Sluiten</target>
+        <context-group name="null">
+          <context context-type="linenumber">51</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f672385c803647b063687d3c912e2ce5738b51c8">
+        <source>Blacklist video</source>
+        <target>Video op de zwarte lijst zetten</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7584313e33a66811eb10646627914a01fff0347d">
+        <source>
+    The video is being imported, it will be available when the import is finished.
+  </source>
+        <target>
+De video wordt geimporteerd, hij zal beschikbaar worden wanneer de import klaar is.</target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9ed65ae88f6c982bc44d6fed2796e55f47dbf304">
+        <source>
+    The video is being transcoded, it may not work properly.
+  </source>
+        <target>
+De video wordt getranscode, hij kan misschien niet naar behoren werken.</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c89a08fd2a05d1013fed8478024f5ba37ac3d308">
+        <source>
+    This video will be published on <x id="INTERPOLATION" equiv-text="{{ video.scheduledUpdate.updateAt | date: 'full' }}"/>.
+  </source>
+        <target>
+Deze video wordt gepubliceerd op <x id="INTERPOLATION" equiv-text="{{ video.scheduledUpdate.updateAt | date: 'full' }}"/>.</target>
+        <context-group name="null">
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd7055d3e38beff538463e75d508d1c75c683710">
+        <source>This video is blacklisted.</source>
+        <target>Deze video staat op de zwarte lijst.</target>
+        <context-group name="null">
+          <context context-type="linenumber">24</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3da5360f8314aa95973aa52629c9f635363c5a36">
+        <source>
+                Published <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
+              </source>
+        <target>
+Gepubliceerde <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> weergaven</target>
+        <context-group name="null">
+          <context context-type="linenumber">37</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="07087373dbf99b5e8b2b2f962fd53baa97d9ab95">
+        <source>
+                  Published <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
+                </source>
+        <target>
+Gepubliceerde <x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> weergaven</target>
+        <context-group name="null">
+          <context context-type="linenumber">46</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82b59049f3f89d900c98da9319e156dd513e3ced">
+        <source>Like this video</source>
+        <target>Like deze video</target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="623698f075025b2b2fc2e0c59fd95f4f4662a509">
+        <source>Dislike this video</source>
+        <target>Dislike deze video</target>
+        <context-group name="null">
+          <context context-type="linenumber">64</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="144fff5c40b85414d59e644d8dee7cfefba925a2">
+        <source>Download the video</source>
+        <target>Download de video</target>
+        <context-group name="null">
+          <context context-type="linenumber">83</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f72992030f134408b675152c397f9d0ec00f3b2a">
+        <source>Report</source>
+        <target>Rapporteer</target>
+        <context-group name="null">
+          <context context-type="linenumber">88</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2f4894617d9c44010f87473e583bd4604b7d6ecf">
+        <source>Report this video</source>
+        <target>Rapporteer deze video</target>
+        <context-group name="null">
+          <context context-type="linenumber">87</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cd27f761b923a5bdb16ba9844da632edd878f1b1">
+        <source>Update this video</source>
+        <target>Werk deze video bij</target>
+        <context-group name="null">
+          <context context-type="linenumber">91</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="007ab5fa2aae8a7372307d3fc45a2dbcb11ffd61">
+        <source>Blacklist</source>
+        <target>Zet op de zwarte lijst</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="803c6317abd2dbafcc93226c4e273c62932e3037">
+        <source>Blacklist this video</source>
+        <target>Zet deze video op de zwarte lijst</target>
+        <context-group name="null">
+          <context context-type="linenumber">95</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="86f26b106c67be3c2e98b82766656e5d9da86dff">
         <source>Unblacklist</source>
         <target>Van zwarte lijst halen</target>
@@ -1311,30 +3110,2464 @@ Als je een video uploadt in dit kanaal, wordt deze tekst ingevuld in het "onders
           <context context-type="linenumber">100</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8e6d54c4f760d9e90518eef5334211c48c0b71e2">
-        <source>Publication scheduled on </source>
-        <target>Publicatie uitgesteld tot</target>
+      <trans-unit id="61021f5011bc24f69cfc3f6dbbbd8f1948328b25">
+        <source>Unblacklist this video</source>
+        <target>Haal deze video van de zwarte lijst</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">99</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1c417b7aef730d6ef5d62fa8a0a7e25e3a2393e4">
-        <source>Display name is required.</source>
-        <target>Een weergavenaam is verplicht</target>
+      <trans-unit id="3dbfdc68f83d91cb360172eb65578cae94e7cbe5">
+        <source>Delete this video</source>
+        <target>Verwijder deze video</target>
+        <context-group name="null">
+          <context context-type="linenumber">103</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5cb397241041f7ad70997806227bafcdf7eb1b33">
+        <source>Go the channel page</source>
+        <target>Ga naar kanaalpagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">123</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0b7f242da10ece3f2995095c455b9a92ebcdd3b4">
+        <source>By <x id="INTERPOLATION" equiv-text="{{ video.byAccount }}"/></source>
+        <target>Door <x id="INTERPOLATION" equiv-text="{{ video.byAccount }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">134</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f0c5f6f270e70cbe063b5368fcf48f9afc1abd9b">
+        <source>Show more</source>
+        <target>Laat meer zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">146</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5403a767248e304199592271bba3366d2ca3f903">
+        <source>Show less</source>
+        <target>Laat minder zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">152</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4c0ba3cde3b3c58b855ffb4beaa5804a2fc3826b">
+        <source>Friendly Reminder: </source>
+        <target>Vriendelijke Herinnering:</target>
+        <context-group name="null">
+          <context context-type="linenumber">208</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9e66f7507eb263abdbab7abafd825f1dc8bc880b">
+        <source>
+        the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers.
+      </source>
+        <target>
+        Uit het deelsysteem gebruikt voor deze video blijkt het dat sommige technische informatie over jouw systeem (zoals een openbaar IP adres) verstuurd kan worden naar andere peers.
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">209</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e60c11e1b1dfbbeda577364b8de39ded2d796c5e">
+        <source>More information</source>
+        <target>Meer informatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">212</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd499ca7913bb5408fd139a4cb4f863852d5f318">
+        <source>Get more information</source>
+        <target>Krijg meer informatie</target>
+        <context-group name="null">
+          <context context-type="linenumber">212</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="20fc98888baf65b5ba9fe9622dc036fa8dec6a5f">
+        <source>
+      OK
+    </source>
+        <target>
+      OK
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">215</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="abf2b0f7b6405fa2841ca39c827e86089a95cc27">
+        <source>
+        Other videos
+    </source>
+        <target>
+        Andere videos
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b5f5df598f2d75640849b2a7744f91e5dbd390e7">
+        <source>
+      Comments
+    </source>
+        <target>
+      Reacties
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="17810e68b0ba21e62e61eecfaf0a93b2c91033b4">
+        <source>No comments.</source>
+        <target>Geen reacties</target>
+        <context-group name="null">
+          <context context-type="linenumber">17</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="69c081796209e45e26af91152ec9bd0a65ec261e">
+        <source>View all <x id="INTERPOLATION" equiv-text="{{ comment.totalReplies }}"/> replies</source>
+        <target>Laat alle <x id="INTERPOLATION" equiv-text="{{ comment.totalReplies }}"/> antwoorden zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">54</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b7fccd922d6473725247ed85a9fdf96fe6794828">
+        <source>
+    Comments are disabled.
+  </source>
+        <target>
+    Reacties zijn uitgeschakeld.
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">63</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="db79255cb8757e9e945ba5f901a2b67e4189016e">
+        <source>Add comment...</source>
+        <target>Voeg reactie toe...</target>
+        <context-group name="null">
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="26fa50ba8e69b53162b348d98e25f8b76c81343e">
+        <source>
+      Post comment
+    </source>
+        <target>
+      Post reactie.
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">20</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8b2bb53dfb5f059f2b68cc4ac00661a865909135">
+        <source>You are one step away from commenting</source>
+        <target>Je bent een stap verwijderd van reageren</target>
+        <context-group name="null">
+          <context context-type="linenumber">28</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7984a44ce86b961f4f18c9a58c638f5e8f07a225">
+        <source>
+      If you have an account on this instance, you can login:
+    </source>
+        <target>
+      Als je een account hebt op deze instantie, kan je inloggen:
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">32</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="afe0ad39fee662489f1033e53aea3e16a7e89228">
+        <source>login to comment</source>
+        <target>log in om te reageren</target>
+        <context-group name="null">
+          <context context-type="linenumber">35</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a5a3f17c9b4876952d78363834d57280c8684e7c">
+        <source>
+      Otherwise you can comment using an account on any ActivityPub-compatible instance.
+      On most platforms, you can find the video by typing its URL in the search bar and then comment it
+      from within the software's interface.
+    </source>
+        <target>
+      Anders kan je reageren door een account te gebruiken op welke ActivityPub-compatibele instatie dan ook.
+      Op de meeste platforms kan je de video vinden door zijn URL in de zoekbalk te typen en vervolgens het vanuit binnenin de software's interface te reageren.</target>
+        <context-group name="null">
+          <context context-type="linenumber">36</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="968b02fbc645be799727de0d1ec3c6f9b11b20eb">
+        <source>
+      If you have an account on Mastodon or Pleroma, you can open it directly in their interface:
+    </source>
+        <target>
+Als je een account op Mastodon of Pleroma hebt, kan je het direct openen vanuit hun interface:</target>
+        <context-group name="null">
+          <context context-type="linenumber">41</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a607fab03e11b0e07c1640e11a1b02d7af06b285">
+        <source>Highlighted comment</source>
+        <target>Belichte reactie</target>
+        <context-group name="null">
+          <context context-type="linenumber">5</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cb23d4d98007aa4d7123837f4c17a671848377d6">
+        <source>Reply</source>
+        <target>Antwoord</target>
+        <context-group name="null">
+          <context context-type="linenumber">14</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
+        <source>No description</source>
+        <target>Geen beschrijving</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2f03e577e8f81a9f8be0095f93e1f9376c6eedc9">
+        <source>Published videos</source>
+        <target>Gepubliceerde videos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="369ef5e9c0dd1251abdbf699a5db408bca10777f">
+        <source>Published <x id="INTERPOLATION" equiv-text="{{totalVideos}}"/> videos</source>
+        <target><x id="INTERPOLATION" equiv-text="{{totalVideos}}"/> Videos gepubliceerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
+        <source>240p</source>
+        <target>240p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c8cfad7e7a16c57c42535331b65cb7de40d8402e">
+        <source>360p</source>
+        <target>360p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="48f0af5a0d0bea4e84b27eaf41b19c85a531c2a5">
+        <source>480p</source>
+        <target>480p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6f06138daf6363746ff26bfc0cb2491c09cdfdf2">
+        <source>720p</source>
+        <target>720p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="65c94f9beb6fe957808c40060da280cc7ace7ab9">
+        <source>1080p</source>
+        <target>1080p</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="421a937491f19774d17eefa1d24816dae1a9f111">
+        <source>Auto (via ffmpeg)</source>
+        <target>Auto (via ffmpeg)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
+        <source>Configuration updated.</source>
+        <target>Configuratie bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="aa6fb95c355f172bda303de1ce2f38c251a149cf">
+        <source>Unlimited</source>
+        <target>Oneindig</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="54adc67482fdaa0d361a2992bc91e064dc61cc9a">
+        <source>100MB</source>
+        <target>100MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cd34ef1f476d5422f49f6ed429f61fc1cfcb1174">
+        <source>500MB</source>
+        <target>500MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a47b4beea31cac6e5970b6bc522902f545acc8b">
+        <source>1GB</source>
+        <target>1GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b26d0cac75638623098ab7e06e16b096d1f55cc8">
+        <source>5GB</source>
+        <target>5GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f9fc4e7ec6743cb6f69bea2d0859a655ed44ffae">
+        <source>20GB</source>
+        <target>20GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a56e3f92fe16d97ee4f05051ea61c466ecb51d5e">
+        <source>50GB</source>
+        <target>50GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="31dcc0c63f6234ace8caa84ae1abc33d4022122d">
+        <source>10MB</source>
+        <target>10MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f2f968b6f2199b919f567702c6f23b43e5ea71af">
+        <source>50MB</source>
+        <target>50MB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c31575424fe1b2a57064413f3eda7ce657c46c8a">
+        <source>2GB</source>
+        <target>2GB</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fc5731a28a99b25c62d43333ceebb250d60aff84">
+        <source><x id="INTERPOLATION" equiv-text="{{host}}"/> is not valid</source>
+        <target><x id="INTERPOLATION" equiv-text="{{host}}"/> is niet valide</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e02f50674f1d96966384dc096beb42d4973997df">
+        <source>You need to specify hosts to follow.</source>
+        <target>Je moet de hosts specificeren om te volgen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c2a114eb000e7c38e8ad4b1768821bdf6e946d71">
+        <source>Hosts need to be unique.</source>
+        <target>Hosts moeten uniek zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a6718d6aaf5bcd1692eed48daa61d2bed62c1f50">
+        <source>If you confirm, you will send a follow request to:<x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/> - </source>
+        <target>Als je bevestigd, verzend je een volgverzoek naar:<x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/> - </target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1266acb081ef0324c4a38ae2d514dd75d8b38409">
+        <source>Follow new server(s)</source>
+        <target>Volg nieuwe server(s)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="950f5111d567e5c0e971f07c26e8c2be1d919a8e">
+        <source>Follow request(s) sent!</source>
+        <target>Volgverzoek(en) verstuurd!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5729c34a858c78daa1aa606f62a3665527cf97e6">
+        <source>Do you really want to unfollow <x id="INTERPOLATION" equiv-text="{{host}}"/>?</source>
+        <target>Wil je echt <x id="INTERPOLATION" equiv-text="{{host}}"/>  onvolgen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a89875525c82ab81ffe32e481a5475b43d0c2902">
+        <source>Unfollow</source>
+        <target>Onvolgen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fb4e35e2b0ea2abc1f71295a4b34830e57c07bd0">
+        <source>You are not following <x id="INTERPOLATION" equiv-text="{{host}}"/> anymore.</source>
+        <target>Je volgt <x id="INTERPOLATION" equiv-text="{{host}}"/> niet meer.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d8f527638f3e0b518a96e07d41d886bcce01246">
+        <source>enabled</source>
+        <target>ingeschakeld</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="795733aac948794cadeb3be6386882efac2c38ad">
+        <source>disabled</source>
+        <target>uitgeschakeld</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1123807fc813c816404598147173403d00117557">
+        <source>Redundancy for <x id="INTERPOLATION" equiv-text="{{host}}"/> is <x id="INTERPOLATION_1" equiv-text="{{stateLabel}}"/></source>
+        <target>Overtolligheid voor<x id="INTERPOLATION" equiv-text="{{host}}"/> is <x id="INTERPOLATION_1" equiv-text="{{stateLabel}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="53cc0f4a4566c4139c65f93b5dce2fe8302e78da">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> niet meer gedempt door jouw instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="468b52e3c04fb9a3d8c8213555dfcad0cbcae330">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</source>
+        <target>Instantie <x id="INTERPOLATION" equiv-text="{{host}}"/> niet meer gedempt door jouw instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="800cd3cdf47751b576587259ba3a1bc0a7f435b6">
+        <source>Comment updated.</source>
+        <target>Reactie bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="586bee8c27a761611eb05661524cc7ca944b5978">
+        <source>Delete this report</source>
+        <target>Verwijder deze rapportage</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cf3b28ba29a907b334ab0e6dccd080a60ba23321">
+        <source>Update moderation comment</source>
+        <target>Werk beheerder-reactie bij</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d512430037b6580ba970c80cfc1687b6bdc221a3">
+        <source>Mark as accepted</source>
+        <target>Markeer als geaccepteerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d895b090c054bfc0ad3aba816af0615a1997f5a3">
+        <source>Mark as rejected</source>
+        <target>Markeer als afgewezen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73b70e37cddaa6494d8a666b6cba90dc80595599">
+        <source>Do you really want to delete this abuse report?</source>
+        <target>Wil je echt dit misbruiksrapportage verwijderen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6a7938b8780c27540ea70cc0f8f4d928c8916cf9">
+        <source>Abuse deleted.</source>
+        <target>Misbruik verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="652845b2b32b2e117b9b02879b1af07859b0e223">
+        <source>Do you really want to remove this video from the blacklist? It will be available again in the videos list.</source>
+        <target>Wil je deze video echt verwijderen van je zwarte lijst? Hij zal weer beschikbaar zijn in de videolijst.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1585babc36806e20e225ac27dbba0e7c7cd09e0f">
+        <source>Video <x id="INTERPOLATION" equiv-text="{{name}}"/> removed from the blacklist.</source>
+        <target>Video <x id="INTERPOLATION" equiv-text="{{name}}"/> verwijdert van de zwarte lijst.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="364463fab6c5714118d6449561a0f8de1cc10bfa">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> created.</source>
+        <target>Gebruiker <x id="INTERPOLATION" equiv-text="{{username}}"/> verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="964865a3cd90b4af99902f071644a4b2aede4c32">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> updated.</source>
+        <target>Gebruiker <x id="INTERPOLATION" equiv-text="{{username}}"/> bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9910122dfedd2eaa544a990f1430e5b82a76d99f">
+        <source>Update user</source>
+        <target>Werk gebruiker bij</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="50dc7afa2305131cdbdb384cfc1f2a5f0f4647d8">
+        <source>Unban</source>
+        <target>Onverban</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>Zet E-mail als Geverifieerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
+        <source>You cannot ban root.</source>
+        <target>Je kan root niet verbannen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98119091712a8ca72905e3b4c1cf60649af7565e">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{num}}"/> users?</source>
+        <target>Wil jij echt <x id="INTERPOLATION" equiv-text="{{num}}"/> gebruikers onverbannen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6121be086a51c4c73bbdd8aebdddd9744c8f1ffd">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users unbanned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> gebruikers onverbannen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="911fc197949e47aa5f0541627bc319f59edd9d11">
+        <source>You cannot delete root.</source>
+        <target>Je kan root niet verwijderen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9de914fe915cc730efc57e81c987188a24d3ac51">
+        <source>If you remove these users, you will not be able to create others with the same username!</source>
+        <target>Als jij deze gebruikers verwijdert, kan je niet meer anderen maken met dezelfde gebruikersnaam!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b708d332e3f89b24745e749fa530210f0bdea329">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> gebruikers verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> gebruikers'' gezet als geverifieerd.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> niet meer gedempt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c6af80b42938d4a49e6f6c4f60ce26228916994c">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</source>
+        <target>Instantie <x id="INTERPOLATION" equiv-text="{{host}}"/> niet meer gedempt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="507192ee1fa84aefed02d603caada2d84927023e">
+        <source>Ownership accepted</source>
+        <target>Eigendom geaccepteerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="19508af0dfbc685cbf10cf02061bb5a0f423b6fc">
+        <source>Password updated.</source>
+        <target>Wachtwoord bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="466fc8cf56fd4e4e90fec4b900ef083d52bec38c">
+        <source>You current password is invalid.</source>
+        <target>Jouw huide wachtwoord is invalide.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ca8e8cf0f1686604db3b6a2ebadab7f7b426a047">
+        <source>Are you sure you want to delete your account? This will delete all you data, including channels, videos etc.</source>
+        <target>Weet je zeker dat je jouw account wil verwijderen? Dit verwijdert al jouw data, inclusief kanalen, videos etc.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e7d5b2de566e4c807c285daf8d8a78b5f7f33311">
+        <source>Type your username to confirm</source>
+        <target>Typ je gebruikersnaam in om te bevestigen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d8a8a7f7160939fb55e82bc01fe9f876f5f2e065">
+        <source>Delete my account</source>
+        <target>Verwijder mijn account</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8eb8b1a728159f43c31abf76c28ef3ff6c230af7">
+        <source>Your account is deleted.</source>
+        <target>Jouw account is verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="db4ff52375f6a25ad0472e92754c8c265ae47c6b">
+        <source>Profile updated.</source>
+        <target>Profiel bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e003ad599ef836949b9f4dad3037a58ef3ff8d1">
+        <source>Avatar changed.</source>
+        <target>Avatar verandert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="214b802dfd6f544003147a7a68938ec1055c8f32">
+        <source>Information updated.</source>
+        <target>Informatie bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3ef8bf973a9a481a08c6f0aaa875f0259b3ea645">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> created.</source>
+        <target>Videokanaal <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> gecreëerd.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f359f6adf6cccca7770019f947ed594169ee7d47">
+        <source>This name already exists on this instance.</source>
+        <target>Deze naam bestaat al op deze instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="70a67e04629f6d412db0a12d51820b480788d795">
+        <source>Create</source>
+        <target>Creeër</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="98ab64f0af924a60a48b40835c1b655bd17c6559">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> updated.</source>
+        <target>Videokanaal <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Videokanaal <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Mijn video's</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="00e16d1f1c5cc936ec0881cd02cbf66aa1b4cddd">
+        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{deleteLength}}"/> videos?</source>
+        <target>Wil jij echt <x id="INTERPOLATION" equiv-text="{{deleteLength}}"/> videos verwijderen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dff7d4574cfaa785cbd4c0a5ffb5befec19a5d83">
+        <source><x id="INTERPOLATION" equiv-text="{{deleteLength}}"/> videos deleted.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{deleteLength}}"/> videos verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ec5852c869b2fb4ae0e564b51278d7be8013fc7">
+        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoName}}"/>?</source>
+        <target>Weet jij zeker dat je<x id="INTERPOLATION" equiv-text="{{videoName}}"/> wilt verwijderen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d39a0bfa616a9a8473b2e379eefe17d8ed1af118">
+        <source>Video <x id="INTERPOLATION" equiv-text="{{videoName}}"/> deleted.</source>
+        <target>Video <x id="INTERPOLATION" equiv-text="{{videoName}}"/> verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dd9f3264feed4935008861c15d81c947124e4ac3">
+        <source>Published</source>
+        <target>Gepubliceerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8e6d54c4f760d9e90518eef5334211c48c0b71e2">
+        <source>Publication scheduled on </source>
+        <target>Publicatie uitgesteld tot</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a7e91ebe1cf184db5f2bfecf9c16ff81c9e2c02">
+        <source>Waiting transcoding</source>
+        <target>Wachten op transcoding</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="21f1c9d5c67346c830aced4f670045fcf0aeb83a">
+        <source>To transcode</source>
+        <target>Om te transcoden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="289fe8342e8b7df689c75026a24a60fd7f5e9392">
+        <source>To import</source>
+        <target>Om te importeren</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="740c53a50a618bf5c7a5bd5c3f7321f0bd1840dd">
+        <source>Ownership change request sent.</source>
+        <target>Eigenaarsveranderingsaanvrag gestuurd.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Mijn bibliotheek</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Mijn kanalen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Mijn abonnementen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Varia</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Veranderingen van eigenaar</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Mijn instellingen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="af55337b4032d675ab6b2081af797ca9c979b706">
+        <source>An email with verification link will be sent to <x id="INTERPOLATION" equiv-text="{{email}}"/>.</source>
+        <target>Een e-mail met verificatielink wordt verstuurd naar <x id="INTERPOLATION" equiv-text="{{email}}"/>.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ccbf0490fb6b60d21e03bb2c9003df0ce1a58752">
+        <source>Unable to find user id or verification string.</source>
+        <target>Niet in staat om gebruikersid of verificatiestring te vinden.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ff6becacbce7fc0943b0af0df4dd67e5e11bf598">
+        <source>Subscribe to the account</source>
+        <target>Abonneer op het account</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c95cc372311830f936b39f73c5d6d20c0b16013">
+        <source>Focus the search bar</source>
+        <target>Focus de zoekbalk</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b19ee83cbd2b735fd081b9aa483a890578019099">
+        <source>Toggle the left menu</source>
+        <target>Schakel het linker menu aan of uit</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b54759e30f7c1983940cdacb8eb03f102a869084">
+        <source>Go to the videos overview page</source>
+        <target>Ga naar de video-overzichtspagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e919c88a3f889d6659288e69d3e178da0ea7ab0">
+        <source>Go to the trending videos page</source>
+        <target>Ga naar de trending videos pagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="249618dcdd7fbdc863c0714e2eb9e8940bc9c37d">
+        <source>Go to the recently added videos page</source>
+        <target>Ga naar recent toegevoegde videos pagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7e194daef3a3509128c4300d4c7c292c49ebf3f5">
+        <source>Go to the local videos page</source>
+        <target>Ga naar de locale videos pagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1fb6204f39a7338e5110b2f113643c9288496ba">
+        <source>Go to the videos upload page</source>
+        <target>Ga naar de videos uploadpagina</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0ed7b40c11da9d4565af9c041df20c15bc6be97e">
+        <source>Toggle Dark theme</source>
+        <target>Schakel donker thema aan of uit</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="badd4b24618ccc8a34620acb9053fc654b9612b2">
+        <source>Go to my subscriptions</source>
+        <target>Ga naar mijn abonnementen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b7184b5a236618e8edd747529869c392ab6dace1">
+        <source>Go to my videos</source>
+        <target>Ga naar mijn videos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="acf985bd42886b9b3030b5f68f0e8417c39b40a7">
+        <source>Go to my imports</source>
+        <target>Ga naar mijn imports</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cfe3c51f0ae9385dc2ce6df740d87e5514aa9390">
+        <source>Go to my channels</source>
+        <target>Ga naar mijn kanalen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="edeaa933b09690523e46977e11064e9c655d77d7">
+        <source>Cannot retrieve OAuth Client credentials: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
+</source>
+        <target>Kan niet credenties van OAuth Client verkrijgen: <x id="INTERPOLATION" equiv-text="{{errorText}}"/>.
+</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8d9b4f4b69108c3c9aa0f3b0dbde87786ba9b319">
+        <source>Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.</source>
+        <target>Weet zeker dat je correct PeerTube geconfigureerd hebt (config/directory), vooral de "webserver" sectie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Fout</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
+        <source>You need to reconnect.</source>
+        <target>Je moet opnieuw verbinden.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="68e710782ccb5398b3acb8844caf0b199da2c3da">
+        <source>Confirm</source>
+        <target>Bevestigen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5c0c574151dc8671d9199980ee04bf65aec3b452">
+        <source>Keyboard Shortcuts:</source>
+        <target>Keyboard Shortcuts:</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Info</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Success</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
+        <source>Incorrect username or password.</source>
+        <target>Incorrecte gebruikersnaam of wachtwoord.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="39980cc1cf8df621d43f5480d001bdf5d4139338">
+        <source>You account is blocked.</source>
+        <target>Jouw account is geblokkeerd.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7701e3762dc4a2b2e302c24f17820bc8dd7cacc1">
+        <source>An email with the reset password instructions will be sent to <x id="INTERPOLATION" equiv-text="{{email}}"/>.</source>
+        <target>Een e-mail met de wachtwoord reset instructies wordt gestuurd naar <x id="INTERPOLATION" equiv-text="{{email}}"/>.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b0f24b7136e551a0deba831f1525711245b31a26">
+        <source>Your password has been successfully reset!</source>
+        <target>Jouw wachtwoord is succesvol gereset!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7fb1099e29660162f9154d5b2feee7743a423df6">
+        <source>Today</source>
+        <target>Vandaag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="02e0243b60007368f87dc01e083f232dd025096d">
+        <source>Last 7 days</source>
+        <target>Laatste 7 dagen </target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7668986b9f753fcd72ad4a00b1a0c4861d1f7fb8">
+        <source>Last 30 days</source>
+        <target>Laatste 30 dagen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a77b663fd9b94c38bc9c6493a51b5f3acacb9bca">
+        <source>Last 365 days</source>
+        <target>Laatste 365 dagen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d2f3bf121699ff08a25fa4859bfdf3996bf821cc">
+        <source>Short (&lt; 4 min)</source>
+        <target>Kort (&lt; 4 min)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ac0fa1039f09ec0d917303658c5bb1ee813a4225">
+        <source>Long (&gt; 10 min)</source>
+        <target>Long (&gt; 10 min)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f24d368d6be0fee70fb4503d2ad37a612e1b0889">
+        <source>Medium (4-10 min)</source>
+        <target>Middelmatig (4-10 min)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ed073fec00d699b7a97bb65b4f3a722b203c5bca">
+        <source>Relevance</source>
+        <target>Relevantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1aee80ab35aa99508802cdec6306e110b2feaf9e">
+        <source>Publish date</source>
+        <target>Publicatiedatum</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b7641aed03492978b4ec6843b1e53f30464294d9">
+        <source>Views</source>
+        <target>Weergaven</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7e892ba15f2c6c17e83510e273b3e10fc32ea016">
+        <source>Search</source>
+        <target>Zoeken</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b67c8e57904c67c4566610363b7f82c748d04323">
+        <source>Instance name is required.</source>
+        <target>Instantienaam is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="10a248adb1ee12830355a04ac3cde2bad2d41d7d">
+        <source>Short description should not be longer than 250 characters.</source>
+        <target>Korte beschrijvingen moeten niet langer zijn dan 250 karakters.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="356e63270712273da168072ec0fc78a969919bf1">
+        <source>Twitter username is required.</source>
+        <target>Twitter gebruikersnaam is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dbb2ef02020afc05e146855f2e1dd7c9522d49b6">
+        <source>Previews cache size is required.</source>
+        <target>Cachegrootte van voorvertoning is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="97836c6e698185b4ce357de9d4b2ab3e838f2459">
+        <source>Previews cache size must be greater than 1.</source>
+        <target>Cachegrootte van voorvertoning moet groter zijn dan 1.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e7393dc4a4aa12d005582eb9e1ddc7e5ca5bebd3">
+        <source>Previews cache size must be a number.</source>
+        <target>Cachegrootte van voorvertoning moet een nummer zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="545f5dea553b2d7c4a65920ccdcb1e9dbdc7f4d8">
+        <source>Captions cache size is required.</source>
+        <target>Cachegrootte van ondertiteling is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a8d7131c0ca1eefe7b058e6081236ca1be364e2c">
+        <source>Captions cache size must be greater than 1.</source>
+        <target>Cachegrootte van ondertiteling moet groter zijn dan 1.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c3decd47b03cf542df091c1a2fb25b756e59074e">
+        <source>Captions cache size must be a number.</source>
+        <target>Cachegrootte van ondertiteling moet een nummer zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2cdd5a8c604ef16c2f9a17ed81d73f4f9509e828">
+        <source>Signup limit is required.</source>
+        <target>Inschrijflimiet is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0ca9f7ec55c9896add6e82d2b52e9217e1140cf7">
+        <source>Signup limit must be greater than 1.</source>
+        <target>Inschrijflimiet moet groter zijn dan 1.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="58c2f66ba74f1400914031ef4ed635938e9e8ced">
+        <source>Signup limit must be a number.</source>
+        <target>Inschrijflimiet moet een nummer zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1245841647f9b42d3e7554903c1c50bdd80ab021">
+        <source>Admin email is required.</source>
+        <target>Administrator e-mail is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3fd2feb77dfe57fe82573e3cdf996105e2fafc66">
+        <source>Admin email must be valid.</source>
+        <target>Administrator e-mail moet valide zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f15f2e02b1f6a96553e98ea4a969045d17ec1400">
+        <source>Transcoding threads is required.</source>
+        <target>Transcoding threads zijn vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4166cc066b963a23829b48a09e394f73b453fabd">
+        <source>Transcoding threads must be greater or equal to 0.</source>
+        <target>Transcoding threads moeten groter of gelijk zijn aan 0.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>E-mail is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>E-mail moet valide zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
+        <source>Username is required.</source>
+        <target>Gebruikersnaam is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4eb39d69b74d7a56652ec84fa6826994ee26c0e5">
+        <source>Password is required.</source>
+        <target>Wachtwoord is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c90872a06666a51c2957c4b29724e68df5c67154">
+        <source>Confirmation of the password is required.</source>
+        <target>Bevestiging van het wachtwoord is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
+        <source>Password must be at least 6 characters long.</source>
+        <target>Wachtwoord moet minstens 6 karakters lang zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0a154031f3e66985af96d5f903441cf84f0dc75e">
+        <source>Password cannot be more than 255 characters long.</source>
+        <target>Wachtwoord kan niet langer zijn dan 255 karakters.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2db8f1f93a5485c32267762a3bf4da499832e732">
+        <source>The new password and the confirmed password do not correspond.</source>
+        <target>Het nieuwe wachtwoord en het bevestigde wachtwoord zijn niet hetzelfde.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="abede840116d58f04a55d99a6cbd68da8a3e1bbf">
+        <source>Video quota is required.</source>
+        <target>Videoquotum is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="93a6dc1d3aa0d3201c86ef1ec8adf5cf0ada3c80">
+        <source>Quota must be greater than -1.</source>
+        <target>Quota moet groter zijn dan -1</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7e58d1fb4e86af94f5199660ef349d55811888bb">
+        <source>Daily upload limit is required.</source>
+        <target>Dagelijks uploadlimiet is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e283cbc4469959ea664f9d545f15278e089a6f1e">
+        <source>Daily upload limit must be greater than -1.</source>
+        <target>Dagelijks uploadlimiet moet groter zijn dan -1.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="545e77fd5d9526228a2133109447c23225ed9c85">
+        <source>User role is required.</source>
+        <target>Gebruikersrol is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c417b7aef730d6ef5d62fa8a0a7e25e3a2393e4">
+        <source>Display name is required.</source>
+        <target>Een weergavenaam is verplicht.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
+        <source>Description must be at least 3 characters long.</source>
+        <target>Beschrijvingen moeten minstens 3 karakters lang zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a4179e366d4aa335f1ddd0a13e9109c71a9338d0">
+        <source>Description cannot be more than 1000 characters long.</source>
+        <target>Beschrijvingen mogen niet langer dan 1000 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a3ebc6ddb6b6677aed7b04eb503f9ddd0cfe561">
+        <source>You must to agree with the instance terms in order to registering on it.</source>
+        <target>Je moet akkoord gaan met de instantie's termijnen om erop te registreren.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6d2c3ebffd49b8933200a6d4e5b74712be49bf00">
+        <source>Ban reason must be at least 3 characters long.</source>
+        <target>Verbanningsreden moet minstens 3 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="be32ff1dd6e464c5c085dd7d128316f476d2e0fd">
+        <source>Ban reason cannot be more than 250 characters long.</source>
+        <target>Verbanningsreden kan niet langer dan 250 tekens zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b3cf1889d2fdd6b15e697c270c9b80772fe2cae6">
+        <source>Report reason is required.</source>
+        <target>Rapportagereden is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="993f9f5703d449a1d467243db75253d288a2947e">
+        <source>Report reason must be at least 2 characters long.</source>
+        <target>Rapportagereden moet minstens 2 tekens zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
+        <source>Moderation comment is required.</source>
+        <target>Beheersreactie is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82e31d0837eaa69a4364e7434d253ce138b3c5c2">
+        <source>Moderation comment must be at least 2 characters long.</source>
+        <target>Beheersreactie moet minstens 2 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
+        <source>The channel is required.</source>
+        <target>Het kanaal is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0776b05d442a0a16f083a5eefa52a166b9d514ca">
+        <source>Blacklist reason must be at least 2 characters long.</source>
+        <target>Zwartelijstreden moet minstens 2 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5009443905b0b152915247799492bf5e164e7626">
+        <source>Blacklist reason cannot be more than 300 characters long.</source>
+        <target>Zwartelijstreden kan niet langer dan 300 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c9eadf8830b3bc09bd444d739af86414eed9bd9e">
+        <source>Video caption language is required.</source>
+        <target>Video ondertitelingstaal is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="82083ae96724851ff37e1c7e4e9f907c25677963">
+        <source>Video caption file is required.</source>
+        <target>Video ondertitelingsbestand is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bd7fc070c728dc6dbf3959d49fe5bb27ce15d294">
+        <source>The username is required.</source>
+        <target>De gebruikersnaam is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c8465c3773699dd075e0147e264d2e232f605803">
+        <source>You can only transfer ownership to a local account</source>
+        <target>Je kan alleen je eigendom transporteren naar een lokaal account</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="541087322c34e8b26954fd67ff4fc80d1a6c1b33">
+        <source>Name is required.</source>
+        <target>Naam is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
+        <source>Support text must be at least 3 characters long.</source>
+        <target>Supporttekst moet minstens 3 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="15ec53d9ee65cb930c5f5d10ae2e8dd3fd44fc85">
+        <source>Support text cannot be more than 1000 characters long.</source>
+        <target>Supporttekst mag niet meer zijn dan 1000 karakters.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6ca60e0f6dfbc0073b0514bce7d273150b0b9e79">
+        <source>Comment is required.</source>
+        <target>Reactie is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5a94cae76685e72f33541b977efdd7845cb0ed6">
+        <source>Comment must be at least 2 characters long.</source>
+        <target>Reactie moet minstens 2 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7c194080446ee6901fd17a8b8648534ffe98b123">
+        <source>Comment cannot be more than 3000 characters long.</source>
+        <target>Reactie kan niet meer dan 3000 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cdc51eaeab88683610a28af8645cf91d136b39e1">
+        <source>Video name is required.</source>
+        <target>Videonaam is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c27cc734f76efd221663921dd0898ea7c8bcbb5c">
+        <source>Video name must be at least 3 characters long.</source>
+        <target>Videonaam moet minstens 3 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0320d0f7f8eec2341e27ca53d7875217a3d99695">
+        <source>Video name cannot be more than 120 characters long.</source>
+        <target>Videonaam kan niet meer dan 120 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a627c58cf1849d7d838696e7f36c1bae1a8b31a4">
+        <source>Video privacy is required.</source>
+        <target>Videoprivacy is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="97afb789c1ab09074495d49aaadb92a1c3e71a16">
+        <source>Video channel is required.</source>
+        <target>Videokanaal is vereist.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="af5e2d5f3ac817c735fb7ff9ca16322789f66fef">
+        <source>Video description must be at least 3 characters long.</source>
+        <target>Videobeschrijving moet minstens 3 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ce28a9403c2d7e5da2e59af27118f8b6d109e906">
+        <source>Video description cannot be more than 10000 characters long.</source>
+        <target>Videobeschrijving kan niet meer dan 10000 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1cffdc2e156716cd9880201d65ba457d11464f8">
+        <source>A tag should be more than 2 characters long.</source>
+        <target>Een tag moet minstens 2 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="34a0811f9a2a7366cc9efcdad52ea59b105326ea">
+        <source>A tag should be less than 30 characters long.</source>
+        <target>Een tag moet minder dan 30 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="665092574f9af9fec262f8349b67b14192391ae6">
+        <source>Video support must be at least 3 characters long.</source>
+        <target>Videosupport moet minstens 3 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f17de746af56840511cae11559539b6d8b6955ad">
+        <source>Video support cannot be more than 1000 characters long.</source>
+        <target>Videosupport kan niet meer dan 1000 karakters zijn.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="453413bf387dea681958871319bab489dd5e6ec0">
+        <source>A date is required to schedule video update.</source>
+        <target>Een datum is vereist om videoupdates in te roosteren.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3b7ed22d0730d03b38c254332829d855ee7256c4">
+        <source>This file is too large.</source>
+        <target>Dit bestand is te groot.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0bf41abaa85526711f7952b4600e4044bc7f04a4">
+        <source>All unsaved data will be lost, are you sure you want to leave this page?</source>
+        <target>Alle onopgeslagen data zal verloren worden, weet je zeker dat je deze pagina wil verlaten?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a8059e31694578c1b0344a76a345357dd60e8f01">
+        <source>Warning</source>
+        <target>Waarschuwing</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8339364b054610983b7f2334bb807fff7613bddf">
+        <source>Sunday</source>
+        <target>Zondag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a43c57a7cbebf57eb33a2eae5e994c91d9887596">
+        <source>Monday</source>
+        <target>Maandag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="48a2a35957ce394eb2c59ae35c99642360af70ee">
+        <source>Tuesday</source>
+        <target>Dinsdag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b0af441f9ba8b82952b9ec10fb8c62e8fec67df9">
+        <source>Wednesday</source>
+        <target>Woensdag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="55c583b99c809818ec27df065ccf05357a6ac10b">
+        <source>Thursday</source>
+        <target>Donderdag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e91b54925dc5f490753f60f53ef6f8b4609e6215">
+        <source>Friday</source>
+        <target>Vrijdag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c0d2dd391a3eca8e841a5d0e035cd268280eb68e">
+        <source>Saturday</source>
+        <target>Zaterdag</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6549890cd0d6b59fb0e1aa383b00483a68a55eef">
+        <source>Sun</source>
+        <target>Zon</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3382aa5d7f520e197fb59a4995fe1beffca2d0ff">
+        <source>Mon</source>
+        <target>Maa</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f883ec926274974df0fc46c037cbffd6a863ebc9">
+        <source>Tue</source>
+        <target>Din</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="242b4f4b5651e24f9a9007ef153a57981e4b989d">
+        <source>Wed</source>
+        <target>Woe</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5a2c39d56b8f00a6a4670a63b53caacbda953be6">
+        <source>Thu</source>
+        <target>Don</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4cdf23d523a0e52e0dec9cd650ffd9bd6952792c">
+        <source>Fri</source>
+        <target>Vri</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1283d165a942d7f4c469ba34f99dbb9e927d0261">
+        <source>Sat</source>
+        <target>Zat</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2fba8448ff13105c57665a9a6ffcfe9615d855dd">
+        <source>Su</source>
+        <target>Zon</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="388144af7ac7651d2615b9be0e84f43ae71d9fb3">
+        <source>Mo</source>
+        <target>Ma</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d96313e42b5f0751ce2676a31d309b4d322ab462">
+        <source>Tu</source>
+        <target>Di</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="06cc3d39f78c0615b707cef39cd4875599611fef">
+        <source>We</source>
+        <target>Wo</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="790894436cca9d675d59be9a8aafd58acccde2cd">
+        <source>Th</source>
+        <target>Do</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="42dfe37169f8471367c31489155229bbe1747ea5">
+        <source>Fr</source>
+        <target>Vr</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1b64ea3e04ceeb512e8974eb0019dee4f211c7a0">
+        <source>Sa</source>
+        <target>Za</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e7815f1c4a6d3cc157a16407a48865023cc35ec0">
+        <source>January</source>
+        <target>Januari</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0393a96b58df82af39a2ec83deec624749e42036">
+        <source>February</source>
+        <target>Februari</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ea41ee3743ec5bdbbf863ab793bbdd6e6d9af96e">
+        <source>March</source>
+        <target>Maart</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b87ee784d9e93b5557aca9bdc9464dbd4328920a">
+        <source>April</source>
+        <target>April</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="862da1034ac2707cc44123ed963b2f42109b6b3e">
+        <source>May</source>
+        <target>Mei</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2f234249d4c3c39e27c0f05d4a6b73a7959caeb2">
+        <source>June</source>
+        <target>Juni</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="11447f95e83c8de675ab6c492150f88e4d9bd15e">
+        <source>July</source>
+        <target>July</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ddd9a3d59a8db4e822e54e9473c05b571aca9829">
+        <source>August</source>
+        <target>Augustus</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e21dc41f9b3fdaf35ab6b2d9e2e5e8a926fb1938">
+        <source>September</source>
+        <target>September</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71f49c502d13e22079a958a5532afa28dbe98b3b">
+        <source>October</source>
+        <target>Oktober</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="64b5ce921faa5e3d277d6d528ddcfc8c2bfe9f52">
+        <source>November</source>
+        <target>November</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2006e2aabb31714ebc684dc382539649f690ed5c">
+        <source>December</source>
+        <target>December</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8270e687cfb5624b3f6fbb7991a2e916c96464b7">
+        <source>Jan</source>
+        <target>Jan</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="23544170afbb981dd52750b641576841cf5dcf60">
+        <source>Feb</source>
+        <target>Feb</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1f14355742459b7d6a0126a1564e1c18f39f86e7">
+        <source>Mar</source>
+        <target>Mar</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="964a5f032bc846d32806a4838580a4f81cf14463">
+        <source>Apr</source>
+        <target>Apr</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8f7274f606f71d9290ed01c5683092d701632d7f">
+        <source>Jun</source>
+        <target>Jun</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7c3d8318d6d8d9920ae0a80350616732c33a3211">
+        <source>Jul</source>
+        <target>Jul</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="be1335ffd1c606321e2c020b638dd3c84b434212">
+        <source>Aug</source>
+        <target>Aug</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4f739d03be1c936c58978739c317d91566348204">
+        <source>Sep</source>
+        <target>Sep</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="6607cacb987a588530a13de7018d959240d19153">
+        <source>Oct</source>
+        <target>Oct</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e597400ded12a366855615e18fcc8f9ac05b72e0">
+        <source>Nov</source>
+        <target>Nov</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="adf2dfa2a9cb490d6a4a74510b7b0846b62d429e">
+        <source>Dec</source>
+        <target>Dec</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="99ee4faa69cd2ea8e3678c1f557c0ff1f05aae46">
+        <source>Clear</source>
+        <target>Wissen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8fb519ba47ea7806beeacdcd44829d85a2aa0cc5">
+        <source>yy-mm-dd </source>
+        <target>jj-mm-dd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a0fdb831d4557925dbaa4f8aff7e5035f7506411">
+        <source>Transcode your videos in multiple resolutions</source>
+        <target>Transcodeer je videos in meerdere resoluties</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="590fc27fcbd7dd680da2bb2da644a183338f6bd1">
+        <source>HTTP import (YouTube, Vimeo, direct URL...)</source>
+        <target>HTTP import(Youtube, Vimeo, directe URL...)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4e231a74ad4739e7b0606e8e66d5a656f5855a5a">
+        <source>Torrent import</source>
+        <target>Torrentimport</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7296e9f7cc4956b6d57c541728b0826e76d108ba">
+        <source>~ <x id="INTERPOLATION" equiv-text="{{minutes}}"/> <x id="ICU" equiv-text="{minutes, plural, =1 {...} other {...}}"/></source>
+        <target>~ <x id="INTERPOLATION" equiv-text="{{minutes}}"/> <x id="ICU" equiv-text="{minutes, plural, =1 {...} other {...}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="cf9ddbb55b25178660e09346209aedc10108aa24">
+        <source>{VAR_PLURAL, plural, =1 {minute} other {minutes} }</source>
+        <target>{VAR_PLURAL, plural, =1 {minute} other {minutes} }</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="10ffa5c3dbcee491d66f80d8d4dce3e119a6ec86">
+        <source><x id="INTERPOLATION" equiv-text="{{seconds}}"/> of full HD videos</source>
+        <target><x id="INTERPOLATION" equiv-text="{{seconds}}"/> aan full HD videos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="344ddae9f45b344e98e7b28cd5e33243982700f8">
+        <source><x id="INTERPOLATION" equiv-text="{{seconds}}"/> of HD videos</source>
+        <target><x id="INTERPOLATION" equiv-text="{{seconds}}"/> aan HD videos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="435c012df6dd990a1ccb7ee73dd79c488bde28b5">
+        <source><x id="INTERPOLATION" equiv-text="{{seconds}}"/> of average quality videos</source>
+        <target><x id="INTERPOLATION" equiv-text="{{seconds}}"/> aan gemiddelde kwaliteit videos</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0b2054a863319d2cf59867addd125b6717cae41d">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> years ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> jaar geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e622d3813449fe36371ea258281059306819199d">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> months ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> maanden geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2f8a5a5f7efb521d7d89dc659ff65dd13cb7b17b">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> month ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> maand geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1d1a46543a29096d3c6676be2d561380a0bc94e1">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> weeks ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> weken geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e1db0b98b6cdf817508195f3649c48475c32ae7e">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> week ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> week geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a7654c3ece96e777527606f1c2870d6ee0b180f7">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> days ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> dagen geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5b465235ae55091d32535e23dd180c407f1352d1">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> day ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> dag geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dc7addf53bd6405a9c746db6dfca665c33679a84">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> hours ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> uren geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d54a610250ed38efccf0e3afdd0004f6ad83ea8d">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> hour ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> uur geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9704e5e3adce178c127ead05f7057d3fb827308a">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> min ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> min geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="7a158a7555a44ea7eff9fa4988df9aa24d262ceb">
+        <source><x id="INTERPOLATION" equiv-text="{{interval}}"/> sec ago</source>
+        <target><x id="INTERPOLATION" equiv-text="{{interval}}"/> sec geleden</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="457f161d3ca706b8de263b0cd58e493d54e7d4c5">
+        <source><x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Markdown<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> compatible that supports:</source>
+        <target><x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>Markeer<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> compatibele dat support:</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ab4426b60f13c00b61d6b714d390dc629f314980">
+        <source>Emphasis</source>
+        <target>Nadruk</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="dc60677d5a906e69f38a5cf9da7f2eb03931bea0">
+        <source>Links</source>
+        <target>Links</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="80220239e07f36ea8d5f10118dc52ce4b13bc15a">
+        <source>New lines</source>
+        <target>Nieuwe lijnen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b15e7bec5c7833d2d9634946ccbed68967b1bee1">
+        <source>Lists</source>
+        <target>Lijsten</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="b73f7f5060fb22a1e9ec462b1bb02493fa3ab866">
+        <source>Images</source>
+        <target>Afbeeldingen</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f9b4f2d8146c789cd40314f640ec4e88efbaf681">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users banned.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> gebruikers verbannen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3ab99e62550869aebc85661fca2faf46785263dd">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> banned.</source>
+        <target>Gebruiker <x id="INTERPOLATION" equiv-text="{{username}}"/> verbannen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="faafee0c03ad25c8a43aa91bd5d98185b67ff734">
+        <source>Do you really want to unban <x id="INTERPOLATION" equiv-text="{{username}}"/>?</source>
+        <target>Weet je zeker dat je <x id="INTERPOLATION" equiv-text="{{username}}"/> wilt onverbannen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="925ba9946b7b256a586f0fcbe3e04fa7a0dee7bd">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> unbanned.</source>
+        <target>Gebruiker <x id="INTERPOLATION" equiv-text="{{username}}"/> onverbannen.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ad07d34d4aadfe03c964cec02ca1d3a921e6b603">
+        <source>If you remove this user, you will not be able to create another with the same username!</source>
+        <target>Als je deze gebruiker verwijdert, is het niet meer mogelijk om een andere te maken met dezelfde gebruikersnaam!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="28220fae6799ab98ef6b41af449aa9680082357a">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> deleted.</source>
+        <target>Gebruiker <x id="INTERPOLATION" equiv-text="{{username}}"/> verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>Gebruiker <x id="INTERPOLATION" equiv-text="{{username}}"/> e-mail gezet als geverifieerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> gedempt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="086eda792aeb1b0d131d633b50fdd1792f5f24c6">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</source>
+        <target>Instantie <x id="INTERPOLATION" equiv-text="{{host}}"/> gedempt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bb72d6d1219e89d182e9fd09d853d83baf8d6499">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> gedempt door de instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8686834bc4afe42c1991c6c18f0bce174a0e17a6">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</source>
+        <target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> niet meer gedempt door de instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="35d3509161861a610b0895bf084c781e56ba2830">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</source>
+        <target>Instantie <x id="INTERPOLATION" equiv-text="{{host}}"/> gedempt door de instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="978aeec5613fa97e8a5336d3599cebb23ee5a90f">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</source>
+        <target>Instantie <x id="INTERPOLATION" equiv-text="{{host}}"/> niet meer gedempt door de instantie.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4a09bf8724e7659fbb5ec33647529cdef7614bdc">
+        <source>Mute this account</source>
+        <target>Demp dit account</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d666ca3261aef72b2ddcd649d7b32af488f59952">
+        <source>Unmute this account</source>
+        <target>Dempt dit account niet meer</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="e17218983b1de76e5a920b04e1c2ecbdb6e3e06d">
+        <source>Mute the instance</source>
+        <target>Demp de instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a23514d8aca2f8633622dda0e86b399dc576a2b9">
+        <source>Unmute the instance</source>
+        <target>Demp de instantie niet meer</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4e4107055b44eee44b6954c41120de1cb4d46432">
+        <source>Mute this account by your instance</source>
+        <target>Demp dit account door jouw instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a51c59cb5ecb7004a6a8ddd2855b5c52266ad957">
+        <source>Unmute this account by your instance</source>
+        <target>Demp dit account niet meer door jouw instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="588073e831cec240d6bb0db0b133e45dab69f178">
+        <source>Mute the instance by your instance</source>
+        <target>Demp de instantie door jouw instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="676221cdabd4805901343976988c028dbf71b20a">
+        <source>Unmute the instance by your instance</source>
+        <target>Demp de instantie niet meer door jouw instantie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0c0f5bbcd2386018ec057877f9d3c5c2c9880cac">
+        <source>Request is too large for the server. Please contact you administrator if you want to increase the limit size.</source>
+        <target>Verzoek te groot voor de server. Alstublieft bereikt de administrator als je de limietgrote wilt vergroten.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="58546fd4d14b2d9635ce3d28c216ac68587bb25b">
+        <source>Too many attempts, please try again after <x id="INTERPOLATION" equiv-text="{{minutesLeft}}"/> minutes.</source>
+        <target>Te vaak geprobeerd, probeer alstublieft weer na <x id="INTERPOLATION" equiv-text="{{minutesLeft}}"/> minuten.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ab783a52f2df9ff7a20139cab0da6d0764f3cc5d">
+        <source>Too many attempts, please try again later.</source>
+        <target>Te vaak geprobeerd, probeer alstublieft later.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0f286a597f0053c3578a52e044769c204ee516fc">
+        <source>Server error. Please retry later.</source>
+        <target>Serverfout. Probeer later alstublieft weer.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
+        <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target>Abonneer op <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Geabonneert</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3e7735fa326fcdc9e1188b6d9ff4b4329312fc26">
+        <source>Unsubscribed from <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
+        <target>Ongeabonneert van <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Ongeabonneert</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
+        <source>Moderator</source>
+        <target>Beheerder</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d4195053fd38eacf6dee1fc507296928978cc8fb">
+        <source>Only I can see this video</source>
+        <target>Ik kan deze video alleen zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="17b62592e5fcabb5235bb25c4883a827ab37cf70">
+        <source>Only people with the private link can see this video</source>
+        <target>Alleen mensen met de privélink kunnen deze video zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="15be15cbdc6e960f57e801f457c19165ab39632b">
+        <source>Anyone can see this video</source>
+        <target>Iedereen kan deze video zien</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>Welkom! Check alstublieft nu jouw e-mails om jouw accont te verifieren en de inschrijving te voltooien</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>Je bent nu ingelogd als <x id="INTERPOLATION" equiv-text="{{username}}"/>!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
+        <source>Video to import updated.</source>
+        <target>Video naar import bijgewerkt.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0e907e5a96537e464b192f8adce79ce6487cbb1c">
+        <source>Your video was uploaded to your account and is private.</source>
+        <target>Jouw video is geupload naar jouw account en is privé.
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="24840228f2826b66252cfcaab9820b1c7e0da264">
+        <source>But associated data (tags, description...) will be lost, are you sure you want to leave this page?</source>
+        <target>Maar geassocieerde data(tags, beschrijving...) zullen verloren raken, weet je zeker dat je deze pagina wilt verlaten?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5af84926d631326e548573ebf0f6dff07845aeb4">
+        <source>Your video is not uploaded yet, are you sure you want to leave this page?</source>
+        <target>Jouw video is nog niet geupload, weet je zeker dat je deze pagina wilt verlaten?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
+        <source>Upload cancelled</source>
+        <target>Upload geannuleerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
+        <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
+        <target>Jouw videoquotum is overschreden met deze video (videogrootte: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, gebruikt: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quotum: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="c980896ac8e08e9751545db1b7ef0e93fb8a52cd">
+        <source>Your daily video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{quotaUsedDaily}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{quotaDaily}}"/>)</source>
+        <target>Jouw dagelijkse videoquotum is overschreden met deze video (videogrootte: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, gebruikt: <x id="INTERPOLATION_1" equiv-text="{{quotaUsedDaily}}"/>, quotum: <x id="INTERPOLATION_2" equiv-text="{{quotaDaily}}"/>)</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="972fc644f847cf84e4732ec012915c4cdaf865ce">
+        <source>Video published.</source>
+        <target>Video gepubliceerd.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="757e9c083c8f3d578bd74f055cc337c72417e187">
+        <source>Video updated.</source>
+        <target>Video geupdate.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="aeb61b334cac080733c3e03766165a346bbf42fd">
+        <source> <x id="INTERPOLATION" equiv-text="{{totalReplies}}"/> replies will be deleted too.</source>
+        <target> <x id="INTERPOLATION" equiv-text="{{totalReplies}}"/> reacties zullen ook worden verwijdert.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73c33d602da89a33d353d686f36c2fff39f0aee3">
+        <source>Video blacklisted.</source>
+        <target>Video op de zwarte lijst.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ef90545bc832876c0d7f9a10363c75137472bbb5">
+        <source>Copied</source>
+        <target>Gekopieerd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="fa2601e52cbf5725a13d33fe14458823b882ea50">
+        <source>Video reported.</source>
+        <target>Video gerapporteerd.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="aca77c42f255d4bc6e95c12c5d656070726c6c2f">
+        <source>Start at <x id="INTERPOLATION" equiv-text="{{timestamp}}"/></source>
+        <target>Start op <x id="INTERPOLATION" equiv-text="{{timestamp}}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="0e65067fdcc9d8725a41896cb1e229d1415a45f6">
+        <source>Like the video</source>
+        <target>Like de video</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1a999e06e1aca0a70cd7d0e3e5c2c63d0e1885c8">
+        <source>Dislike the video</source>
+        <target>Dislike de video</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f1abd89c9280323209e939fa9c30f6e5cda20c95">
+        <source>Do you really want to delete this video?</source>
+        <target>Weet je zeker dat je de video wil verwijderen?</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="d5a4811e15319ad9354e1b62e9ca0131192b489e">
+        <source><x id="INTERPOLATION" equiv-text="{{likesNumber}}"/> likes / <x id="INTERPOLATION_1" equiv-text="{{dislikesNumber}}"/> dislikes</source>
+        <target><x id="INTERPOLATION" equiv-text="{{likesNumber}}"/> likes / <x id="INTERPOLATION_1" equiv-text="{{dislikesNumber}}"/> dislikes</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="ed013c2c29216501c688e9cb5f3a1c9fd9147b71">
+        <source>This video contains mature or explicit content. Are you sure you want to watch it?</source>
+        <target>Deze video bevat volwassen of expliciete inhoud. Weet je zeker dat je hem wilt kijken?</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Je weergavenaam moet minstens 3 tekens lang zijn.</target>
+      <trans-unit id="5ba3d522e4146eefcbd5c222247c1e2423d27cd8">
+        <source>Mature or explicit content</source>
+        <target>Volwassen of expliciete content</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Je weergavenaam kan niet langer dan 120 tekens zijn.</target>
+      <trans-unit id="1b157e15c434469d91e56d027b78bf69c9983165">
+        <source>Videos from your subscriptions</source>
+        <target>Videos van jou abonnementen</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
index c353ead7ac70b06f9e1cc570825b510d5efed432..106de7d5e72bcb8421c38319b09c7e9e866844e9 100644 (file)
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f3e63578c50546530daf6050d2ba6f8226040f2c">
+        <source>You don't have notifications.</source>
+        <target>Avètz pas cap de notificacion.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f79d1d9ecaab3deb3d44e23017f8283a04d2a0f3">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/> published a <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>new video<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.video.channel.displayName }}"/> a publicat una <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/>nòva vidèo<x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="23b7d6f08c5c3b8722ecd627c3d54f4950923156">
+        <source>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> commented your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </source>
+        <target>
+        <x id="INTERPOLATION" equiv-text="{{ notification.comment.account.displayName }}"/> a comentat vòstra vidèo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION_1" equiv-text="{{ notification.comment.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/>
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">23</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2d0ee93317d4daa301eee7fec775c21c2f7b5a4b">
+        <source>
+        Your video <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> has been published
+      </source>
+        <target>
+        Vòstra vidèo <x id="START_LINK" ctype="x-a" equiv-text="&lt;a&gt;"/><x id="INTERPOLATION" equiv-text="{{ notification.video.name }}"/><x id="CLOSE_LINK" ctype="x-a" equiv-text="&lt;/a&gt;"/> es publicada
+      </target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="473117e02024f603dc2dbd24a0bf81f8722cf8dc">
+        <source>
+      <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+    </source>
+        <target>
+      <x id="START_TAG_DIV" ctype="x-div" equiv-text="&lt;div&gt;"/><x id="CLOSE_TAG_DIV" ctype="x-div" equiv-text="&lt;/div&gt;"/>
+    </target>
+        <context-group name="null">
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="4b3963c6d0863118fe9e9e33447d12be3c2db081">
         <source>Unlisted</source>
         <target>Pas listada</target>
           <context context-type="linenumber">25</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="c078d4901a5fac169665947cc7a6108b94dd80c7">
+        <source><x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/></source>
+        <target><x id="INTERPOLATION" equiv-text="{{ menuEntry.label }}"/></target>
+        <context-group name="null">
+          <context context-type="linenumber">11</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="12910217fdcdbca64bee06f511639b653d5428ea">
         <source>
     Login
         <source>Password</source>
         <target>Senhal</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Connexion</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Me mandar un corrièl per reïnicializar lo senhal</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Inscripcion</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>Cambiar la lenga</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1c98d728375e7bd5b166d1aeb29485ef8b5d6e28">
+        <source>
+    Help to translate PeerTube!
+  </source>
+        <target>
+    Ajudatz a traduire PeerTube !
+  </target>
+        <context-group name="null">
+          <context context-type="linenumber">8</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Mon perfil public
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Mon compte
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Mas vidèos
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Desconnexion
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Crear un compte</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Abonaments</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Apercebut</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Tendéncias</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Apondons recents</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Localas</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Mai</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administracion</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>Mostrar los acorchis clavièr</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>Passar a l’interfàcia escura</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="2dc8a0a3763cd5c456c84630fc335398c9b86771">
+        <source>View your notifications</source>
+        <target>Veire vòstras notificacions</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8bcabdf6b16cad0313a86c7e940c5e3ad7f9f8ab">
+        <source>Notifications</source>
+        <target>Notificacions</target>
+        <context-group name="null">
+          <context context-type="linenumber">10</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="341e026e3f317aa3164916cc63a059c961a78b81">
+        <source>Update your notification preferences</source>
+        <target>Actualizar vòstras preferéncias de notificacion</target>
+        <context-group name="null">
+          <context context-type="linenumber">15</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3d1b5c9cd76948c04fdb7bb3fe51b6c1242c1bd5">
+        <source>See all your notifications</source>
+        <target>Veire totas vòstras notificacions</target>
+        <context-group name="null">
+          <context context-type="linenumber">22</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>Mostrar las vidèos pas listadas e las privadas</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Cap de resultat</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
-        <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
-  A prepaus de l’instància <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+      <trans-unit id="5fea66be16da46ed7a0775e9a62b7b5e94b77473">
+        <source>Contact <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> administrator</source>
+        <target>Contactar l’administrator de <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
-        <source>Description</source>
-        <target>Descripcion</target>
+      <trans-unit id="533b2b9a76ee1335cb44c01f0bfd50d43e9400b0">
+        <source>Your name</source>
+        <target>Vòstre nom</target>
         <context-group name="null">
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="69580f2c2dbf4edf7096820ba8c393367352d774">
-        <source>Terms</source>
-        <target>Tèrmes</target>
+      <trans-unit id="0b892c7805a1c5afc0b7c21c3449760860fe7f3d">
+        <source>Your email</source>
+        <target>Vòstra adreça</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
-        <source>User registration is allowed and</source>
-        <target>Las inscripcions son autorizadas e</target>
+      <trans-unit id="d2815c9b510b8172d8cac4008b9709df69d636df">
+        <source>Your message</source>
+        <target>Vòstre messatge</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
+          Cancel
+        </source>
         <target>
-      aquesta instància provesís un quòta de basa de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> d’espaci per las vidèos de sos utilizaires.
-    </target>
+          Anullar
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">26</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      aquesta instància fornís un espaci sens limit per las vidèos de sos utilizaires.
-    </target>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Mandar</target>
         <context-group name="null">
           <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>Las inscriptions son pas pel moment possiblas</target>
+      <trans-unit id="89e55a86cb300f06139ff398c9c8bb7376f78b07">
+        <source>About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance</source>
+        <target>A prepaus de l’instància <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/></target>
         <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">4</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
+        <source>Description</source>
+        <target>Descripcion</target>
+        <context-group name="null">
+          <context context-type="linenumber">27</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="69580f2c2dbf4edf7096820ba8c393367352d774">
+        <source>Terms</source>
+        <target>Tèrmes</target>
+        <context-group name="null">
+          <context context-type="linenumber">39</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
+        <source>User registration is allowed and</source>
+        <target>Las inscripcions son autorizadas e</target>
+        <context-group name="null">
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Descripcion corta</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Rota del client per defaut</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Apercebuts de las vidèos</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Vidèos  a la mòda </target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Vidèos ajustadas recentament</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Vidèos localas</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Politica tocant las vidèos amb de contengut sensible</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Inscripcions activadas</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>L’inscripcion demanda una verificacion d’adreça electronica</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limit d’inscripcions</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Utilizaires</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Quòta per defaut per utilizaire</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Quòta jornalièr de mandadís per defaut dels utilizaires </target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importar</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>Import vidèo amb URL HTTP (per exemple YouTube) activat</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Import de vidèos via un fichièr torretn o un magnet URI activat</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administrator</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Adreça de l’admin</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Utilizaires</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Quòta per defaut per utilizaire</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Quòta jornalièr de mandadís per defaut dels utilizaires </target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Vòstre nom d’utilizaire Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indica lo compte Twitter del site o de la plataforma ont lo contengut foguèt publicat.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instàncias en lista blanca per Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Transcodatge</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transcodatge activat</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Se desactivatz lo transcodatge, un fum de vidèos de vòstres utilizaires foncionaràn pas !</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Transcodatge dels threads</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Resolucion <x id="INTERPOLATION" equiv-text="{{resolution}}"/> activada</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Qualques fichièrs son pas federats (apercebuts, legendas). Los recuperam de l’instància d’origina estant e los metèm en cache.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Talha del cache d’apercebut</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Talha del cache per las legendas de las vidèos</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalizacions</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Escrivètz dirèctament de JavaScript còdi.&lt;br /&gt;Exemple : &lt;pre&gt;console.log('mon instància es tròp crana');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                Escrivètz dirèctament lo còdi CSS. Exemple :&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prefixatz amb &lt;em&gt;#custom-css&lt;/em&gt; per subrecargar los estiles. Exemple :
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>Configuracion avançada</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Actualizar la configuracion</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Sembla que la configuracion es invalida. Mercés de cercar d’errors possiblas pels diferents onglets.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>Lo corrièl de l’utilizaire deu èsser verificat abans la connexion</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>Lo corrièl de l’utilizaire es verificat / Pòt se connectar sens verificacion de l’adreça</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
         <source>Ban reason:</source>
         <target>Rason del bandiment :</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>Accions</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Data <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Rason de la mesa en lista negra :</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
         <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Mut lo <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">13</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1f689fada9748a830117f5b429a88ef8629082a8">
-        <source>Unmute</source>
-        <target>Restablir</target>
-        <context-group name="null">
-          <context context-type="linenumber">23</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Mos paramètres</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Ma bibliotèca</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Mas cadenas</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Mas vidèos</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Mos abonaments</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Mas importacions</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>Divèrs</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>Instàncias mudas</target>
-        <context-group name="null">
-          <context context-type="linenumber">2</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Cambiaments de proprietats</target>
+      <trans-unit id="1f689fada9748a830117f5b429a88ef8629082a8">
+        <source>Unmute</source>
+        <target>Restablir</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Profile</source>
         <target>Perfil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Paramètres vidèo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Zòna perilhosa</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Mandar</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> vistas</target>
@@ -2475,6 +2492,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Instàncias mudas</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Cambiar lo senhal</target>
@@ -2573,6 +2597,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">4</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="847dffd493abbb2a5c71f3313f0eb730dd88a355">
+        <source>Web</source>
+        <target>Site web</target>
+        <context-group name="null">
+          <context context-type="linenumber">3</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e242e3e8608a3c4a944327eb3d5c221dc6e4e3cd">
         <source>
   Sorry, but we couldn't find the page you were looking for.
@@ -2680,6 +2711,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>O planhèm, quicòm a trucat</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2716,14 +2754,14 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
         <source>Publish will be available when upload is finished</source>
         <target>La publicacion serà possibla un còp lo mandadís acabat</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publicar</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2906,14 +2944,14 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
         <source>Wait transcoding before publishing the video</source>
         <target>Esperar lo transcodatge abans de publicar la vidèo</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Se decidissètz d’esperar pas lo transcodatge abans de publicar la vidèo, serà pas legibla fins a la fin del transcodatge.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2927,49 +2965,49 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
         <source>Add another caption</source>
         <target>Ajustar una legenda mai</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Veire lo fichièr de sostítols</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>Ja enviada     ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
         <source>Will be created on update</source>
         <target>Serà creada en actualizar</target>
         <context-group name="null">
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Anullar la creacion</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
         <source>Will be deleted on update</source>
         <target>Serà suprimida en actualizar</target>
         <context-group name="null">
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Anullar la supression</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
@@ -2980,28 +3018,28 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
             Encara cap de legendas.
           </target>
         <context-group name="null">
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Legendas</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Enviar una vinheta d’apercebut</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Enviar un apercebut</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -3015,14 +3053,14 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Pichon tèxte per explicar al monde cossí pòdon vos sosténer (plataforma pels sòcis...).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Paramètres avançats</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3089,17 +3127,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Anullar
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Partejar</target>
@@ -3486,13 +3513,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Error en recuperar las informacions de la seccion «A prepaus» del servidor</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Cap de descripcion</target>
@@ -3514,13 +3534,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Error</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3563,13 +3576,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Succès</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Configuracion actualizada.</target>
@@ -3836,6 +3842,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>Passar l’adreça coma verificada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
         <source>You cannot ban root.</source>
         <target>Podètz pas fòrabandir lo root.</target>
@@ -3878,6 +3891,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> adreças d’utilizaires passadas coma verificadas.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
         <target>Compte <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> pas mai mut.</target>
@@ -3941,6 +3961,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="a0f04081717f5f00c0a2c723903c3a2d4c296401">
+        <source>Preferences saved</source>
+        <target>Preferéncias salvagardadas</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="db4ff52375f6a25ad0472e92754c8c265ae47c6b">
         <source>Profile updated.</source>
         <target>Perfil actualizat</target>
@@ -3990,23 +4017,16 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Volètz vertadièrament suprimir <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> ? Aquò suprimarà tanben totas las vidèos enviadas a la cadena.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Tornatz picar lo nom de la cadena per confirmar</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Cadena vidèo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> suprimida.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Cadena vidèo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> suprimida.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Mas vidèos</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4081,16 +4101,58 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Cadenas</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Ma bibliotèca</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Mas cadenas</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Mos abonaments</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4f953496ca94b4f83af049ff715172df2729fb79">
+        <source>My history</source>
+        <target>Mon istoric</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Divèrs</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Cambiaments de proprietats</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Mos paramètres</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Imports vidèo</target>
+      <trans-unit id="0e2434e7d84145c4e8a930ccc4c26c3cb2887e0d">
+        <source>My notifications</source>
+        <target>Mas notificacions</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4216,6 +4278,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Error</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Vos cal vos reconnectar.</target>
@@ -4237,6 +4306,20 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Info</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Succès</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Nom d’utilizaire o senhal incorrècte.</target>
@@ -4454,58 +4537,58 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
-        <source>Username is required.</source>
-        <target>Lo nom d’utilizaire es requesit.</target>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>L’adreça electronica es requesida.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4eb39d69b74d7a56652ec84fa6826994ee26c0e5">
-        <source>Password is required.</source>
-        <target>Lo senhal es requesit.</target>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>L’adreça electronica deu èsser valida.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c90872a06666a51c2957c4b29724e68df5c67154">
-        <source>Confirmation of the password is required.</source>
-        <target>La confirmacion del senhal es requesida.</target>
+      <trans-unit id="ac451f128840b34804ea69c820dc3566f476fb33">
+        <source>Your name is required.</source>
+        <target>Vòstre nom es requesit</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Lo nom d’utilizaire deu conténer almens 3 caractèrs.</target>
+      <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
+        <source>Username is required.</source>
+        <target>Lo nom d’utilizaire es requesit.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Lo nom d’utilizaire pòt pas conténer mai de 20 caractèrs.</target>
+      <trans-unit id="4eb39d69b74d7a56652ec84fa6826994ee26c0e5">
+        <source>Password is required.</source>
+        <target>Lo senhal es requesit.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Lo nom d’utilizaire deu èsser alfanumeric e en minuscula. </target>
+      <trans-unit id="c90872a06666a51c2957c4b29724e68df5c67154">
+        <source>Confirmation of the password is required.</source>
+        <target>La confirmacion del senhal es requesida.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>L’adreça electronica es requesida.</target>
+      <trans-unit id="6330d25a3bc6f55dfd5177da6e681d1d3b1a2b1a">
+        <source>Username must be at least 1 character long.</source>
+        <target>Lo nom d’utilizaire deu almens conténer 1 caractèr.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>L’adreça electronica deu èsser valida.</target>
+      <trans-unit id="aaaf3d00c35f809eebc7fd68a3f7b8b0230b197a">
+        <source>Username cannot be more than 50 characters long.</source>
+        <target>Lo nom d’utilizaire pòt pas conténer mai de 50 caractèrs.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4573,16 +4656,16 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>L’escais-nom deu almens conténer 3 caractèrs.</target>
+      <trans-unit id="085b2d6f79819a72a2b56cada4ef5085ba51d90c">
+        <source>Display name must be at least 1 character long.</source>
+        <target>Lo nom public deu almens conténer 1 caractèr.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>L’escais-nom pòt pas conténer mai de 120 caractèrs.</target>
+      <trans-unit id="5a920575b8e1067f5b11c66a4a36d3ced87756f1">
+        <source>Display name cannot be more than 50 characters long.</source>
+        <target>Lo nom public pòt pas conténer mai de 50 caractèrs.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4636,13 +4719,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>La rason del senhalament pòt pas conténer mai de 300 caractèrs.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Lo comentari de moderacion es requesit.</target>
@@ -4657,13 +4733,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Lo comentari de moderacion pòt pas conténer mai de 300 caractèrs.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>La cadena es requesida.</target>
@@ -4720,23 +4789,16 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Lo nom deu almens conténer 3 caractèrs.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Lo nom pòt pas conténer mai de 20 caractèrs.</target>
+      <trans-unit id="b8b59b6284a14fc71268cf722ed98c62c5af4a76">
+        <source>Name must be at least 1 character long.</source>
+        <target>Lo nom deu almens conténer 1 caractèr.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Lo nom deu èsser alfanumeric e en minuscula</target>
+      <trans-unit id="e14cd37d29f13eac7384c339e4f1df58d96e4e3d">
+        <source>Name cannot be more than 50 characters long.</source>
+        <target>Lo nom pòt pas conténer mai de 50 caractèrs.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5420,6 +5482,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>L’adreça a <x id="INTERPOLATION" equiv-text="{{username}}"/> es passada coma verificada</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
         <target>Lo compte <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> es mut.</target>
@@ -5546,13 +5615,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Abonat</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>S’abonèt a <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -5560,9 +5622,9 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Pas mai abonat</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Abonat</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5574,6 +5636,13 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Pas mai abonat</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>Moderator</target>
@@ -5602,6 +5671,20 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>La benvenguda ! Ara volgatz ben verificar vòstres corrièls per confirmar vòstre compte e acabar l’inscripcion.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>Sètz ara connectat coma <x id="INTERPOLATION" equiv-text="{{username}}"/> !</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>Vidèo d’importar actualizada</target>
@@ -5630,13 +5713,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Info</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Mandadís anullat</target>
@@ -5644,13 +5720,6 @@ Quand enviaretz una vidèo dins aquesta cadena, lo camp vidèo sosten serà auto
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>O planhèm mas PeerTube pòt pas gerir de vidèos de mai de 8 Go</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>Vòstre quòta de vidèo es excedit amb aquesta vidèo  (talha vidèo : <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, utilizat : <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quòta : <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index fd6e0346993639db1fee2093fa6701d041f0ed8c..6d5a06e531dd07bbdbe593663d83f31afb6d3e81 100644 (file)
         <source>Password</source>
         <target>Hasło</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Zaloguj się</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Wyślij mi wiadomość e-mail przywracającą hasło</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Zarejestruj się</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9167c6d3c4c3b74373cf1e90997e4966844ded1a">
         <source>Change the language</source>
         <target>Zmień język</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Mój profil publiczny
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Moje konto
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Moje filmy
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Wyloguj się
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Utwórz konto</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Subskrybcje</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Przegląd</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Na czasie</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Ostatnio dodane</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Lokalne</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Więcej</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administracja</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Toggle dark interface</source>
         <target>Przełącz ciemny interfejs</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>No results.</source>
         <target>Brak wyników.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  O instancji <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Anuluj
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Wyślij</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Zasady</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Rejestracja użytkowników jest dozwolona i</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      ta instancja oferuje podstawową powierzchnię <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> na filmy swoich użytkowników.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      ta instancja oferuje nieograniczoną powierzchnię na filmy dla swoich użytkowników.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Rejestracja użytkowników nie jest obecnie dozwolona.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Krótki opis</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source><target>Default client route</target><context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Przegląd Filmów</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Filmy na czasie</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Ostatnio dodane filmy</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Lokalne filmy</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Polityka dotycząca filmów zawierających wrażliwą zawartość</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Wymagana rejestracja</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Rejestracja wymaga weryfikacji emaila</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limit rejestracji</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Użytkownicy</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Domyślna powierzchnia na filmy dla użytkownika</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Domyślny limit dziennego wysyłania przez użytkownika</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importuj</target>
         <source>Administrator</source>
         <target>Administrator</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>E-mail administratora</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Użytkownicy</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Domyślna powierzchnia na filmy dla użytkownika</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Domyślny limit dziennego wysyłania przez użytkownika</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Twoja nazwa użytkownika na Twitterze</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Oznacza konto Twittera dla strony lub platformy na której została opublikowana zawartość.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instancja jest na białej liście Twittera</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Transkodowanie</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transkodowanie jest włączone</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Jeżeli wyłączysz transkodowanie, wiele filmów od użytkowników może nie działać!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Wątki transkodowania</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Włączono rozdzielczość <x id="INTERPOLATION" equiv-text="{{resolution}}"/></target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Rozmiar pamięci podręcznej podglądu</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Dostosowywanie</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Wprowadź kod JavaScript.&lt;br /&gt;Przykład: &lt;pre&gt;console.log('moja instancja jest świetna');&lt;/pre&gt;</target>
         <context-group name="null">
-          <context context-type="linenumber">281</context>
+          <context context-type="linenumber">297</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6c44844ebdb7352c433b7734feaa65f01bb594ab">
         <source>Advanced configuration</source>
         <target>Zaawansowana konfiguracja</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Aktualizuj konfigurację</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Wygląda na to, że konfiguracja jest nieprawidłowa. Poszukaj możliwych błędów w innych kartach.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
       Transcoding is enabled on server. The video quota only take in account <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>original<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> video. <x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/>
       At most, this user could use ~ <x id="INTERPOLATION" equiv-text="{{ computeQuotaWithTranscoding() | bytes: 0 }}"/>.
     </source>
+        <target>
+      Transcoding is enabled on server. The video quota only take in account <x id="START_TAG_STRONG" ctype="x-strong" equiv-text="&lt;strong&gt;"/>original<x id="CLOSE_TAG_STRONG" ctype="x-strong" equiv-text="&lt;/strong&gt;"/> video. <x id="LINE_BREAK" ctype="lb" equiv-text="&lt;br/&gt;"/>
+      At most, this user could use ~ <x id="INTERPOLATION" equiv-text="{{ computeQuotaWithTranscoding() | bytes: 0 }}"/>.
+    </target>
         <context-group name="null">
           <context context-type="linenumber">65</context>
         </context-group>
         <source>Actions</source>
         <target>Akcje</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Data <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Moje ustawienia</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Moja biblioteka</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Moje kanały</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Moje filmy</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Moje subskrybcje</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Moje importy</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Powierzchnia na filmy:</target>
         <source>Profile</source>
         <target>Profil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Ustawienia wideo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a5433ae2324496bea9537caa5e8a2719d8e958d8">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Wyślij</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> wyświetlenia</target>
@@ -2008,14 +1937,14 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
         <source>Publish will be available when upload is finished</source>
         <target>Opublikuj automatycznie po ukończeniu wysyłania</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Opublikuj</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1b518e7f8c067fa55ea797bb1b35b4a2d31dccbc">
@@ -2127,7 +2056,7 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
         <source>Wait transcoding before publishing the video</source>
         <target>Poczekaj na transkodowanie przed opublikowaniem filmu</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2141,14 +2070,14 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
         <source>Upload thumbnail</source>
         <target>Wyślij miniaturę</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Podgląd wysyłania</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2162,14 +2091,14 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Krótki tekst informujący innych, jak mogą Cię wesprzeć (platforma członkowska…).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Ustawienia zaawansowane</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -2236,17 +2165,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Anuluj
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Udostępnij</target>
@@ -2522,13 +2440,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Błąd podczas uzyskiwania informacji z serwera</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Brak opisu</target>
@@ -2550,13 +2461,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Błąd</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -2592,13 +2496,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Pomyślnie</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Zaktualizowano konfigurację.</target>
@@ -2844,23 +2741,16 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Czy na pewno chcesz usunąć <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Skutkuje to usunięciem wszystkich filmów wysłanych na ten kanał.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Wprowadź nazwę kanału wideo, aby potwierdzić</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Usunięto kanał wideo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Usunięto kanał wideo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Moje filmy</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2921,9 +2811,30 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Kanały</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Moja biblioteka</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Moje kanały</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Moje subskrybcje</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Moje ustawienia</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -2951,6 +2862,13 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Błąd</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Musisz połączyć się ponownie.</target>
@@ -2965,6 +2883,20 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Informacja</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Pomyślnie</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="b0f24b7136e551a0deba831f1525711245b31a26">
         <source>Your password has been successfully reset!</source>
         <target>Pomyślnie zresetowano hasło!</target>
@@ -3126,6 +3058,20 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>Adres e-mail jest wymagany.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>Adres e-mail musi być prawidłowy.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Nazwa użytkownika jest wymagana.</target>
@@ -3147,41 +3093,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Nazwa użytkownika musi zawierać przynajmniej 3 znaki.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Nazwa użytkownika nie może być dłuższa niż 20 znaków.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Nazwa użytkownika może zawierać tylko małe znaki alfanumeryczne.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>Adres e-mail jest wymagany.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>Adres e-mail musi być prawidłowy.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Hasło musi składać się z przynajmniej 6 znaków.</target>
@@ -3231,20 +3142,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Nazwa wyświetlana musi zawierać przynajmniej 3 znaki.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Nazwa wyświetlana nie może zawierać więcej niż 120 znaków.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>Opis musi zawierać przynajmniej 3 znaki.</target>
@@ -3266,13 +3163,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Przyczyna zgłoszenia nie może zawierać więcej niż 300 znaków.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="bd7fc070c728dc6dbf3959d49fe5bb27ce15d294">
         <source>The username is required.</source>
         <target>Nazwa użytkownika jest wymagana.</target>
@@ -3287,27 +3177,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Nazwa musi zawierać przynajmniej 3 znaki.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Nazwa nie może być dłuższa niż 20 znaków.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Nazwa może zawierać tylko małe znaki alfanumeryczne.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>Tekst o wsparciu musi zawierać przynajmniej 3 znaki.</target>
@@ -3999,13 +3868,6 @@ Jeżeli umieścisz film na ten kanał, pole informujące o możliwości wsparcia
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Informacja</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Anulowano wysyłanie</target>
index 71aeac8d59096a59a529aa3dbc50356c3894671b..0227115e3cfd1cff71c10963ea01fd84ebeb7dc9 100644 (file)
         <source>Password</source>
         <target>Senha</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Entrar</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Me envie um e-mail para redefinir minha senha</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Inscrever-se</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>Alterar o idioma</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Meu perfil público
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Minha conta
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Meus vídeos
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Sair
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Criar uma conta</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Inscrições</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Visão geral</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Tendências</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Adicionado recentemente</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Local</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Mais</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administração</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Toggle dark interface</source>
         <target>Alternar interface escura</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>No results.</source>
         <target>Nenhum resultado.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  Sobre a instância <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Cancelar
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Enviar</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Termos</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Registro de usuários não está permitida e</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      esta instância fornece uma cota base de <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> espaço para os vídeos de seus usuários.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      esta instância fornece espaço ilimitado para os vídeos de seus usuários.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Registro de usuários atualmente não está permitido.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Descrição curta</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Rota padrão do cliente</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Visão geral dos vídeos</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Vídeos em Tendência</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Vídeos Adicionados Recentemente</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Vídeos locais</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Política sobre vídeos que possuem conteúdo sensível</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Inscrição permitida</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Inscrição requer verificação de email</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Limite de inscrições</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Usuários</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Cota padrão de vídeos do usuário</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Padrão de limite diário de upload</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importar</target>
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Importação de vídeo com um arquivo torrent ou URI magnética habilitada</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administrador</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Email de administrador</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Usuários</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Cota padrão de vídeos do usuário</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Padrão de limite diário de upload</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Seu nome de usuário no Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Indica a conta Twitter do sítio web ou plataforma em que o conteúdo foi publicado.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instância listada como permitida pelo Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Transcodificação</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Transcodificação ativada</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Se você desativar a transcodificação, muitos vídeos dos seus usuários não funcionarão!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Threads de transcodificação</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Resolução <x id="INTERPOLATION" equiv-text="{{resolution}}"/> habilitada</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Alguns arquivos não são federados (pré-visualizações, legendas ocultas). Nós as obtivemos diretamente da instância de origem e a colocamos em cache.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Tamanho do cache de pré-visualizações</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Tamanho do cache de legendas ocultas de vídeos</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Personalizações</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Escreva diretamente código JavaScript.&lt;br /&gt;Exemplo: &lt;pre&gt;console.log('minha instância é demais');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                Escreva código CSS diretamente. Exemplo:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Preceda com &lt;em&gt;#custom-css&lt;/em&gt; para sobrescrever estilos. Exemplo:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>Configurações avançadas</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Atualizar configuração</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Aparentemente a configuração está valida. Por favor procure potenciais erros nas diferentes abas.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
         <source>Ban reason:</source>
         <target>Motivo do banimento:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>Ações</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Data <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Motivo da lista negra:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Minhas configurações</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Minha biblioteca</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Meus canais</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Meus vídeos</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Minhas inscrições</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Minhas importações</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Mudanças de dono</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Cota de vídeo:</target>
         <source>Profile</source>
         <target>Perfil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Configurações de vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Zona perigosa</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Enviar</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> visualizações</target>
@@ -2162,6 +2045,13 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">30</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="0dd390d056411e1709ec97ec51c46d78600e3f7b">
+        <source>Current password</source>
+        <target>Senha atual</target>
+        <context-group name="null">
+          <context context-type="linenumber">7</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e70e209561583f360b1e9cefd2cbb1fe434b6229">
         <source>New password</source>
         <target>Nova senha</target>
@@ -2368,14 +2258,14 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
         <source>Publish will be available when upload is finished</source>
         <target>A publicação estará disponível quando o envio terminar</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publicar</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2537,14 +2427,14 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
         <source>Wait transcoding before publishing the video</source>
         <target>Aguarde a transcodificação antes de publicar o vídeo</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Se você decidir não aguardar a transcodificação antes de publicar o vídeo, ele poderá não ser reproduzido até que a transcodificação termine.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2558,49 +2448,49 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
         <source>Add another caption</source>
         <target>Adicionar outra legenda oculta</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Veja o arquivo de legenda</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Cancelar criação</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Cancelar exclusão</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Legendas ocultas</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Enviar miniatura</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Enviar pré-visualização</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2614,14 +2504,14 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Texto curto para dizer às pessoas como elas podem apoiar você (plataforma de membros, etc.).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Configurações avançadas</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -2688,17 +2578,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Cancelar
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Compartilhar</target>
@@ -3034,13 +2913,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Erro ao obter detalhes do servidor</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Nenhuma descrição</target>
@@ -3062,20 +2934,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Erro</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Sucesso</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Configuração atualizada.</target>
@@ -3328,23 +3186,16 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Você realmente deseja excluir <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Isso também excluirá todos os vídeos enviados neste canal.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Por favor, digite o nome do canal de vídeo para confirmar</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Canal de vídeo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> excluído.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Canal de vídeo <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> excluído.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Meus vídeos</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3419,16 +3270,37 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Canais</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Minha biblioteca</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Importações de vídeos</target>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Meus canais</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Minhas inscrições</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Mudanças de dono</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Minhas configurações</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -3463,6 +3335,13 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Erro</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>você precisa se reconectar.</target>
@@ -3477,6 +3356,20 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Info</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Sucesso</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Nome de usuário ou senha incorretos.</target>
@@ -3694,6 +3587,20 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>E-mail é necessário.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>E-mail deve ser válido.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Nome de usuário é necessário.</target>
@@ -3715,41 +3622,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Nome de usuário deve ter pelo menos 3 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Nome de usuário não pode ter mais que 20 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Nome de usuário deve ter apenas caracteres alfanuméricos minúsculos.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>E-mail é necessário.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>E-mail deve ser válido.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Senha deve ter pelo menos 6 caracteres.</target>
@@ -3813,20 +3685,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Nome de exibição deve ter pelo menos 3 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Nome de exibição não pode ter mais que 120 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>Descrição deve ter pelo menos 3 caracteres.</target>
@@ -3869,13 +3727,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Motivo da denúncia não pode ter mais que 300 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Comentário de moderação é obrigatório.</target>
@@ -3890,13 +3741,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Comentário de moderação não pode ter mais de 300 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>O canal é obrigatório.</target>
@@ -3946,27 +3790,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Nome deve ter pelo menos 3 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Nome não pode ter mais de 20 caracteres.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Nome deve ser apenas caracteres alfanuméricos minúsculos.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>Texto de apoio deve ter pelo menos 3 caracteres.</target>
@@ -4646,13 +4469,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Inscrito</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>Inscrito em <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -4660,9 +4476,9 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Desinscrito</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Inscrito</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4674,6 +4490,13 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Desinscrito</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="d4195053fd38eacf6dee1fc507296928978cc8fb">
         <source>Only I can see this video</source>
         <target>Apenas eu posso ver este vídeo</target>
@@ -4723,13 +4546,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Info</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Envio cancelado</target>
@@ -4737,13 +4553,6 @@ Quando você enviar um vídeo neste canal, o campo de apoio a vídeo será preen
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Lamentamos, mas o PeerTube não consegue lidar com vídeos&gt; 8GB</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>Sua quota de vídeos é excedida com este vídeo (tamanho do vídeo: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, utilizado: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index db2a690d5a1f5e8294f0ef5bae4329cc0c8b6a01..b3e2afb96726a68749dcbd7256e0b2772f38fec3 100644 (file)
         <source>Password</source>
         <target>Пароль</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Авторизация</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Отправить пароль на электронную почту</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Зарегистрироваться</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>Изменить язык</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Мой побличный профиль
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Моя учетная запись
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Мои видео
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
              Выйти
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Создать учетную запись</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Подписки</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Общий вид</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Тенденции</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Недавно добавленный </target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Локальный</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Больше</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Администрация</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Toggle dark interface</source>
         <target>Изменить интерфейс</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>No results.</source>
         <target>Нет результатов</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
-        <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>
- О <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> Инстанци
-</target>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Отправить</target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Условия пользователя  </target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Создания учетной записи разрешено и</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      этот сервер предоставляет <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> места для видео для пользователей.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      этот сервер предоставляет неограниченное место для пользователей.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Создание учетной записи не разрешено в данный момент.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>Краткое описание</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Default client route</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Все видео</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Тенденции видео</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Недавно добавленное видео</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Местное видео</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Политика касательно видео содержащих нежелательный контент</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Регистрация активирована</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Для регистрации нужно подтвержение через электронную почту</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Лимит регистрации</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Пользователи</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Квота видео по умолчанию на одного пользователя</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Ежедневный лимит загрузок по умолчанию на одгого пользователя</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Импортировать</target>
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Импорт видео с помощью файла торент или magnet URI активирован</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Администратор</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Электронная почта администратора</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Пользователи</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Квота видео по умолчанию на одного пользователя</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Ежедневный лимит загрузок по умолчанию на одгого пользователя</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Ваше имя пользователя в Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Показывает учетнаю запись в Twitter сайта или платформы с которых было опубликован контент</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Сервер имеет аккредитацию Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Транскодирование</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Транскодирование активировано</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Если вы дезактивируете транскодирование, многие видео пользователей перестанут работать</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Количество threads для транскодирования</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Разрешение <x id="INTERPOLATION" equiv-text="{{resolution}}"/> активировано</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Некоторые миниатюры не федератные (миниатюры, названия). Они взяты непосредственно из их оригинального сервера и мы их не храним.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Размер кеша предпросмотра</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Размер кеша предпросмотра надписей</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Персонализация</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>Ява Скрипт</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Напишите непосредственно код Ява Скрипта .&lt;br /&gt;Пример : &lt;pre&gt;console.log('мой сервер крут');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                Напишите непосредственно код CSS. Пример:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Начните с &lt;em&gt;#custom-css&lt;/em&gt; чтоб получить приоритет. Пример:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>Продвинутая конфигурация</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Обновить конфигурацию</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Конфигурация неудачная. Пожалуйста, найдите потенциальную ошибку в разных окнах. </target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
         <source>Ban reason:</source>
         <target>Причины бана:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>Действия</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Дата <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Причина блокирования:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Мои настройки</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Моя библиотека</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Мои каналы</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Мои видео</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Мои подписки</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Мои импортированные видео</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Смена собственника</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>Квота видео</target>
         <source>Profile</source>
         <target>Профиль</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Настройки видео</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Орасная зона</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Отправить</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> просмотры</target>
@@ -2279,6 +2151,48 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">17</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Мои видео</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Моя библиотека</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Мои каналы</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Мои подписки</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Смена собственника</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Мои настройки</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="f15f2e02b1f6a96553e98ea4a969045d17ec1400">
         <source>Transcoding threads is required.</source>
         <target>Транскодирование потоки требуется.</target>
@@ -2307,13 +2221,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Имя пользователя должно быть длиной не менее 3-х символов.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Пароль должен быть длиной не менее 6 символов.</target>
@@ -2321,13 +2228,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Отображаемое имя должно иметь длину не менее 3-х символов.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>Описание должно быть длиной не менее 3-х символов.</target>
@@ -2363,13 +2263,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Длина имени должна быть не менее 3 символов.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>Текст поддержки должен содержать не менее 3 символов.</target>
index e30575d80a03a4f2aff952f2a64db308d1e8436f..ffa8b19ca04ae5244f7d7ed949b8efa3402287a9 100644 (file)
         <source>Password</source>
         <target>Lösenord</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>Logga in</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>Skicka ett e-postmeddelande för att återställa mitt lösenord</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>Registrering</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>Ändra språk</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              Min offentliga profil
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               Mitt konto
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               Mina videor
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               Logga ut
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>Skapa ett konto</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>Prenumerationer</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>Översikt</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>Populärt</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>Nyligen tillagt</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>Lokalt</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>Mer</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>Administration</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>Visa kortkommandon</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>Växla mörkt gränssnitt</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>Visa olistade och privata videor</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>Inga resultat.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  Om instansen <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          Avbryt
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>Skicka</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>Villkor</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>Användarregistrering är tillåten och</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      den här instansen tillhandahåller en grundkvot på <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> utrymme för sina användares videor.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      den här instansen tillhandahåller obegränsat utrymme för sina användares videor.
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    Användarregistrering tillåts inte för tillfället.
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         </context-group>
       </trans-unit>
       <trans-unit id="62a557fcfdbd25a31d1a0332294f94a466fee809">
-        <source>Muted</source><target>Muted</target><context-group name="null">
+        <source>Muted</source>
+        <target>Ignorerad</target>
+        <context-group name="null">
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="48bbf6dbdb22e0ef4bd257eae2ab356f2ea66c89">
-        <source>Muted by your instance</source><target>Muted by your instance</target><context-group name="null">
+        <source>Muted by your instance</source>
+        <target>Ignorerad av din instans</target>
+        <context-group name="null">
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="44bd08a7ec1e407356620967d65d8fe2d8639d0a">
-        <source>Instance muted</source><target>Instance muted</target><context-group name="null">
+        <source>Instance muted</source>
+        <target>Instans ignorerad</target>
+        <context-group name="null">
           <context context-type="linenumber">15</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1a6443bb7ed01046dd83cf78806f795f1204ffa1">
-        <source>Instance muted by your instance</source><target>Instance muted by your instance</target><context-group name="null">
+        <source>Instance muted by your instance</source>
+        <target>Instans ignorerad av din instans</target>
+        <context-group name="null">
           <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
         <source>Short description</source>
         <target>Kort beskrivning</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>Klientens standardrouting</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>Videoöversikt</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>Populära videor</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>Nyligen tillagda videor</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>Lokala videor</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>Policy för videor med känsligt innehåll</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>Registrering aktiverad</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>Registrering kräver e-postverifikation</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>Registreringsgräns</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>Användare</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>Standardkvot för användares videor</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>Standarduppladdningsgräns för användare</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>Importera</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>Videoimport med HTTP-URL tillåten (t.ex. YouTube)</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>Videoimport med torrentfil eller magnet-URI är tillåten</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>Administratör</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>Administratörens e-postadress</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>Användare</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>Standardkvot för användares videor</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>Standarduppladdningsgräns för användare</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>Ditt användarnamn på Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>Webbplatsens eller plattformens Twitterkonto, på vilken innehållet publicerades.</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>Instans vitlistad av Twitter</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>Omkodning</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>Omkodning aktiverad</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>Om du avaktiverar omkodning, kommer många av dina användares videor inte fungera!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>Omkodningstrådar</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>Upplösningen <x id="INTERPOLATION" equiv-text="{{resolution}}"/> tillåten</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>Vissa filer är inte federerade (till exempel förhandsvisningar och undertexter). Vi kan hämta dem direkt från ursprungsinstansen och cachelagra dem.</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>Förhandsvisningens cachestorlek</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>Undertexternas cachestorlek</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>Anpassningar</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>Skriv direkt med JavaScript-kod.&lt;br /&gt;Exempel: &lt;pre&gt;console.log('min instans är fantastisk');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                Skriv CSS-kod direkt. Exempel:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Lägg till &lt;em&gt;#custom-css&lt;/em&gt; först för att åsidosätta stilmallen. Exempel:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>Avancerade inställningar</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>Uppdatera inställningar</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>Det verkar som att konfigurationen inte stämmer. Sök efter eventuella fel i de olika flikarna.</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
@@ -2015,11 +1962,25 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>Användarens e-post måste verifieras innan inloggning</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>Användarens e-post har verifierats / Användaren behöver inte verifiera sin e-post för att logga in</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
         <source>Ban reason:</source>
         <target>Blockeringsanledning:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
@@ -2086,7 +2047,7 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
         <source>Actions</source>
         <target>Åtgärder</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
@@ -2121,14 +2082,14 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>Datum <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>Anledning för svartlistning:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
@@ -2153,12 +2114,16 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
         </context-group>
       </trans-unit>
       <trans-unit id="b1ff109b26ae8f08650415454b9098c43eba2e2c">
-        <source>Muted accounts</source><target>Muted accounts</target><context-group name="null">
+        <source>Muted accounts</source>
+        <target>Ignorerade konton</target>
+        <context-group name="null">
           <context context-type="linenumber">2</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bd0611346af048015e0a1275091ef68ce98832d2">
-        <source>Muted servers</source><target>Muted servers</target><context-group name="null">
+        <source>Muted servers</source>
+        <target>Ignorerade servrar</target>
+        <context-group name="null">
           <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
@@ -2170,74 +2135,17 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
         </context-group>
       </trans-unit>
       <trans-unit id="079e99cce11c87b142e80fdd14dae98a61012fc4">
-        <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source><target>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target><context-group name="null">
+        <source>Muted at <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
+        <target>Ignorerad på <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
+        <context-group name="null">
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1f689fada9748a830117f5b429a88ef8629082a8">
-        <source>Unmute</source><target>Unmute</target><context-group name="null">
-          <context context-type="linenumber">23</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>Mina inställningar</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>Mitt bibliotek</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>Mina kanaler</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>Mina videor</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>Mina prenumerationer</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>Mina importeringar</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>Diverse</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source><target>Muted instances</target><context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>Ändringar av ägarskap</target>
+        <source>Unmute</source>
+        <target>Sluta ignorera</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
@@ -2251,21 +2159,21 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
         <source>Profile</source>
         <target>Profil</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>Videoinställningar</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>Riskzon</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
@@ -2293,13 +2201,6 @@ Det verkar som du inte är på en HTTPS-server. Din webbserver behöver ha TLS a
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>Skicka</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> visningar</target>
@@ -2459,6 +2360,13 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>Ignorerade instanser</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>Ändra lösenord</target>
@@ -2664,6 +2572,13 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>Någonting har tyvärr gått fel</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2700,14 +2615,14 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
         <source>Publish will be available when upload is finished</source>
         <target>Du kan publicera när uppladdningen är klar</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>Publisera</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2890,14 +2805,14 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
         <source>Wait transcoding before publishing the video</source>
         <target>Publicera video när omkodningen är avklarad</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>Om du väljer att inte vänta på omkodningen innan publicering, kommer videon inte gå att spela förrän omkodningen är färdig.</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2911,49 +2826,49 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
         <source>Add another caption</source>
         <target>Lägg till ännu en text</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>Se undertextfilen</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>Redan uppladdad        ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
         <source>Will be created on update</source>
         <target>Kommer skapas vid uppdatering</target>
         <context-group name="null">
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>Avbryt skapande</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
         <source>Will be deleted on update</source>
         <target>Kommer raderas vid uppdatering</target>
         <context-group name="null">
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>Avbryt radering</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
@@ -2964,28 +2879,28 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
             Inga undertexter för tillfället.
           </target>
         <context-group name="null">
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>Texter</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>Ladda upp miniatyrbild</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>Ladda upp förhandsvisning</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2999,14 +2914,14 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>Kort text för att berätta hur andra kan stödja dig (medlemsplattform …).</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>Avancerade inställningar</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3073,17 +2988,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          Avbryt
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>Dela</target>
@@ -3309,7 +3213,7 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
         the sharing system used for this video implies that some technical information about your system (such as a public IP address) can be sent to other peers.
       </source>
         <target>
-        den här videons delningssystem medför att en del teknisk information om ditt system (såsom publik IP-adress) kan skickas till andra serventer.
+        den här videons delningssystem gör att en del teknisk information om ditt system (som publik IP-adress) kan skickas till andra serventer.
       </target>
         <context-group name="null">
           <context context-type="linenumber">209</context>
@@ -3470,13 +3374,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>Kan inte hämta information om instansen från servern</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>Ingen beskrivning</target>
@@ -3498,13 +3395,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>Fel</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3547,13 +3437,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>Åtgärden lyckades</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>Konfigurering uppdaterad.</target>
@@ -3716,12 +3599,16 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
         </context-group>
       </trans-unit>
       <trans-unit id="53cc0f4a4566c4139c65f93b5dce2fe8302e78da">
-        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source><target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</target><context-group name="null">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by your instance.</source>
+        <target>Kontot <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ignoreras inte längre av din instans.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="468b52e3c04fb9a3d8c8213555dfcad0cbcae330">
-        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</source><target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</target><context-group name="null">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by your instance.</source>
+        <target>Instansen <x id="INTERPOLATION" equiv-text="{{host}}"/> ignoreras inte längre av din instans.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -3816,6 +3703,13 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>Markera e-post som verifierad</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
         <source>You cannot ban root.</source>
         <target>Du kan inte blockera root.</target>
@@ -3858,13 +3752,24 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> användares e-post har markerats som verifierade.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
-        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source><target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</target><context-group name="null">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
+        <target>Kontot <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ignoreras inte längre.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c6af80b42938d4a49e6f6c4f60ce26228916994c">
-        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</source><target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</target><context-group name="null">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted.</source>
+        <target>Instansen <x id="INTERPOLATION" equiv-text="{{host}}"/> ignoreras inte längre.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -3966,23 +3871,16 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>Vill du verkligen radera <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? Det kommer radera samtliga videor som laddats upp till kanalen.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>Fyll i kanalens namn för att bekräfta</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>Kanalen <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> har raderats.</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>Kanalen <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> har raderats.</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>Mina videor</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4057,16 +3955,44 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>Kanaler</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>Mitt bibliotek</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>Mina kanaler</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>Mina prenumerationer</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>Diverse</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>Ändringar av ägarskap</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>Videoimporteringar</target>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>Mina inställningar</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4192,6 +4118,13 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>Fel</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>Du måste återansluta.</target>
@@ -4213,6 +4146,20 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>Information</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>Åtgärden lyckades</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>Felaktigt användarnamn eller lösenord.</target>
@@ -4430,6 +4377,20 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>E-postadress måste uppges.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>E-postadressen måste vara giltig.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>Användarnamn måste fyllas i.</target>
@@ -4451,41 +4412,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>Användarnamnet måste innehålla minst tre tecken.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>Användarnamnet får inte vara mer än 20 tecken långt.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>Användarnamnet får endast bestå av små bokstäver och siffror.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>E-postadress måste uppges.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>E-postadressen måste vara giltig.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>Lösenordet måste innehålla minst sex tecken.</target>
@@ -4549,20 +4475,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>Visningsnamnet måste innehålla minst tre tecken.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>Visningsnamnet får inte vara mer än 120 tecken långt.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>Beskrivningen måste innehålla minst tre tecken.</target>
@@ -4612,13 +4524,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>Orsak för rapportering får inte vara mer än 300 tecken lång.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>Moderationskommentar krävs.</target>
@@ -4633,13 +4538,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>Moderationskommentaren får inte vara mer än 300 tecken lång.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>Kanalen måste anges.</target>
@@ -4696,27 +4594,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>Namnet måste innehålla minst tre tecken.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>Namnet får inte vara mer än 20 tecken långt.</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>Namnet kan endast bestå av små bokstäver och siffror</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>Supporttexten måste innehålla minst tre tecken.</target>
@@ -5396,73 +5273,108 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>Användaren <x id="INTERPOLATION" equiv-text="{{username}}"/>s e-post har markerats som verifierad.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
-        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source><target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</target><context-group name="null">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
+        <target>Kontot <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ignoreras.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="086eda792aeb1b0d131d633b50fdd1792f5f24c6">
-        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</source><target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</target><context-group name="null">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted.</source>
+        <target>Instansen <x id="INTERPOLATION" equiv-text="{{host}}"/> ignoreras.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb72d6d1219e89d182e9fd09d853d83baf8d6499">
-        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</source><target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</target><context-group name="null">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted by the instance.</source>
+        <target>Kontot <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ignoreras av instansen.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8686834bc4afe42c1991c6c18f0bce174a0e17a6">
-        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</source><target>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</target><context-group name="null">
+        <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted by the instance.</source>
+        <target>Kontot <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> ignoreras inte längre av instansen.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="35d3509161861a610b0895bf084c781e56ba2830">
-        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</source><target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</target><context-group name="null">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> muted by the instance.</source>
+        <target>Instansen <x id="INTERPOLATION" equiv-text="{{host}}"/> ignoreras av instansen.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="978aeec5613fa97e8a5336d3599cebb23ee5a90f">
-        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</source><target>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</target><context-group name="null">
+        <source>Instance <x id="INTERPOLATION" equiv-text="{{host}}"/> unmuted by the instance.</source>
+        <target>Instansen <x id="INTERPOLATION" equiv-text="{{host}}"/> ignoreras inte längre av instansen.</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4a09bf8724e7659fbb5ec33647529cdef7614bdc">
-        <source>Mute this account</source><target>Mute this account</target><context-group name="null">
+        <source>Mute this account</source>
+        <target>Ignorera det här kontot</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d666ca3261aef72b2ddcd649d7b32af488f59952">
-        <source>Unmute this account</source><target>Unmute this account</target><context-group name="null">
+        <source>Unmute this account</source>
+        <target>Sluta ignorera det här kontot</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e17218983b1de76e5a920b04e1c2ecbdb6e3e06d">
-        <source>Mute the instance</source><target>Mute the instance</target><context-group name="null">
+        <source>Mute the instance</source>
+        <target>Ignorera instansen</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a23514d8aca2f8633622dda0e86b399dc576a2b9">
-        <source>Unmute the instance</source><target>Unmute the instance</target><context-group name="null">
+        <source>Unmute the instance</source>
+        <target>Sluta ignorera instansen</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4e4107055b44eee44b6954c41120de1cb4d46432">
-        <source>Mute this account by your instance</source><target>Mute this account by your instance</target><context-group name="null">
+        <source>Mute this account by your instance</source>
+        <target>Ignorera det här kontot av din instans</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a51c59cb5ecb7004a6a8ddd2855b5c52266ad957">
-        <source>Unmute this account by your instance</source><target>Unmute this account by your instance</target><context-group name="null">
+        <source>Unmute this account by your instance</source>
+        <target>Sluta ignorera det här kontot av din instans</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="588073e831cec240d6bb0db0b133e45dab69f178">
-        <source>Mute the instance by your instance</source><target>Mute the instance by your instance</target><context-group name="null">
+        <source>Mute the instance by your instance</source>
+        <target>Ignorera instansen av din instans</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
       <trans-unit id="676221cdabd4805901343976988c028dbf71b20a">
-        <source>Unmute the instance by your instance</source><target>Unmute the instance by your instance</target><context-group name="null">
+        <source>Unmute the instance by your instance</source>
+        <target>Sluta ignorera instansen av din instans</target>
+        <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
@@ -5494,13 +5406,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>Prenumererar</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>Prenumererar på <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -5508,9 +5413,9 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>Prenumeration avbruten</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>Prenumererar</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5522,6 +5427,13 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>Prenumeration avbruten</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>Moderator</target>
@@ -5550,6 +5462,20 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>Välkommen! Kontrollera gärna din e-post för att verifiera ditt konto och fullfölja kontoskapandet.</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>Du är nu inloggad som <x id="INTERPOLATION" equiv-text="{{username}}"/>!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>Videon att importera har uppdaterats.</target>
@@ -5578,13 +5504,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>Information</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>Uppladdningen avbröts</target>
@@ -5592,13 +5511,6 @@ När du laddar upp en video i den här kanalen kommer supportfältet automatiskt
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>Vi ber om ursäkt, PeerTube kan tyvärr inte hantera videor större än 8GB</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>Den här videon kommer överskrida din videokvot (videostorlek: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, använt: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, kvot: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index 35385cfdb074baa9947e19f2e4c3f1d803d5aad5..abd878aaa69cea131877810d23f05ff014d3a814 100644 (file)
         <source>Password</source>
         <target>கடவுச்சொல்</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6765b4c916060f6bc42d9bb69e80377dbcb5e4e9">
         <source>Login</source>
         <target>உள்நுழை</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Change the language</source>
         <target>மொழியை மாற்று</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               வெளியேறு
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>கணக்கை உருவாக்கு</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>சமீபத்தியவை</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>மேலும்</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>No results.</source>
         <target>முடிவுகள் இல்லை.</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6385c357c1de58ce92c0cf618ecf9cf74b917390">
         <source>Users</source>
         <target>பயணர்கள்</target>
         <context-group name="null">
-          <context context-type="linenumber">144</context>
+          <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
       <trans-unit id="99cb827741e93125476a0f5b676372d85d15b5fc">
         <source>Your Twitter username</source>
         <target>உங்கள் Twitter பயணர்பெயர்</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
index c8ed702f89219f3dc826a41cbda8db700107ecc9..09ed78392db43aab9c19b72d14705e3cf8519cad 100644 (file)
         <source>Password</source>
         <target>密码</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>登录</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>发送密码重置邮件</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>注册</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>更改语言</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              我的公开个人资料
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               我的帐户
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               我的视频
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               注销
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>创建帐户</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>订阅内容</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>总览</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>时下流行</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>最近添加</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>本地</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>更多</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>管理</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>显示键盘快捷键</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>切换夜间模式</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>显示不公开和私享视频</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>没有结果。</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
+          Cancel
+        </source>
         <target>
-  关于实例 <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/>
-</target>
+          取消
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>提交</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>条款</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>当前开放注册,并且</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>
-      本实例为用户上传的视频提供 <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> 的基本存储空间。
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>
-      本实例为用户上传的视频提供无限制的存储空间。
-    </target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>
-    当前不开放注册。
-  </target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>简介</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>首页默认内容</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>视频总览</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>时下流行的视频</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>最近添加的视频</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>本地视频</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>针对包含敏感内容视频的策略</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>开放注册</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>注册需要验证电子邮件地址</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>注册限制</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>用户</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>用户默认视频存储空间大小</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>用户默认单日上传限额</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>导入</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>允许通过 HTTP URL(例如 YouTube)导入视频</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>允许通过种子文件或磁力链导入视频</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>管理员</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>管理员电子邮件地址</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>用户</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>用户默认视频存储空间大小</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>用户默认单日上传限额</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>您的 Twitter 用户名</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>显示此内容所在的发布平台对应的 Twitter 帐户。</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>实例已进入 Twitter 白名单</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>转码</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>启用转码</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>如果禁用转码,用户上传的视频很有可能无法正常播放!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>转码线程数</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>启用 <x id="INTERPOLATION" equiv-text="{{resolution}}"/> 分辨率</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>部分文件不会自动同步(如预览图、字幕)。我们会直接从源实例拉取并进行缓存。</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>预览图缓存大小</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>视频字幕缓存大小</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>自定义</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>在此处直接输入 JavaScript 代码。&lt;br /&gt;示例:&lt;pre&gt;console.log('我的实例太棒了');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                在此处直接输入 CSS 代码。示例:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                您可以通过插入 &lt;em&gt;#custom-css&lt;/em&gt; 来覆盖样式设置。示例:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>高级设置</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>更新设置</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>设置信息不合法。请检查各选项卡中的设置是否存在错误。</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
         <source>Ban reason:</source>
         <target>封禁理由:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>操作</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>日期 <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>黑名单理由:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>我的设置</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>我的库</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>我的频道</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>我的视频</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>我的订阅</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>我的导入</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>杂项</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>已屏蔽的实例</target>
-        <context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>视频转移</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>视频存储空间:</target>
         <source>Profile</source>
         <target>个人资料</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>视频设置</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>危险选项</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>提交</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> 次观看</target>
@@ -2478,6 +2347,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>已屏蔽的实例</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>更改密码</target>
@@ -2719,14 +2595,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Publish will be available when upload is finished</source>
         <target>上传完毕后即可发布</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>发布</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2909,14 +2785,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Wait transcoding before publishing the video</source>
         <target>等待转码完毕后再发布视频</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>如果您选择不等待转码就发布视频,则视频在转码完毕前很有可能无法正常播放。</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2930,49 +2806,49 @@ When you will upload a video in this channel, the video support field will be au
         <source>Add another caption</source>
         <target>添加字幕</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>查看字幕文件</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>已上传      ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
         <source>Will be created on update</source>
         <target>将在更新时创建</target>
         <context-group name="null">
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>取消创建</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
         <source>Will be deleted on update</source>
         <target>将在更新时删除</target>
         <context-group name="null">
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>取消删除</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
@@ -2983,28 +2859,28 @@ When you will upload a video in this channel, the video support field will be au
             当前没有字幕。
           </target>
         <context-group name="null">
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>字幕</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>上传缩略图</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>上传预览图</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -3018,14 +2894,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>用一段简短的文字告知观众支持您的频道的方法(赞助社区等)。</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>高级设置</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3092,17 +2968,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          取消
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>分享</target>
@@ -3488,13 +3353,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>从服务器获取关于信息时发生错误</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>没有说明</target>
@@ -3516,13 +3374,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>错误</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3565,13 +3416,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>成功</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>设置已更新。</target>
@@ -3993,23 +3837,16 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>您确定要删除 <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> 吗?这将同时删除上传至该频道的所有视频。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>输入视频频道的显示名以确认操作</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>视频频道 <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> 已删除。</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>视频频道 <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> 已删除。</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>我的视频</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4084,16 +3921,44 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>é¢\91é\81\93</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>æ\88\91ç\9a\84åº\93</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>导入的视频</target>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>我的频道</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>我的订阅</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>杂项</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>视频转移</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>我的设置</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4219,6 +4084,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>错误</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>请重新进行授权。</target>
@@ -4240,6 +4112,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>提示</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>成功</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>用户名或密码不正确。</target>
@@ -4457,6 +4343,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>请输入电子邮件地址。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>请输入合法的电子邮件地址。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>请输入用户名。</target>
@@ -4478,41 +4378,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>用户名应至少 3 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>用户名不能超过 20 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>用户名只能使用小写字母和数字。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>请输入电子邮件地址。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>请输入合法的电子邮件地址。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>密码应至少 6 个字符。</target>
@@ -4576,20 +4441,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>显示名称应至少 3 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>显示名称不能超过 120 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>说明应至少 3 个字符。</target>
@@ -4639,13 +4490,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>举报理由不能超过 300 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>请输入运营备注信息。</target>
@@ -4660,13 +4504,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>运营备注信息不能超过 300 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>必须指定频道。</target>
@@ -4723,27 +4560,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>频道用户名应至少 3 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>频道用户名不能超过 20 个字符。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>频道用户名只能使用小写字母和数字。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>支持信息应至少 3 个字符。</target>
@@ -5549,13 +5365,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>已订阅</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>成功订阅 <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -5563,9 +5372,9 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>已退订</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>已订阅</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5577,6 +5386,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>已退订</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>监察员</target>
@@ -5633,13 +5449,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>提示</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>上传已取消</target>
@@ -5647,13 +5456,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>非常抱歉,PeerTube 不支持 8GB 以上的视频</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>此视频已超出您的视频存储总空间(视频大小:<x id="INTERPOLATION" equiv-text="{{videoSize}}"/>,当前已使用:<x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>,总空间:<x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index 4779d9cc8fa04c7f69366dc8b793febc3eb35fee..c1aefbb40c4159149477a316c3e2399bbbca7d99 100644 (file)
         <source>Password</source>
         <target>密碼</target>
         <context-group name="null">
-          <context context-type="linenumber">12</context>
+          <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b87e81682959464211443afc3e23c506865d2eda">
         <source>Login</source>
         <target>登入</target>
         <context-group name="null">
-          <context context-type="linenumber">38</context>
+          <context context-type="linenumber">36</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d2eb6c5d41f70d4b8c0937e7e19e196143b47681">
         <source>Send me an email to reset my password</source>
         <target>傳送電子郵件給我以重設我的密碼</target>
         <context-group name="null">
-          <context context-type="linenumber">75</context>
+          <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2ba14c37f3b23553b2602c5e535d0ff4916f24aa">
         <source>Signup</source>
         <target>註冊</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">78</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa48c3ddc2ef8e40e5c317e68bc05ae62c93b0c1">
         <source>Change the language</source>
         <target>變更語言</target>
         <context-group name="null">
-          <context context-type="linenumber">88</context>
+          <context context-type="linenumber">86</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8c654f49714163eb2991b264e9fd4858e72c04c6">
              我的公開個人資料
             </target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="01d7a5f4ca6470b564031481bc16485b53a8d4fb">
               我的帳號
             </target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fa9f3da5641dbd73d83395a0bde61bb6d5cefb10">
               我的影片
             </target>
         <context-group name="null">
-          <context context-type="linenumber">26</context>
+          <context context-type="linenumber">24</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b795a1acb4a57ee68e6c5114daa280bf6e0f70e1">
               登出
             </target>
         <context-group name="null">
-          <context context-type="linenumber">30</context>
+          <context context-type="linenumber">28</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d207cc1965ec0c29e594e0e9917f39bfc276ed87">
         <source>Create an account</source>
         <target>建立帳號</target>
         <context-group name="null">
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">37</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a52dae09be10ca3a65da918533ced3d3f4992238">
         <source>Subscriptions</source>
         <target>訂閱</target>
         <context-group name="null">
-          <context context-type="linenumber">47</context>
+          <context context-type="linenumber">45</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e95ae009d0bdb45fcc656e8b65248cf7396080d5">
         <source>Overview</source>
         <target>概覽</target>
         <context-group name="null">
-          <context context-type="linenumber">52</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
         <source>Trending</source>
         <target>趨勢</target>
         <context-group name="null">
-          <context context-type="linenumber">57</context>
+          <context context-type="linenumber">55</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1">
         <source>Recently added</source>
         <target>最近新增</target>
         <context-group name="null">
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">60</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eadc17c3df80143992e2d9028dead3199ae6d79d">
         <source>Local</source>
         <target>本地</target>
         <context-group name="null">
-          <context context-type="linenumber">67</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ac0f81713a84217c9bd1d9bb460245d8190b073f">
         <source>More</source>
         <target>更多</target>
         <context-group name="null">
-          <context context-type="linenumber">72</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b7648e7aced164498aa843b5c4e8f2f1c36a7919">
         <source>Administration</source>
         <target>管理</target>
         <context-group name="null">
-          <context context-type="linenumber">76</context>
+          <context context-type="linenumber">74</context>
         </context-group>
       </trans-unit>
       <trans-unit id="004b222ff9ef9dd4771b777950ca1d0e4cd4348a">
         <source>Show keyboard shortcuts</source>
         <target>顯示鍵盤快捷鍵</target>
         <context-group name="null">
-          <context context-type="linenumber">91</context>
+          <context context-type="linenumber">89</context>
         </context-group>
       </trans-unit>
       <trans-unit id="cf75021ac8cb9efd4f95e8880cf52c9acd265768">
         <source>Toggle dark interface</source>
         <target>切換至暗色介面</target>
         <context-group name="null">
-          <context context-type="linenumber">94</context>
+          <context context-type="linenumber">92</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8aa58cf00d949c509df91c621ab38131df0a7599">
         <source>Display unlisted and private videos</source>
         <target>顯示未列出與私密影片</target>
         <context-group name="null">
-          <context context-type="linenumber">11</context>
+          <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c31161d1661884f54fbc5635aad5ce8d4803897e">
         <source>No results.</source>
         <target>沒有結果</target>
         <context-group name="null">
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2290d09f4f113351baa9152ca8ad14cd03a11ba6">
           <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="5849c589454817c1e991639d3091d8da0e8d6bd2">
+      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
         <source>
-  About <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> instance
-</source>
-        <target>關於 <x id="INTERPOLATION" equiv-text="{{ instanceName }}"/> 實體</target>
+          Cancel
+        </source>
+        <target>
+          取消
+        </target>
         <context-group name="null">
-          <context context-type="linenumber">1</context>
+          <context context-type="linenumber">26</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
+        <source>Submit</source>
+        <target>遞交</target>
+        <context-group name="null">
+          <context context-type="linenumber">31</context>
         </context-group>
       </trans-unit>
       <trans-unit id="eec715de352a6b114713b30b640d319fa78207a0">
         <source>Terms</source>
         <target>條款</target>
         <context-group name="null">
-          <context context-type="linenumber">44</context>
+          <context context-type="linenumber">39</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9c6e6db693ab265457c6578df179c65694141d27">
         <source>User registration is allowed and</source>
         <target>允許使用者註冊與</target>
         <context-group name="null">
-          <context context-type="linenumber">25</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="ac324b07e7c3c972f1c33894eda02dc2917eda5e">
-        <source>
-      this instance provides a baseline quota of <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/> space for the videos of its users.
-    </source>
-        <target>此實體提供了基本配額 <x id="INTERPOLATION" equiv-text="{{ userVideoQuota | bytes: 0 }}"/>  空間給它的使用者的影片。</target>
-        <context-group name="null">
-          <context context-type="linenumber">27</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a6865ec6abf6af58f808501d84c8ed6ff8ce46ae">
-        <source>
-      this instance provides unlimited space for the videos of its users.
-    </source>
-        <target>此實體提供了無限的影片空間給它的使用者。</target>
-        <context-group name="null">
-          <context context-type="linenumber">31</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5c856a6a233b6f6c4cc8eed46436d31d2da63fc1">
-        <source>
-    User registration is currently not allowed.
-  </source>
-        <target>目前不允許使用者註冊。</target>
-        <context-group name="null">
-          <context context-type="linenumber">36</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a11e3ba2c5aea841de67a3c85892bb61295e94dc">
         <source>Short description</source>
         <target>短描述</target>
         <context-group name="null">
-          <context context-type="linenumber">22</context>
+          <context context-type="linenumber">21</context>
         </context-group>
       </trans-unit>
       <trans-unit id="554488d11165f38b27b8fe230aba8a2e30d57003">
         <source>Default client route</source>
         <target>預設客戶端路由</target>
         <context-group name="null">
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3fae5a310387c065757fde11f22689b45a7b6f2d">
         <source>Videos Overview</source>
         <target>影片概覽</target>
         <context-group name="null">
-          <context context-type="linenumber">58</context>
+          <context context-type="linenumber">51</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1cbeb1eb589bfbe5efce94184cacd3095ca26948">
         <source>Videos Trending</source>
         <target>影片趨勢</target>
         <context-group name="null">
-          <context context-type="linenumber">59</context>
+          <context context-type="linenumber">52</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1861c96217213992e02dcb77e15ea69e718c9883">
         <source>Videos Recently Added</source>
         <target>最近新增的影片</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">53</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f">
         <source>Local videos</source>
         <target>本地影片</target>
         <context-group name="null">
-          <context context-type="linenumber">61</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8551afadb69b3fef89e191f507e8ac84e624e8b9">
         <source>Policy on videos containing sensitive content</source>
         <target>包含敏感內容的影片政策</target>
         <context-group name="null">
-          <context context-type="linenumber">70</context>
+          <context context-type="linenumber">61</context>
         </context-group>
       </trans-unit>
       <trans-unit id="aa3ef567a1ea22c1e4d0acfdc8f80bc636bf12df">
         <source>Signup enabled</source>
         <target>已啟用註冊</target>
         <context-group name="null">
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">84</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90f449b1f4787e6c9731198a96d35399c1b340a7">
         <source>Signup requires email verification</source>
         <target>註冊需要電子郵件驗證</target>
         <context-group name="null">
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">91</context>
         </context-group>
       </trans-unit>
       <trans-unit id="68bda70e0dd4f7f91549462e55f1b2a1602d8402">
         <source>Signup limit</source>
         <target>限制註冊</target>
+        <context-group name="null">
+          <context context-type="linenumber">96</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
+        <source>Users</source>
+        <target>使用者</target>
         <context-group name="null">
           <context context-type="linenumber">105</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
+        <source>User default video quota</source>
+        <target>使用者預設影片配額</target>
+        <context-group name="null">
+          <context context-type="linenumber">109</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
+        <source>User default daily upload limit</source>
+        <target>預設使用者每日上傳限制</target>
+        <context-group name="null">
+          <context context-type="linenumber">121</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a059709f71aa4c0ac219e160e78a738682ca6a36">
         <source>Import</source>
         <target>匯入</target>
         <source>Video import with HTTP URL (i.e. YouTube) enabled</source>
         <target>以 HTTP URL 匯入影片(如 YouTube)已啟用</target>
         <context-group name="null">
-          <context context-type="linenumber">120</context>
+          <context context-type="linenumber">141</context>
         </context-group>
       </trans-unit>
       <trans-unit id="05fdf7b5be1c3a7126e3c06d81da3134981b0a9e">
         <source>Video import with a torrent file or a magnet URI enabled</source>
         <target>已啟用種子檔案或磁力連結匯入影片</target>
         <context-group name="null">
-          <context context-type="linenumber">127</context>
+          <context context-type="linenumber">148</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca2283fc765b9f44b69f0175d685dc2443da6011">
         <source>Administrator</source>
         <target>管理員</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">155</context>
         </context-group>
       </trans-unit>
       <trans-unit id="55a0f51e38679d3141841e8333da5779d349c587">
         <source>Admin email</source>
         <target>管理電子郵件</target>
         <context-group name="null">
-          <context context-type="linenumber">134</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4d13a9cd5ed3dcee0eab22cb25198d43886942be">
-        <source>Users</source>
-        <target>使用者</target>
-        <context-group name="null">
-          <context context-type="linenumber">144</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="31b3275d999af45fe64c6824e6e017d2e2704f09">
-        <source>User default video quota</source>
-        <target>使用者預設影片配額</target>
-        <context-group name="null">
-          <context context-type="linenumber">147</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="f5528147716c4d3286c89defbe63ee0b75da5ffe">
-        <source>User default daily upload limit</source>
-        <target>預設使用者每日上傳限制</target>
-        <context-group name="null">
-          <context context-type="linenumber">161</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="50247a2f9711ea9e9a85aacc46668131e9b424a5">
         <source>Your Twitter username</source>
         <target>您的 Twitter 使用者名稱</target>
         <context-group name="null">
-          <context context-type="linenumber">181</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6e671e839ca889feef0d8ed525d1a44b4b10870c">
         <source>Indicates the Twitter account for the website or platform on which the content was published.</source>
         <target>指示發佈影片的網頁或平臺的 Twitter 帳號</target>
         <context-group name="null">
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">187</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c0716c28b9d4c9e0b2fd6031334394214e5f9605">
         <source>Instance whitelisted by Twitter</source>
         <target>由 Twitter 列入白名單的實體</target>
         <context-group name="null">
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">199</context>
         </context-group>
       </trans-unit>
       <trans-unit id="419d940613972cc3fae9c8ea0a4306dbf80616e5">
         <source>Transcoding</source>
         <target>轉換編碼</target>
         <context-group name="null">
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">215</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fca29003c4ea1226ff8cbee89481758aab0e2be9">
         <source>Transcoding enabled</source>
         <target>轉換編碼已啟用</target>
         <context-group name="null">
-          <context context-type="linenumber">215</context>
+          <context context-type="linenumber">221</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6ef2ab819d4441fa8bddf6759b6936783d06616f">
         <source>If you disable transcoding, many videos from your users will not work!</source>
         <target>若您停用轉換編碼,從您的使用者們而來的許多影片將會無法運作!</target>
         <context-group name="null">
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">222</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a33feadefbb776217c2db96100736314f8b765c2">
         <source>Transcoding threads</source>
         <target>轉換編碼執行緒</target>
         <context-group name="null">
-          <context context-type="linenumber">223</context>
+          <context context-type="linenumber">237</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5afc7e831e59c325e8fb3e208ec108ff53fb3500">
         <source>Resolution <x id="INTERPOLATION" equiv-text="{{resolution}}"/> enabled</source>
         <target>解析度 <x id="INTERPOLATION" equiv-text="{{resolution}}"/> 已啟用</target>
         <context-group name="null">
-          <context context-type="linenumber">239</context>
+          <context context-type="linenumber">252</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e9fb2d7685ae280026fe6463731170b067e419d5">
           <x id="START_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;my-help&gt;"/><x id="CLOSE_TAG_MY-HELP" ctype="x-my-help" equiv-text="&lt;/my-help&gt;"/>
         </target>
         <context-group name="null">
-          <context context-type="linenumber">244</context>
+          <context context-type="linenumber">260</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d5bf7bea37daff4e018fd11a1b552512e5cb54c0">
         <source>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</source>
         <target>有一些檔案並未聯盟化(預覽、字幕)。我們會直接從原始實體擷取它們並快取。</target>
         <context-group name="null">
-          <context context-type="linenumber">249</context>
+          <context context-type="linenumber">265</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d00f6c2dcb426440a0a8cd8eec12d094fbfaf6f7">
         <source>Previews cache size</source>
         <target>預覽快取大小</target>
         <context-group name="null">
-          <context context-type="linenumber">254</context>
+          <context context-type="linenumber">271</context>
         </context-group>
       </trans-unit>
       <trans-unit id="98970cd72e776308a37dc4e84bebbedffc787607">
         <source>Video captions cache size</source>
         <target>影片字幕快取大小</target>
         <context-group name="null">
-          <context context-type="linenumber">265</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e3a65df2560e99864bbde695da3a7bdf743a184c">
         <source>Customizations</source>
         <target>自訂</target>
         <context-group name="null">
-          <context context-type="linenumber">275</context>
+          <context context-type="linenumber">289</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0da9752916950ce6890d897b835c923a71ad9c5c">
         <source>JavaScript</source>
         <target>JavaScript</target>
         <context-group name="null">
-          <context context-type="linenumber">278</context>
+          <context context-type="linenumber">294</context>
         </context-group>
       </trans-unit>
       <trans-unit id="fda2339a6e6ba017ee43b560caf660ed4022333c">
         <source>Write directly JavaScript code.&lt;br /&gt;Example: &lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</source>
         <target>直接編寫 JavaScript 程式碼。&lt;br /&gt;範例:&lt;pre&gt;console.log('my instance is amazing');&lt;/pre&gt;</target>
-        <context-group name="null">
-          <context context-type="linenumber">281</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="3c2a41724fa0abcd1047ed111508367405f229b5">
-        <source>
-                Write directly CSS code. Example:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                Prepend with &lt;em&gt;#custom-css&lt;/em&gt; to override styles. Example:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </source>
-        <target>
-                直接撰寫 CSS 程式碼。範例:&lt;br /&gt;
-                &lt;pre&gt;
-      body <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        background-color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-
-                附加 &lt;em&gt;#custom-css&lt;/em&gt; 以覆寫樣式。範例:
-                &lt;pre&gt;
-      #custom-css .logged-in-email <x id="INTERPOLATION" equiv-text="{{ '{' }}"/>
-        color: red;
-      <x id="INTERPOLATION_1" equiv-text="{{ '}' }}"/>
-                &lt;/pre&gt;
-              </target>
         <context-group name="null">
           <context context-type="linenumber">297</context>
         </context-group>
         <source>Advanced configuration</source>
         <target>進階設定</target>
         <context-group name="null">
-          <context context-type="linenumber">207</context>
+          <context context-type="linenumber">212</context>
         </context-group>
       </trans-unit>
       <trans-unit id="dad5a5283e4c853c011a0f03d5a52310338bbff8">
         <source>Update configuration</source>
         <target>更新設定</target>
         <context-group name="null">
-          <context context-type="linenumber">325</context>
+          <context context-type="linenumber">340</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3e459b5c3861d8c80084d21d233b7c8e2edd3cca">
         <source>It seems the configuration is invalid. Please search potential errors in the different tabs.</source>
         <target>設定似乎無效。請在不同的分頁中搜尋潛在的錯誤。</target>
         <context-group name="null">
-          <context context-type="linenumber">326</context>
+          <context context-type="linenumber">341</context>
         </context-group>
       </trans-unit>
       <trans-unit id="80dbb8ba42b97a9ec035c0ba09f45c07ea07096c">
           <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="02ba1a65db92d1d0ab4ba380086e9be61891aaa5">
+        <source>User's email must be verified to login</source>
+        <target>使用者的電子郵件必須驗證過才能登入</target>
+        <context-group name="null">
+          <context context-type="linenumber">72</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="79cee9973620b2592ff2824c525aa8ed0b5e2b8b">
+        <source>User's email is verified / User can login without email verification</source>
+        <target>使用者的電子郵件已驗證/使用者可以不透過電子郵件驗證登入</target>
+        <context-group name="null">
+          <context context-type="linenumber">76</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="a9587caabf0dc5d824f817baae1c2f5521d9b1ee">
         <source>Ban reason:</source>
         <target>阻擋理由:</target>
         <context-group name="null">
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="bb863c794307735652d8695143e116eaee8a3c4f">
         <source>Actions</source>
         <target>動作</target>
         <context-group name="null">
-          <context context-type="linenumber">33</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e330cbadca2d8639aabf525d5fe7e5b62d324ee2">
         <source>Date <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></source>
         <target>日期 <x id="START_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;p-sortIcon&gt;"/><x id="CLOSE_TAG_P-SORTICON" ctype="x-p-sortIcon" equiv-text="&lt;/p-sortIcon&gt;"/></target>
         <context-group name="null">
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">11</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7963019b5535b51efa399e6a62b163f3e04d296f">
         <source>Blacklist reason:</source>
         <target>黑名單理由:</target>
         <context-group name="null">
-          <context context-type="linenumber">41</context>
+          <context context-type="linenumber">43</context>
         </context-group>
       </trans-unit>
       <trans-unit id="90868353e7e6f5994109ee1011131cefa992116c">
           <context context-type="linenumber">23</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
-        <source>My settings</source>
-        <target>我的設定</target>
-        <context-group name="null">
-          <context context-type="linenumber">3</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
-        <source>My library</source>
-        <target>我的媒體庫</target>
-        <context-group name="null">
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
-        <source>My channels</source>
-        <target>我的頻道</target>
-        <context-group name="null">
-          <context context-type="linenumber">12</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
-        <source>My videos</source>
-        <target>我的影片</target>
-        <context-group name="null">
-          <context context-type="linenumber">14</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
-        <source>My subscriptions</source>
-        <target>我的訂閱</target>
-        <context-group name="null">
-          <context context-type="linenumber">16</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bd751145ec934c2839fd6acffee05fbf439782ed">
-        <source>My imports</source>
-        <target>我的匯入</target>
-        <context-group name="null">
-          <context context-type="linenumber">18</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
-        <source>Misc</source>
-        <target>雜項</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
-        <source>Muted instances</source>
-        <target>已靜音的實體</target>
-        <context-group name="null">
-          <context context-type="linenumber">2</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
-        <source>Ownership changes</source>
-        <target>所有權變更</target>
-        <context-group name="null">
-          <context context-type="linenumber">33</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9518d3fb042d551167c1701ddeb88a1374cf1e48">
         <source>Video quota:</source>
         <target>影片配額:</target>
         <source>Profile</source>
         <target>簡介</target>
         <context-group name="null">
-          <context context-type="linenumber">8</context>
+          <context context-type="linenumber">7</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5398623f87ee72ed23f5023918db1707771e925">
         <source>Video settings</source>
         <target>影片設定</target>
         <context-group name="null">
-          <context context-type="linenumber">15</context>
+          <context context-type="linenumber">16</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c74e3202d080780c6415d0e9209c1c859438b735">
         <source>Danger zone</source>
         <target>危險區域</target>
         <context-group name="null">
-          <context context-type="linenumber">18</context>
+          <context context-type="linenumber">19</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2dc22fcebf6aaa76196d2def33a827a34bf910bf">
           <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="71c77bb8cecdf11ec3eead24dd1ba506573fa9cd">
-        <source>Submit</source>
-        <target>遞交</target>
-        <context-group name="null">
-          <context context-type="linenumber">24</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="8057bddbed23d6cd911df8cc3a4ec24d1f258b79">
         <source><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views</source>
         <target><x id="INTERPOLATION" equiv-text="{{ video.createdAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> 次檢視</target>
@@ -2425,6 +2316,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2bc7533f8c8e7d183950ba1094a0acd9efc22e5e">
+        <source>Muted instances</source>
+        <target>已靜音的實體</target>
+        <context-group name="null">
+          <context context-type="linenumber">2</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="739516c2ca75843d5aec9cf0e6b3e4335c4227b9">
         <source>Change password</source>
         <target>變更密碼</target>
@@ -2628,6 +2526,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">159</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="385811ab5a5c3e96e0db46c9ce1fc3147d8cd4c7">
+        <source>Sorry, but something went wrong</source>
+        <target>抱歉,不過好像有什麼東西出錯了</target>
+        <context-group name="null">
+          <context context-type="linenumber">49</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="63d6bf87c9f30441175648dfd3ef6a19292287c2">
         <source>
   Congratulations, the video behind <x id="INTERPOLATION" equiv-text="{{ targetUrl }}"/> will be imported! You can already add information about this video.
@@ -2662,14 +2567,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Publish will be available when upload is finished</source>
         <target>上傳完成時將可發佈</target>
         <context-group name="null">
-          <context context-type="linenumber">53</context>
+          <context context-type="linenumber">58</context>
         </context-group>
       </trans-unit>
       <trans-unit id="223aae0477f79f0bc4436c1c57619415f04cbbb3">
         <source>Publish</source>
         <target>發佈</target>
         <context-group name="null">
-          <context context-type="linenumber">60</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2fcbf437e001f47974d45bd03a19e0d9245fdb3b">
@@ -2850,14 +2755,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Wait transcoding before publishing the video</source>
         <target>正等待發佈影片前的轉換編碼</target>
         <context-group name="null">
-          <context context-type="linenumber">130</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="24f468ce1148a096477d8dd0d00f0d1fd88d6c63">
         <source>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</source>
         <target>如果您決定不要等待在發佈影片前的轉換編碼,它可能會在轉換編碼結束前都無法播放。</target>
         <context-group name="null">
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">132</context>
         </context-group>
       </trans-unit>
       <trans-unit id="c7742322b1d3dbc921362058d1747c7ec2adbec7">
@@ -2871,49 +2776,49 @@ When you will upload a video in this channel, the video support field will be au
         <source>Add another caption</source>
         <target>新增其他字幕</target>
         <context-group name="null">
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">147</context>
         </context-group>
       </trans-unit>
       <trans-unit id="a46a7503167b77b3ec4e28274a3d1dda637617ed">
         <source>See the subtitle file</source>
         <target>檢視字幕檔案</target>
         <context-group name="null">
-          <context context-type="linenumber">155</context>
+          <context context-type="linenumber">156</context>
         </context-group>
       </trans-unit>
       <trans-unit id="e687f6387adbaf61ce650b58f0e60ca42d843cee">
         <source>Already uploaded       ✔</source>
         <target>已上傳      ✔</target>
         <context-group name="null">
-          <context context-type="linenumber">159</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="ca4588e185413b2fc77dbe35c861cc540b11b9ad">
         <source>Will be created on update</source>
         <target>將在更新時建立</target>
         <context-group name="null">
-          <context context-type="linenumber">167</context>
+          <context context-type="linenumber">168</context>
         </context-group>
       </trans-unit>
       <trans-unit id="308a79679d012938a625e41fdd4b804fe42b57b9">
         <source>Cancel create</source>
         <target>取消建立</target>
         <context-group name="null">
-          <context context-type="linenumber">169</context>
+          <context context-type="linenumber">170</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b6bfdd386cb0b560d697c93555d8cd8cab00c393">
         <source>Will be deleted on update</source>
         <target>將在更新時刪除</target>
         <context-group name="null">
-          <context context-type="linenumber">175</context>
+          <context context-type="linenumber">176</context>
         </context-group>
       </trans-unit>
       <trans-unit id="88395fc0137e46a9853cf16762bf5a87687d0d0c">
         <source>Cancel deletion</source>
         <target>取消刪除</target>
         <context-group name="null">
-          <context context-type="linenumber">177</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="82f867b2607d45ba36de11d4c8b53d7177122ee0">
@@ -2924,28 +2829,28 @@ When you will upload a video in this channel, the video support field will be au
             現在沒有字幕。
           </target>
         <context-group name="null">
-          <context context-type="linenumber">182</context>
+          <context context-type="linenumber">183</context>
         </context-group>
       </trans-unit>
       <trans-unit id="0c720e0dd9e6c60095f961cb714f47e8c0090f93">
         <source>Captions</source>
         <target>字幕</target>
         <context-group name="null">
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">140</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1dd793abd1cb8d16a7a2cb71ca5549a7111ee513">
         <source>Upload thumbnail</source>
         <target>上傳縮圖</target>
         <context-group name="null">
-          <context context-type="linenumber">195</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9df3f57e251c077bef7e7da81677cb971c55b639">
         <source>Upload preview</source>
         <target>上傳預覽</target>
         <context-group name="null">
-          <context context-type="linenumber">202</context>
+          <context context-type="linenumber">203</context>
         </context-group>
       </trans-unit>
       <trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
@@ -2959,14 +2864,14 @@ When you will upload a video in this channel, the video support field will be au
         <source>Short text to tell people how they can support you (membership platform...).</source>
         <target>告訴人們他們可以如何支援您(成員平臺等)的短文</target>
         <context-group name="null">
-          <context context-type="linenumber">209</context>
+          <context context-type="linenumber">210</context>
         </context-group>
       </trans-unit>
       <trans-unit id="d91da0abc638c05e52adea253d0813f3584da4b1">
         <source>Advanced settings</source>
         <target>進階設定</target>
         <context-group name="null">
-          <context context-type="linenumber">190</context>
+          <context context-type="linenumber">191</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2335f0bd17c63d835b50cfbbcea6c459cb1314c0">
@@ -3033,17 +2938,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">3</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="fb8aad312b72bbb7e5a1e2cc0b55fae8962bf0fb">
-        <source>
-          Cancel
-        </source>
-        <target>
-          取消
-        </target>
-        <context-group name="null">
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
         <source>Share</source>
         <target>分享</target>
@@ -3428,13 +3322,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">14</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="814d28bf9dcbd3122254e664b446ac8e0442bc08">
-        <source>Error getting about from server</source>
-        <target>取得關於伺服器的錯誤</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="37b56526e384f843a15323dc730b484a97b4c968">
         <source>No description</source>
         <target>沒有描述</target>
@@ -3456,13 +3343,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
-        <source>Error</source>
-        <target>錯誤</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d9fc2b03f04056671d7d4ffcac7197189d959cd6">
         <source>240p</source>
         <target>240p</target>
@@ -3505,13 +3385,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
-        <source>Success</source>
-        <target>成功</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="b9e64712e3e5c342ce9cd32eec6cd7d6c00f4048">
         <source>Configuration updated.</source>
         <target>設定已更新。</target>
@@ -3778,6 +3651,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="910ed85f550272401b134a40d019ab3359fe883f">
+        <source>Set Email as Verified</source>
+        <target>設定電子郵件為已驗證</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="ac401df84c5fa471700c3368de51c969ccb8bacf">
         <source>You cannot ban root.</source>
         <target>您不能阻擋 root。</target>
@@ -3820,6 +3700,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="f4a8f2ef1fbfc19e1e049e69f63c40063c0d0650">
+        <source><x id="INTERPOLATION" equiv-text="{{num}}"/> users email set as verified.</source>
+        <target><x id="INTERPOLATION" equiv-text="{{num}}"/> 個使用者電子郵件設定為已驗證。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="2667ca38672421a0a7a22343d2a0060ee41246de">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> unmuted.</source>
         <target>帳號 <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> 已解除靜音。</target>
@@ -3932,23 +3819,16 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="d5adc9efad0469fc3e1503d68c4ec2ff4453a814">
-        <source>Do you really want to delete <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/>? It will delete all videos uploaded in this channel too.</source>
-        <target>您真的想要刪除 <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> 嗎?這也會刪除所有上傳到這個頻道的影片。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="703dee7f3e693f9c77ef17c46f9fa71999609f8e">
-        <source>Please type the name of the video channel to confirm</source>
-        <target>請輸入影片頻道的名稱以確認</target>
+      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
+        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
+        <target>影片頻道 <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> 已刪除。</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="a81a33275b683729ad938b6102e7e34a057537a2">
-        <source>Video channel <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> deleted.</source>
-        <target>影片頻道 <x id="INTERPOLATION" equiv-text="{{videoChannelName}}"/> 已刪除。</target>
+      <trans-unit id="d02888c485d3aeab6de628508f4a00312a722894">
+        <source>My videos</source>
+        <target>我的影片</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4023,16 +3903,44 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19">
-        <source>Channels</source>
-        <target>頻道</target>
+      <trans-unit id="4ef4f031c147fb9ee0168bc6eacb78de180d7432">
+        <source>My library</source>
+        <target>我的媒體庫</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="8dd18d9047c4b2dc9786550dfd8fa99f3b14e17f">
+        <source>My channels</source>
+        <target>我的頻道</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="29038e66547b3ba70701fb34eda68834a56f17d9">
+        <source>My subscriptions</source>
+        <target>我的訂閱</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="46aa32e581922d6d2c3d7bc4c87209ad5808b029">
+        <source>Misc</source>
+        <target>雜項</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="73022f1676784c4f9b8cdbb322e52b02ccc800b7">
+        <source>Ownership changes</source>
+        <target>所有權變更</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="4bc7db3e3f8ae777dd480e2019af97fd8c1be47d">
-        <source>Video imports</source>
-        <target>影片匯入</target>
+      <trans-unit id="efad4be364b8fb5c73cbfcc7acccd542f9d84ad6">
+        <source>My settings</source>
+        <target>我的設定</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -4158,6 +4066,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d">
+        <source>Error</source>
+        <target>錯誤</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="e31bbf15d6ba5c7c0f17f89a98029cff0bd40b87">
         <source>You need to reconnect.</source>
         <target>您需要重新連線。</target>
@@ -4179,6 +4094,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
+        <source>Info</source>
+        <target>資訊</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba">
+        <source>Success</source>
+        <target>成功</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="247071f6c9233b7e5bc1d8f46795ab6b032f1fbe">
         <source>Incorrect username or password.</source>
         <target>不正確的使用者名稱或密碼。</target>
@@ -4396,6 +4325,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
+        <source>Email is required.</source>
+        <target>電子郵件必填。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
+        <source>Email must be valid.</source>
+        <target>電子郵件必須為有效電子郵件。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="5db300f6fba918a35597160183205ede13e8e149">
         <source>Username is required.</source>
         <target>使用者名稱必填。</target>
@@ -4417,41 +4360,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="05ad6b99d9bf7b51968aa0b0b939e8627a329bea">
-        <source>Username must be at least 3 characters long.</source>
-        <target>使用者名稱必須至少 3 個字元長。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="d4b11fd0ddeea39b33f911d3aac1e82799cdaaef">
-        <source>Username cannot be more than 20 characters long.</source>
-        <target>使用者名稱不能多於 20 個字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5acbe0aa7a7157b1f09057a98ba01ab578a303a9">
-        <source>Username should be only lowercase alphanumeric characters.</source>
-        <target>使用者名稱應該僅有小寫英數字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="b6f52e19f074f77866fa03fabe1ddd5cdae346f0">
-        <source>Email is required.</source>
-        <target>電子郵件必填。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="bef8a36c3dffff15fb5faf3d20bdbbbc1af824c1">
-        <source>Email must be valid.</source>
-        <target>電子郵件必須為有效電子郵件。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="1fe26e49476ac701885abc59127e96a3760847f0">
         <source>Password must be at least 6 characters long.</source>
         <target>密碼必須至少 6 個字元長。</target>
@@ -4515,20 +4423,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="bdeb1a8e69e137572df795d64120ea85069b7674">
-        <source>Display name must be at least 3 characters long.</source>
-        <target>顯示名稱必須至少 3 個字元長。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="e81bda510399d52f26a44a15c3dbf4d6205d90a9">
-        <source>Display name cannot be more than 120 characters long.</source>
-        <target>顯示名稱不能多於 120 個字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="d531c2261dc0c2739bd7cbb2bb175946b7eeb3ae">
         <source>Description must be at least 3 characters long.</source>
         <target>描述必須至少 3 個字元長。</target>
@@ -4578,13 +4472,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7de2178ed1036844fb1c3ad8b7899a039fcdcdb9">
-        <source>Report reason cannot be more than 300 characters long.</source>
-        <target>回報理由不能多於 300 個字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="2fa41debd17a206d4a2a5e8d14bcd7055f6e5118">
         <source>Moderation comment is required.</source>
         <target>管理評論必填。</target>
@@ -4599,13 +4486,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="89d0b662dde0871cf17244e79b2cb62cd517e44f">
-        <source>Moderation comment cannot be more than 300 characters long.</source>
-        <target>管理評論無法多於 300 個字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="94b831c7e3684258f88e099c6cd3b8f73f8a2de6">
         <source>The channel is required.</source>
         <target>頻道必填。</target>
@@ -4662,27 +4542,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="06b5d33d89bb8e6a5013dbd3c07c44389a6f1069">
-        <source>Name must be at least 3 characters long.</source>
-        <target>名稱必須至少 3 個字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="a35f2514e29113179795cdb27bca8a2e99c43482">
-        <source>Name cannot be more than 20 characters long.</source>
-        <target>名稱不能超過 20 個字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="807f79894e0c31beca2db09ca4aff57dfaaf3bb9">
-        <source>Name should be only lowercase alphanumeric characters.</source>
-        <target>名稱應該只有小寫英數字元。</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="e7182e21e9566cc81c83f92727461322f71fd69b">
         <source>Support text must be at least 3 characters long.</source>
         <target>支援文字必須至少 3 個字元長。</target>
@@ -5362,6 +5221,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="534202c90c6dcadd2989fc72c5030d5483e26096">
+        <source>User <x id="INTERPOLATION" equiv-text="{{username}}"/> email set as verified</source>
+        <target>使用者 <x id="INTERPOLATION" equiv-text="{{username}}"/> 的電子郵件設定為已驗證</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="33a6319f765848a22a155cef9f1d8e645202e249">
         <source>Account <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> muted.</source>
         <target>帳號 <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/> 已解除靜音。</target>
@@ -5488,13 +5354,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
-        <source>Subscribed</source>
-        <target>已訂閱</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="58639b3f0be657475928fb49c4a7cbd16aa44ded">
         <source>Subscribed to <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></source>
         <target>訂閱 <x id="INTERPOLATION" equiv-text="{{nameWithHost}}"/></target>
@@ -5502,9 +5361,9 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
-        <source>Unsubscribed</source>
-        <target>已取消訂閱</target>
+      <trans-unit id="1cadbf82f0e91611321c5abd282f0c23d8ccbfa1">
+        <source>Subscribed</source>
+        <target>已訂閱</target>
         <context-group name="null">
           <context context-type="linenumber">1</context>
         </context-group>
@@ -5516,6 +5375,13 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="294395337b767af84f952ac28d58d54a13a11471">
+        <source>Unsubscribed</source>
+        <target>已取消訂閱</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="38c877fb0a5fdcadc379256953ad2d1eb8233fdf">
         <source>Moderator</source>
         <target>主持人</target>
@@ -5544,6 +5410,20 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="21565881ad1dff3c98738b9535b3515cec140609">
+        <source>Welcome! Now please check your emails to verify your account and complete signup.</source>
+        <target>歡迎!現在請檢查您的電子郵件以驗證您的帳號並完成註冊。</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="14200e26888a07633c0f177020dce8f3ec7311a6">
+        <source>You are now logged in as <x id="INTERPOLATION" equiv-text="{{username}}"/>!</source>
+        <target>您現在登入為 <x id="INTERPOLATION" equiv-text="{{username}}"/>!</target>
+        <context-group name="null">
+          <context context-type="linenumber">1</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="320c9c3482a0ebe46da42ce9e0cbdc5ba26ea8bb">
         <source>Video to import updated.</source>
         <target>要匯入的影片已更新。</target>
@@ -5572,13 +5452,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="321e4419a943044e674beb55b8039f42a9761ca5">
-        <source>Info</source>
-        <target>資訊</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="c5cb19aeb6447deda40cc1227ceca1359ab955e9">
         <source>Upload cancelled</source>
         <target>已取消上傳</target>
@@ -5586,13 +5459,6 @@ When you will upload a video in this channel, the video support field will be au
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="c55f41189ac6ad3003cce813245f4508284ed0aa">
-        <source>We are sorry but PeerTube cannot handle videos &gt; 8GB</source>
-        <target>我們很抱歉,但 PeerTube 無法處理大於 8GB 的影片</target>
-        <context-group name="null">
-          <context context-type="linenumber">1</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="a6019e856f511dbe1fe658790c71c594b26930ee">
         <source>Your video quota is exceeded with this video (video size: <x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>, quota: <x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</source>
         <target>您的影片配額已因此影片超過(影片大小:<x id="INTERPOLATION" equiv-text="{{videoSize}}"/>, used: <x id="INTERPOLATION_1" equiv-text="{{videoQuotaUsed}}"/>,配額:<x id="INTERPOLATION_2" equiv-text="{{videoQuota}}"/>)</target>
index 9c0e2fd9ec9417c50bf73eceeccd583710fc83a4..b3a4ff54134b4342d85bfa1fb1ab14cc91b7115a 100644 (file)
@@ -5,7 +5,7 @@
     <body>
       <trans-unit id="Afar">
         <source>Afar</source>
-        <target>Afar</target>
+        <target>Ver</target>
       </trans-unit>
       <trans-unit id="Abkhazian">
         <source>Abkhazian</source>
         <source>Avaric</source>
         <target>Avaars</target>
       </trans-unit>
+      <trans-unit id="Kotava">
+        <source>Kotava</source>
+        <target>Kotava</target>
+      </trans-unit>
       <trans-unit id="Aymara">
         <source>Aymara</source>
         <target>Aymara</target>
         <source>English</source>
         <target>Engels</target>
       </trans-unit>
+      <trans-unit id="Esperanto">
+        <source>Esperanto</source>
+        <target>Esperanto</target>
+      </trans-unit>
       <trans-unit id="Estonian">
         <source>Estonian</source>
         <target>Ests</target>
         <source>Javanese</source>
         <target>Javaans</target>
       </trans-unit>
+      <trans-unit id="Lojban">
+        <source>Lojban</source>
+        <target>Lojban</target>
+      </trans-unit>
       <trans-unit id="Japanese">
         <source>Japanese</source>
         <target>Japans</target>
         <source>Nyanja</source>
         <target>Nyanja</target>
       </trans-unit>
+      <trans-unit id="Occitan">
+        <source>Occitan</source>
+        <target>Occitan</target>
+      </trans-unit>
       <trans-unit id="Ojibwa">
         <source>Ojibwa</source>
         <target>Ojibwe</target>
         <source>Tigrinya</source>
         <target>Tigrinya</target>
       </trans-unit>
+      <trans-unit id="Klingon">
+        <source>Klingon</source>
+        <target>Klingon</target>
+      </trans-unit>
       <trans-unit id="Tonga (Tonga Islands)">
         <source>Tonga (Tonga Islands)</source>
         <target>Tongaans</target>
diff --git a/client/src/locale/target/iso639_pl_PL.xml b/client/src/locale/target/iso639_pl_PL.xml
deleted file mode 100644 (file)
index b483f5c..0000000
+++ /dev/null
@@ -1,695 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--XLIFF document generated by Zanata. Visit http://zanata.org for more infomation.-->
-<xliff xmlns="urn:oasis:names:tc:xliff:document:1.1" xmlns:xyz="urn:appInfo:Items" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.1 http://www.oasis-open.org/committees/xliff/documents/xliff-core-1.1.xsd" version="1.1">
-  <file source-language="en-US" datatype="plaintext" original="" target-language="pl-PL">
-    <body>
-      <trans-unit id="Afar">
-        <source>Afar</source>
-        <target>Afar</target>
-      </trans-unit>
-      <trans-unit id="Abkhazian">
-        <source>Abkhazian</source>
-        <target>Abchaski</target>
-      </trans-unit>
-      <trans-unit id="Afrikaans">
-        <source>Afrikaans</source>
-        <target>Afrikaans</target>
-      </trans-unit>
-      <trans-unit id="Akan">
-        <source>Akan</source>
-        <target>Akan</target>
-      </trans-unit>
-      <trans-unit id="Amharic">
-        <source>Amharic</source>
-        <target>Amharski</target>
-      </trans-unit>
-      <trans-unit id="Arabic">
-        <source>Arabic</source>
-        <target>Arabski</target>
-      </trans-unit>
-      <trans-unit id="Aragonese">
-        <source>Aragonese</source>
-      </trans-unit>
-      <trans-unit id="American Sign Language">
-        <source>American Sign Language</source>
-        <target>Amerykański Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Assamese">
-        <source>Assamese</source>
-      </trans-unit>
-      <trans-unit id="Avaric">
-        <source>Avaric</source>
-        <target>Awarski</target>
-      </trans-unit>
-      <trans-unit id="Kotava">
-        <source>Kotava</source>
-      </trans-unit>
-      <trans-unit id="Aymara">
-        <source>Aymara</source>
-      </trans-unit>
-      <trans-unit id="Azerbaijani">
-        <source>Azerbaijani</source>
-      </trans-unit>
-      <trans-unit id="Bashkir">
-        <source>Bashkir</source>
-        <target>Baszkirski</target>
-      </trans-unit>
-      <trans-unit id="Bambara">
-        <source>Bambara</source>
-        <target>Bambara</target>
-      </trans-unit>
-      <trans-unit id="Belarusian">
-        <source>Belarusian</source>
-        <target>Białoruski</target>
-      </trans-unit>
-      <trans-unit id="Bengali">
-        <source>Bengali</source>
-        <target>Bengalski</target>
-      </trans-unit>
-      <trans-unit id="British Sign Language">
-        <source>British Sign Language</source>
-        <target>Brytyjski Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Bislama">
-        <source>Bislama</source>
-        <target>Bislama</target>
-      </trans-unit>
-      <trans-unit id="Tibetan">
-        <source>Tibetan</source>
-        <target>Tybetański</target>
-      </trans-unit>
-      <trans-unit id="Bosnian">
-        <source>Bosnian</source>
-        <target>Bośniacki</target>
-      </trans-unit>
-      <trans-unit id="Breton">
-        <source>Breton</source>
-        <target>Bretoński</target>
-      </trans-unit>
-      <trans-unit id="Bulgarian">
-        <source>Bulgarian</source>
-        <target>Bułgarski</target>
-      </trans-unit>
-      <trans-unit id="Brazilian Sign Language">
-        <source>Brazilian Sign Language</source>
-      </trans-unit>
-      <trans-unit id="Catalan">
-        <source>Catalan</source>
-        <target>Kataloński</target>
-      </trans-unit>
-      <trans-unit id="Czech">
-        <source>Czech</source>
-        <target>Czeski</target>
-      </trans-unit>
-      <trans-unit id="Chamorro">
-        <source>Chamorro</source>
-      </trans-unit>
-      <trans-unit id="Chechen">
-        <source>Chechen</source>
-      </trans-unit>
-      <trans-unit id="Chuvash">
-        <source>Chuvash</source>
-      </trans-unit>
-      <trans-unit id="Cornish">
-        <source>Cornish</source>
-        <target>Kornijski</target>
-      </trans-unit>
-      <trans-unit id="Corsican">
-        <source>Corsican</source>
-        <target>Korsykański</target>
-      </trans-unit>
-      <trans-unit id="Cree">
-        <source>Cree</source>
-        <target>Kri</target>
-      </trans-unit>
-      <trans-unit id="Czech Sign Language">
-        <source>Czech Sign Language</source>
-        <target>Czeski Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Chinese Sign Language">
-        <source>Chinese Sign Language</source>
-        <target>Chiński Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Welsh">
-        <source>Welsh</source>
-        <target>Walijski</target>
-      </trans-unit>
-      <trans-unit id="Danish">
-        <source>Danish</source>
-        <target>Duński</target>
-      </trans-unit>
-      <trans-unit id="German">
-        <source>German</source>
-        <target>Niemiecki</target>
-      </trans-unit>
-      <trans-unit id="Dhivehi">
-        <source>Dhivehi</source>
-      </trans-unit>
-      <trans-unit id="Danish Sign Language">
-        <source>Danish Sign Language</source>
-        <target>Duński Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Dzongkha">
-        <source>Dzongkha</source>
-        <target>Dzongkha</target>
-      </trans-unit>
-      <trans-unit id="Modern Greek (1453-)">
-        <source>Modern Greek (1453-)</source>
-        <target>Nowogrecki (1453-)</target>
-      </trans-unit>
-      <trans-unit id="English">
-        <source>English</source>
-        <target>Angielski</target>
-      </trans-unit>
-      <trans-unit id="Esperanto">
-        <source>Esperanto</source>
-      </trans-unit>
-      <trans-unit id="Estonian">
-        <source>Estonian</source>
-        <target>Estoński</target>
-      </trans-unit>
-      <trans-unit id="Basque">
-        <source>Basque</source>
-        <target>Baskijski</target>
-      </trans-unit>
-      <trans-unit id="Ewe">
-        <source>Ewe</source>
-        <target>Ewe</target>
-      </trans-unit>
-      <trans-unit id="Faroese">
-        <source>Faroese</source>
-      </trans-unit>
-      <trans-unit id="Persian">
-        <source>Persian</source>
-        <target>Perski</target>
-      </trans-unit>
-      <trans-unit id="Fijian">
-        <source>Fijian</source>
-        <target>Fidżyjski</target>
-      </trans-unit>
-      <trans-unit id="Finnish">
-        <source>Finnish</source>
-        <target>Fiński</target>
-      </trans-unit>
-      <trans-unit id="French">
-        <source>French</source>
-        <target>Francuski</target>
-      </trans-unit>
-      <trans-unit id="Western Frisian">
-        <source>Western Frisian</source>
-      </trans-unit>
-      <trans-unit id="French Sign Language">
-        <source>French Sign Language</source>
-        <target>Francuski Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Fulah">
-        <source>Fulah</source>
-        <target>Ful</target>
-      </trans-unit>
-      <trans-unit id="Scottish Gaelic">
-        <source>Scottish Gaelic</source>
-      </trans-unit>
-      <trans-unit id="Irish">
-        <source>Irish</source>
-        <target>Irlandzki</target>
-      </trans-unit>
-      <trans-unit id="Galician">
-        <source>Galician</source>
-        <target>Galicyjski</target>
-      </trans-unit>
-      <trans-unit id="Manx">
-        <source>Manx</source>
-      </trans-unit>
-      <trans-unit id="Guarani">
-        <source>Guarani</source>
-      </trans-unit>
-      <trans-unit id="German Sign Language">
-        <source>German Sign Language</source>
-        <target>Niemiecki Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Gujarati">
-        <source>Gujarati</source>
-      </trans-unit>
-      <trans-unit id="Haitian">
-        <source>Haitian</source>
-      </trans-unit>
-      <trans-unit id="Hausa">
-        <source>Hausa</source>
-        <target>Hausa</target>
-      </trans-unit>
-      <trans-unit id="Serbo-Croatian">
-        <source>Serbo-Croatian</source>
-        <target>Serbsko-Chorwacki</target>
-      </trans-unit>
-      <trans-unit id="Hebrew">
-        <source>Hebrew</source>
-      </trans-unit>
-      <trans-unit id="Herero">
-        <source>Herero</source>
-      </trans-unit>
-      <trans-unit id="Hindi">
-        <source>Hindi</source>
-        <target>Hindi</target>
-      </trans-unit>
-      <trans-unit id="Hiri Motu">
-        <source>Hiri Motu</source>
-      </trans-unit>
-      <trans-unit id="Croatian">
-        <source>Croatian</source>
-        <target>Chorwacki</target>
-      </trans-unit>
-      <trans-unit id="Hungarian">
-        <source>Hungarian</source>
-        <target>Węgierski</target>
-      </trans-unit>
-      <trans-unit id="Armenian">
-        <source>Armenian</source>
-        <target>Ormański</target>
-      </trans-unit>
-      <trans-unit id="Igbo">
-        <source>Igbo</source>
-        <target>Igbo</target>
-      </trans-unit>
-      <trans-unit id="Sichuan Yi">
-        <source>Sichuan Yi</source>
-      </trans-unit>
-      <trans-unit id="Inuktitut">
-        <source>Inuktitut</source>
-      </trans-unit>
-      <trans-unit id="Indonesian">
-        <source>Indonesian</source>
-        <target>Indonezyjski</target>
-      </trans-unit>
-      <trans-unit id="Inupiaq">
-        <source>Inupiaq</source>
-      </trans-unit>
-      <trans-unit id="Icelandic">
-        <source>Icelandic</source>
-        <target>Islandzki</target>
-      </trans-unit>
-      <trans-unit id="Italian">
-        <source>Italian</source>
-        <target>Włoski</target>
-      </trans-unit>
-      <trans-unit id="Javanese">
-        <source>Javanese</source>
-        <target>Jawajski</target>
-      </trans-unit>
-      <trans-unit id="Lojban">
-        <source>Lojban</source>
-      </trans-unit>
-      <trans-unit id="Japanese">
-        <source>Japanese</source>
-        <target>Japoński</target>
-      </trans-unit>
-      <trans-unit id="Japanese Sign Language">
-        <source>Japanese Sign Language</source>
-        <target>Japoński Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Kalaallisut">
-        <source>Kalaallisut</source>
-      </trans-unit>
-      <trans-unit id="Kannada">
-        <source>Kannada</source>
-      </trans-unit>
-      <trans-unit id="Kashmiri">
-        <source>Kashmiri</source>
-      </trans-unit>
-      <trans-unit id="Georgian">
-        <source>Georgian</source>
-      </trans-unit>
-      <trans-unit id="Kanuri">
-        <source>Kanuri</source>
-      </trans-unit>
-      <trans-unit id="Kazakh">
-        <source>Kazakh</source>
-      </trans-unit>
-      <trans-unit id="Khmer">
-        <source>Khmer</source>
-      </trans-unit>
-      <trans-unit id="Kikuyu">
-        <source>Kikuyu</source>
-      </trans-unit>
-      <trans-unit id="Kinyarwanda">
-        <source>Kinyarwanda</source>
-      </trans-unit>
-      <trans-unit id="Kirghiz">
-        <source>Kirghiz</source>
-      </trans-unit>
-      <trans-unit id="Komi">
-        <source>Komi</source>
-        <target>Komi</target>
-      </trans-unit>
-      <trans-unit id="Kongo">
-        <source>Kongo</source>
-        <target>Kongo</target>
-      </trans-unit>
-      <trans-unit id="Korean">
-        <source>Korean</source>
-        <target>Koreański</target>
-      </trans-unit>
-      <trans-unit id="Kuanyama">
-        <source>Kuanyama</source>
-      </trans-unit>
-      <trans-unit id="Kurdish">
-        <source>Kurdish</source>
-        <target>Kurdyjski</target>
-      </trans-unit>
-      <trans-unit id="Lao">
-        <source>Lao</source>
-        <target>Laotański</target>
-      </trans-unit>
-      <trans-unit id="Latvian">
-        <source>Latvian</source>
-        <target>Łotewski</target>
-      </trans-unit>
-      <trans-unit id="Limburgan">
-        <source>Limburgan</source>
-      </trans-unit>
-      <trans-unit id="Lingala">
-        <source>Lingala</source>
-      </trans-unit>
-      <trans-unit id="Lithuanian">
-        <source>Lithuanian</source>
-        <target>Litewski</target>
-      </trans-unit>
-      <trans-unit id="Luxembourgish">
-        <source>Luxembourgish</source>
-        <target>Luksemburski</target>
-      </trans-unit>
-      <trans-unit id="Luba-Katanga">
-        <source>Luba-Katanga</source>
-      </trans-unit>
-      <trans-unit id="Ganda">
-        <source>Ganda</source>
-      </trans-unit>
-      <trans-unit id="Marshallese">
-        <source>Marshallese</source>
-      </trans-unit>
-      <trans-unit id="Malayalam">
-        <source>Malayalam</source>
-      </trans-unit>
-      <trans-unit id="Marathi">
-        <source>Marathi</source>
-      </trans-unit>
-      <trans-unit id="Macedonian">
-        <source>Macedonian</source>
-      </trans-unit>
-      <trans-unit id="Malagasy">
-        <source>Malagasy</source>
-      </trans-unit>
-      <trans-unit id="Maltese">
-        <source>Maltese</source>
-      </trans-unit>
-      <trans-unit id="Mongolian">
-        <source>Mongolian</source>
-      </trans-unit>
-      <trans-unit id="Maori">
-        <source>Maori</source>
-      </trans-unit>
-      <trans-unit id="Malay (macrolanguage)">
-        <source>Malay (macrolanguage)</source>
-      </trans-unit>
-      <trans-unit id="Burmese">
-        <source>Burmese</source>
-      </trans-unit>
-      <trans-unit id="Nauru">
-        <source>Nauru</source>
-        <target>Naurański</target>
-      </trans-unit>
-      <trans-unit id="Navajo">
-        <source>Navajo</source>
-      </trans-unit>
-      <trans-unit id="South Ndebele">
-        <source>South Ndebele</source>
-      </trans-unit>
-      <trans-unit id="North Ndebele">
-        <source>North Ndebele</source>
-      </trans-unit>
-      <trans-unit id="Ndonga">
-        <source>Ndonga</source>
-      </trans-unit>
-      <trans-unit id="Nepali (macrolanguage)">
-        <source>Nepali (macrolanguage)</source>
-      </trans-unit>
-      <trans-unit id="Dutch">
-        <source>Dutch</source>
-        <target>Holenderski</target>
-      </trans-unit>
-      <trans-unit id="Norwegian Nynorsk">
-        <source>Norwegian Nynorsk</source>
-        <target>Norweski Nynorsk</target>
-      </trans-unit>
-      <trans-unit id="Norwegian Bokmål">
-        <source>Norwegian Bokmål</source>
-        <target>Norweski Bokmål</target>
-      </trans-unit>
-      <trans-unit id="Norwegian">
-        <source>Norwegian</source>
-        <target>Norweski</target>
-      </trans-unit>
-      <trans-unit id="Nyanja">
-        <source>Nyanja</source>
-      </trans-unit>
-      <trans-unit id="Occitan">
-        <source>Occitan</source>
-      </trans-unit>
-      <trans-unit id="Ojibwa">
-        <source>Ojibwa</source>
-      </trans-unit>
-      <trans-unit id="Oriya (macrolanguage)">
-        <source>Oriya (macrolanguage)</source>
-      </trans-unit>
-      <trans-unit id="Oromo">
-        <source>Oromo</source>
-        <target>Oromo</target>
-      </trans-unit>
-      <trans-unit id="Ossetian">
-        <source>Ossetian</source>
-      </trans-unit>
-      <trans-unit id="Panjabi">
-        <source>Panjabi</source>
-      </trans-unit>
-      <trans-unit id="Pakistan Sign Language">
-        <source>Pakistan Sign Language</source>
-      </trans-unit>
-      <trans-unit id="Polish">
-        <source>Polish</source>
-        <target>Polski</target>
-      </trans-unit>
-      <trans-unit id="Portuguese">
-        <source>Portuguese</source>
-        <target>Portugalski</target>
-      </trans-unit>
-      <trans-unit id="Pushto">
-        <source>Pushto</source>
-        <target>Paszto</target>
-      </trans-unit>
-      <trans-unit id="Quechua">
-        <source>Quechua</source>
-      </trans-unit>
-      <trans-unit id="Romansh">
-        <source>Romansh</source>
-        <target>Romansz</target>
-      </trans-unit>
-      <trans-unit id="Romanian">
-        <source>Romanian</source>
-        <target>Rumuński</target>
-      </trans-unit>
-      <trans-unit id="Russian Sign Language">
-        <source>Russian Sign Language</source>
-        <target>Rosyjski Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Rundi">
-        <source>Rundi</source>
-        <target>Rundi</target>
-      </trans-unit>
-      <trans-unit id="Russian">
-        <source>Russian</source>
-        <target>Rosyjski</target>
-      </trans-unit>
-      <trans-unit id="Sango">
-        <source>Sango</source>
-        <target>Sango</target>
-      </trans-unit>
-      <trans-unit id="Saudi Arabian Sign Language">
-        <source>Saudi Arabian Sign Language</source>
-      </trans-unit>
-      <trans-unit id="South African Sign Language">
-        <source>South African Sign Language</source>
-      </trans-unit>
-      <trans-unit id="Sinhala">
-        <source>Sinhala</source>
-      </trans-unit>
-      <trans-unit id="Slovak">
-        <source>Slovak</source>
-        <target>Słowacki</target>
-      </trans-unit>
-      <trans-unit id="Slovenian">
-        <source>Slovenian</source>
-        <target>Słoweński</target>
-      </trans-unit>
-      <trans-unit id="Northern Sami">
-        <source>Northern Sami</source>
-      </trans-unit>
-      <trans-unit id="Samoan">
-        <source>Samoan</source>
-        <target>Samoański</target>
-      </trans-unit>
-      <trans-unit id="Shona">
-        <source>Shona</source>
-        <target>Shona</target>
-      </trans-unit>
-      <trans-unit id="Sindhi">
-        <source>Sindhi</source>
-        <target>Sindhi</target>
-      </trans-unit>
-      <trans-unit id="Somali">
-        <source>Somali</source>
-        <target>Somalijski</target>
-      </trans-unit>
-      <trans-unit id="Southern Sotho">
-        <source>Southern Sotho</source>
-      </trans-unit>
-      <trans-unit id="Spanish">
-        <source>Spanish</source>
-        <target>Hiszpański</target>
-      </trans-unit>
-      <trans-unit id="Albanian">
-        <source>Albanian</source>
-      </trans-unit>
-      <trans-unit id="Sardinian">
-        <source>Sardinian</source>
-      </trans-unit>
-      <trans-unit id="Serbian">
-        <source>Serbian</source>
-        <target>Serbski</target>
-      </trans-unit>
-      <trans-unit id="Swati">
-        <source>Swati</source>
-      </trans-unit>
-      <trans-unit id="Sundanese">
-        <source>Sundanese</source>
-      </trans-unit>
-      <trans-unit id="Swahili (macrolanguage)">
-        <source>Swahili (macrolanguage)</source>
-      </trans-unit>
-      <trans-unit id="Swedish">
-        <source>Swedish</source>
-        <target>Szwedzki</target>
-      </trans-unit>
-      <trans-unit id="Swedish Sign Language">
-        <source>Swedish Sign Language</source>
-        <target>Szwedzki Język Migowy</target>
-      </trans-unit>
-      <trans-unit id="Tahitian">
-        <source>Tahitian</source>
-      </trans-unit>
-      <trans-unit id="Tamil">
-        <source>Tamil</source>
-        <target>Tamilski</target>
-      </trans-unit>
-      <trans-unit id="Tatar">
-        <source>Tatar</source>
-        <target>Tatarski</target>
-      </trans-unit>
-      <trans-unit id="Telugu">
-        <source>Telugu</source>
-        <target>Telugu</target>
-      </trans-unit>
-      <trans-unit id="Tajik">
-        <source>Tajik</source>
-        <target>Tadżycki</target>
-      </trans-unit>
-      <trans-unit id="Tagalog">
-        <source>Tagalog</source>
-        <target>Tagalski</target>
-      </trans-unit>
-      <trans-unit id="Thai">
-        <source>Thai</source>
-        <target>Tajski</target>
-      </trans-unit>
-      <trans-unit id="Tigrinya">
-        <source>Tigrinya</source>
-      </trans-unit>
-      <trans-unit id="Klingon">
-        <source>Klingon</source>
-      </trans-unit>
-      <trans-unit id="Tonga (Tonga Islands)">
-        <source>Tonga (Tonga Islands)</source>
-      </trans-unit>
-      <trans-unit id="Tswana">
-        <source>Tswana</source>
-      </trans-unit>
-      <trans-unit id="Tsonga">
-        <source>Tsonga</source>
-      </trans-unit>
-      <trans-unit id="Turkmen">
-        <source>Turkmen</source>
-        <target>Turkmeński</target>
-      </trans-unit>
-      <trans-unit id="Turkish">
-        <source>Turkish</source>
-        <target>Turecki</target>
-      </trans-unit>
-      <trans-unit id="Twi">
-        <source>Twi</source>
-        <target>Twi</target>
-      </trans-unit>
-      <trans-unit id="Uighur">
-        <source>Uighur</source>
-      </trans-unit>
-      <trans-unit id="Ukrainian">
-        <source>Ukrainian</source>
-        <target>Ukraiński</target>
-      </trans-unit>
-      <trans-unit id="Urdu">
-        <source>Urdu</source>
-        <target>Urdu</target>
-      </trans-unit>
-      <trans-unit id="Uzbek">
-        <source>Uzbek</source>
-        <target>Uzbecki</target>
-      </trans-unit>
-      <trans-unit id="Venda">
-        <source>Venda</source>
-        <target>Venda</target>
-      </trans-unit>
-      <trans-unit id="Vietnamese">
-        <source>Vietnamese</source>
-        <target>Wietnamski</target>
-      </trans-unit>
-      <trans-unit id="Walloon">
-        <source>Walloon</source>
-        <target>Waloński</target>
-      </trans-unit>
-      <trans-unit id="Wolof">
-        <source>Wolof</source>
-        <target>Wolof</target>
-      </trans-unit>
-      <trans-unit id="Xhosa">
-        <source>Xhosa</source>
-        <target>Xhosa</target>
-      </trans-unit>
-      <trans-unit id="Yiddish">
-        <source>Yiddish</source>
-        <target>Jidysz</target>
-      </trans-unit>
-      <trans-unit id="Yoruba">
-        <source>Yoruba</source>
-        <target>Joruba</target>
-      </trans-unit>
-      <trans-unit id="Zhuang">
-        <source>Zhuang</source>
-        <target>Zhuang</target>
-      </trans-unit>
-      <trans-unit id="Chinese">
-        <source>Chinese</source>
-        <target>Chiński</target>
-      </trans-unit>
-      <trans-unit id="Zulu">
-        <source>Zulu</source>
-        <target>Zulu</target>
-      </trans-unit>
-    </body>
-  </file></xliff>
\ No newline at end of file
index f81c084d4fe90387b72029ca62a793ca16d32e9a..4e3a12ad7a701d62da9b05cc3a3c4c73f52291dd 100644 (file)
@@ -81,7 +81,7 @@
       </trans-unit>
       <trans-unit id="Subtitles">
         <source>Subtitles</source>
-        <target>ترجÙ\85Ø©</target>
+        <target>اÙ\84ترجÙ\85ات</target>
       </trans-unit>
       <trans-unit id="subtitles off">
         <source>subtitles off</source>
diff --git a/client/src/locale/target/player_it_IT.json b/client/src/locale/target/player_it_IT.json
new file mode 100644 (file)
index 0000000..add193b
--- /dev/null
@@ -0,0 +1 @@
+{"Audio Player":"Riproduttore Audio","Video Player":"Riproduttore Video","Play":"Play","Pause":"Pausa","Replay":"Replay","Current Time":"Posizione attuale","Duration":"Durata","Remaining Time":"Tempo rimanente","Stream Type":"Tipo dello Streaming","LIVE":"LIVE","Loaded":"Caricato","Progress":"Stato","Progress Bar":"Barra di progresso","progress bar timing: currentTime={1} duration={2}":"{1} di {2}","Fullscreen":"Schermo intero","Non-Fullscreen":"Chiudi schermo intero","Mute":"Muto","Unmute":"Audio ","Playback Rate":"Velocità di riproduzione","Subtitles":"Sottotitoli","subtitles off":"Senza sottotitoli","Captions":"Sottotitoli per non udenti","captions off":"Senza sottotitoli per non udenti","Chapters":"Capitoli","Descriptions":"Descrizioni","descriptions off":"Descrizioni disattivate","Audio Track":"Traccia Audio","Volume Level":"Volume","You aborted the media playback":"La riproduzione del filmato è stata interrotta","A network error caused the media download to fail part-way.":"Il download del filmato è stato interrotto a causa di un problema rete.","The media could not be loaded, either because the server or network failed or because the format is not supported.":"Il filmato non può essere caricato a causa di un errore nel server o nella rete o perché il formato non viene supportato.","The media playback was aborted due to a corruption problem or because the media used features your browser did not support.":"La riproduzione del filmato è stata interrotta a causa di un file danneggiato o per l’utilizzo di impostazioni non supportate dal browser.","No compatible source was found for this media.":"Non ci sono fonti compatibili per questo filmato.","The media is encrypted and we do not have the keys to decrypt it.":"Il filmato è crittato e non disponiamo delle chiavi per decrittarlo","Play Video":"Riproduci Video","Close":"Chiudi","Close Modal Dialog":"Chiudi finestra di dialogo","Modal Window":"Finestra di dialogo","This is a modal window":"Questa è una finestra di dialogo","This modal can be closed by pressing the Escape key or activating the close button.":"Questa finestra di dialogo può essere chiusa premendo Esc o cliccando sul pulsante chiudi.",", opens captions settings dialog":", apri la finestra delle impostazioni delle didascalie",", opens subtitles settings dialog":", apri la finestra delle impostazioni dei sottotitoli",", opens descriptions settings dialog":", apri la finestra delle impostazioni delle descrizioni",", selected":", selezionati","captions settings":"impostazioni delle didascalie","subtitles settings":"impostazioni dei sottotitoli","descriptions settings":"impostazioni delle descrizioni","Text":"Testo","White":"Bianco","Black":"Nero","Red":"Rosso","Green":"Verde","Blue":"Blu","Yellow":"Giallo","Magenta":"Magenta","Cyan":"Ciano","Background":"Sfondo","Window":"Finestra","Transparent":"Trasparente","Semi-Transparent":"Semi-Trasparente","Opaque":"Opaco","Font Size":"Dimensione del Testo","Text Edge Style":"Stile dei Bordi del Testo","None":"Nessuno","Raised":"In Rilievo","Depressed":"Incavato","Uniform":"Uniforme","Dropshadow":"Ombreggiatura","Font Family":"Stile del Testo","Proportional Sans-Serif":"Senza Grazie Proporzionale","Monospace Sans-Serif":"Senza Grazie Monospazio","Proportional Serif":"Con Grazie Proporzionale","Monospace Serif":"Con Grazie Monospazio","Casual":"Casuale","Script":"Codice","Small Caps":"Maiuscoletto","Reset":"Ripristina","restore all settings to the default values":"ripristina tutte le impostazioni ai valori predefiniti","Done":"Fatto","Caption Settings Dialog":"Finestra delle Impostazioni dei Sottotitoli","Beginning of dialog window. Escape will cancel and close the window.":"Apertura della finestra di dialogo. Premendo ESC si annullerà e si chiuderà la finestra.","End of dialog window.":"Chiusura della finestra di dialogo.","{1} is loading.":"{1} è in caricamento.","Quality":"Qualità","Auto":"Auto","Speed":"Velocità","Subtitles/CC":"Sottotitoli/CC","peers":"nodi","Go to the video page":"Vai alla pagina del video","Settings":"Impostazioni","Uses P2P, others may know you are watching this video.":"Usa P2P, altri potrebbero sapere che stai guardando questo video.","Copy the video URL":"Copia l'URL del video","Copy the video URL at the current time":"Copia l'URL del video della posizione corrente","Copy embed code":"Copia il codice per incorporare"}
\ No newline at end of file
index 5b689f2a24e28cd942c026a3df50659aa18e7106..45bc76a5566d3c2ca0f745b28ac282061fecaed1 100644 (file)
         <source>Speed</source>
         <target>Snelheid</target>
       </trans-unit>
+      <trans-unit id="Subtitles/CC">
+        <source>Subtitles/CC</source>
+        <target>Ondertiteling/CC</target>
+      </trans-unit>
       <trans-unit id="peers">
         <source>peers</source>
         <target>peers</target>
diff --git a/client/src/locale/target/player_pl_PL.json b/client/src/locale/target/player_pl_PL.json
new file mode 100644 (file)
index 0000000..2178a13
--- /dev/null
@@ -0,0 +1 @@
+{"Audio Player":"Odtwarzacz audio","Video Player":"Odtwarzacz wideo","Play":"Odtwórz","Pause":"Wstrzymaj","Replay":"Powtórz","Current Time":"Obecny czas","Duration":"Czas trwania","Remaining Time":"Pozostały czas","Stream Type":"Rodzaj strumienia","LIVE":"NA ŻYWO","Loaded":"Załadowano","Progress":"Postęp","Progress Bar":"Pasek postępu","progress bar timing: currentTime={1} duration={2}":"{1} z {2}","Fullscreen":"Pełny ekran","Non-Fullscreen":"Bez pełnego ekranu","Mute":"Wycisz","Unmute":"Cofnij wyciszenie","Playback Rate":"Szybkość odtwarzania","Subtitles":"Napisy","subtitles off":"napisy są wyłączone","Captions":"CC","captions off":"CC są wyłączone","Chapters":"Rozdziały","Descriptions":"Opisy","descriptions off":"opisy są wyłączone","Audio Track":"Ścieżka dźwiękowa","Volume Level":"Poziom głośności","You aborted the media playback":"Przerwałeś odtwarzanie zawartości mulimedialnej","A network error caused the media download to fail part-way.":"Błąd sieci spowodował, że zawartość multimedialna została pobrana tylko częściowo.","The media could not be loaded, either because the server or network failed or because the format is not supported.":"Nie udało się załadować zawartości multimedialnej z powodu błędy sieci lub serwera lub ponieważ format nie jest obsługiwany.","The media playback was aborted due to a corruption problem or because the media used features your browser did not support.":"Odtwarzanie zostało przerwane ze względu na uszkodzenie pliku lub przez brak wsparcia funkcji multimediów przez Twoją przeglądarkę.","No compatible source was found for this media.":"Nie znaleziono kompatybilnego źródła dla tego media.","The media is encrypted and we do not have the keys to decrypt it.":"Zawartość multimedialna jest zaszyfrowana, a klucz do jej odszyfrowania jest nieznany.","Play Video":"Odtwórz film","Close":"Zamknij","Close Modal Dialog":"Zamknij okno modalne","Modal Window":"Okno modalne","This is a modal window":"To jest okno modalne","This modal can be closed by pressing the Escape key or activating the close button.":"To okno może zostać zamknięte klawiszem Escape lub przyciskiem zamykania.",", opens captions settings dialog":", otwiera okno ustawień CC",", opens subtitles settings dialog":", otwiera okno ustawień napisów",", opens descriptions settings dialog":", otwiera okno ustawień opisów",", selected":", zaznaczone","captions settings":"ustawienia CC","subtitles settings":"ustawienia napisów","descriptions settings":"ustawienia opisów","Text":"Tekst","White":"Biały","Black":"Czarny","Red":"Czerwony","Green":"Zielony","Blue":"Niebieski","Yellow":"Żółty","Magenta":"Magenta","Cyan":"Cyjanowy","Background":"Tło","Window":"Okno","Transparent":"Przezroczyste","Semi-Transparent":"Półprzezroczyste","Opaque":"Widoczne","Font Size":"Rozmiar czcionki","Text Edge Style":"Styl krawędzi tekstu","None":"Brak","Raised":"Wypukłe","Depressed":"Wgłębione","Uniform":"Jednolity","Dropshadow":"Cień","Font Family":"Rodzina czcionek","Proportional Sans-Serif":"Proporcjonalne bezszeryfowe","Monospace Sans-Serif":"Bezszeryfowe o stałej szerokości","Proportional Serif":"Proporcjonalne szeryfowe","Monospace Serif":"Szeryfowe o stałej szerokości","Casual":"Ozdobne","Script":"Pismo odręczne","Small Caps":"Kapitaliki","Reset":"Resetuj","restore all settings to the default values":"przywróć wszystkie ustawienia do wartości domyślnych","Done":"Gotowe","Caption Settings Dialog":"Okno ustawień napisów","Beginning of dialog window. Escape will cancel and close the window.":"Początek okna dialogowego. Przycisk Escape anuluje i zamknie okno.","End of dialog window.":"Koniec okna dialogowego.","{1} is loading.":"{1} ładuje się.","Quality":"Jakość","Auto":"Automatyczna","Speed":"Prędkość","Subtitles/CC":"Napisy/CC","peers":"peers","Go to the video page":"Przejdź na stronę filmu","Settings":"Ustawienia","Uses P2P, others may know you are watching this video.":"Korzysta z P2P, inni mogą dowiedzieć się, że oglądasz ten film.","Copy the video URL":"Skopiuj adres URL filmu","Copy the video URL at the current time":"Skopiuj adres URL filmu z obecnym czasem","Copy embed code":"Skopiuj kod do osadzenia"}
\ No newline at end of file
diff --git a/client/src/locale/target/player_pl_PL.xml b/client/src/locale/target/player_pl_PL.xml
deleted file mode 100644 (file)
index 2affa59..0000000
+++ /dev/null
@@ -1,383 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--XLIFF document generated by Zanata. Visit http://zanata.org for more infomation.-->
-<xliff xmlns="urn:oasis:names:tc:xliff:document:1.1" xmlns:xyz="urn:appInfo:Items" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.1 http://www.oasis-open.org/committees/xliff/documents/xliff-core-1.1.xsd" version="1.1">
-  <file source-language="en-US" datatype="plaintext" original="" target-language="pl-PL">
-    <body>
-      <trans-unit id="Audio Player">
-        <source>Audio Player</source>
-        <target>Odtwarzacz audio</target>
-      </trans-unit>
-      <trans-unit id="Video Player">
-        <source>Video Player</source>
-        <target>Odtwarzacz wideo</target>
-      </trans-unit>
-      <trans-unit id="Play">
-        <source>Play</source>
-        <target>Odtwórz</target>
-      </trans-unit>
-      <trans-unit id="Pause">
-        <source>Pause</source>
-        <target>Wstrzymaj</target>
-      </trans-unit>
-      <trans-unit id="Replay">
-        <source>Replay</source>
-        <target>Powtórz</target>
-      </trans-unit>
-      <trans-unit id="Current Time">
-        <source>Current Time</source>
-        <target>Obecny czas</target>
-      </trans-unit>
-      <trans-unit id="Duration">
-        <source>Duration</source>
-        <target>Czas trwania</target>
-      </trans-unit>
-      <trans-unit id="Remaining Time">
-        <source>Remaining Time</source>
-        <target>Pozostały czas</target>
-      </trans-unit>
-      <trans-unit id="Stream Type">
-        <source>Stream Type</source>
-        <target>Rodzaj strumienia</target>
-      </trans-unit>
-      <trans-unit id="LIVE">
-        <source>LIVE</source>
-        <target>NA ŻYWO</target>
-      </trans-unit>
-      <trans-unit id="Loaded">
-        <source>Loaded</source>
-        <target>Załadowano</target>
-      </trans-unit>
-      <trans-unit id="Progress">
-        <source>Progress</source>
-        <target>Postęp</target>
-      </trans-unit>
-      <trans-unit id="Progress Bar">
-        <source>Progress Bar</source>
-        <target>Pasek postępu</target>
-      </trans-unit>
-      <trans-unit id="progress bar timing: currentTime={1} duration={2}">
-        <source>{1} of {2}</source>
-        <target>{1} z {2}</target>
-      </trans-unit>
-      <trans-unit id="Fullscreen">
-        <source>Fullscreen</source>
-        <target>Pełny ekran</target>
-      </trans-unit>
-      <trans-unit id="Non-Fullscreen">
-        <source>Non-Fullscreen</source>
-        <target>Bez pełnego ekranu</target>
-      </trans-unit>
-      <trans-unit id="Mute">
-        <source>Mute</source>
-        <target>Wycisz</target>
-      </trans-unit>
-      <trans-unit id="Unmute">
-        <source>Unmute</source>
-        <target>Cofnij wyciszenie</target>
-      </trans-unit>
-      <trans-unit id="Playback Rate">
-        <source>Playback Rate</source>
-        <target>Szybkość odtwarzania</target>
-      </trans-unit>
-      <trans-unit id="Subtitles">
-        <source>Subtitles</source>
-        <target>Napisy</target>
-      </trans-unit>
-      <trans-unit id="subtitles off">
-        <source>subtitles off</source>
-        <target>napisy są wyłączone</target>
-      </trans-unit>
-      <trans-unit id="Captions">
-        <source>Captions</source>
-        <target>CC</target>
-      </trans-unit>
-      <trans-unit id="captions off">
-        <source>captions off</source>
-        <target>CC są wyłączone</target>
-      </trans-unit>
-      <trans-unit id="Chapters">
-        <source>Chapters</source>
-        <target>Rozdziały</target>
-      </trans-unit>
-      <trans-unit id="Descriptions">
-        <source>Descriptions</source>
-        <target>Opisy</target>
-      </trans-unit>
-      <trans-unit id="descriptions off">
-        <source>descriptions off</source>
-        <target>opisy są wyłączone</target>
-      </trans-unit>
-      <trans-unit id="Audio Track">
-        <source>Audio Track</source>
-        <target>Ścieżka dźwiękowa</target>
-      </trans-unit>
-      <trans-unit id="Volume Level">
-        <source>Volume Level</source>
-        <target>Poziom głośności</target>
-      </trans-unit>
-      <trans-unit id="You aborted the media playback">
-        <source>You aborted the media playback</source>
-        <target>Przerwałeś odtwarzanie zawartości mulimedialnej</target>
-      </trans-unit>
-      <trans-unit id="A network error caused the media download to fail part-way.">
-        <source>A network error caused the media download to fail part-way.</source>
-        <target>Błąd sieci spowodował, że zawartość multimedialna została pobrana tylko częściowo.</target>
-      </trans-unit>
-      <trans-unit id="The media could not be loaded, either because the server or network failed or because the format is not supported.">
-        <source>The media could not be loaded, either because the server or network failed or because the format is not supported.</source>
-        <target>Nie udało się załadować zawartości multimedialnej z powodu błędy sieci lub serwera lub ponieważ format nie jest obsługiwany.</target>
-      </trans-unit>
-      <trans-unit id="The media playback was aborted due to a corruption problem or because the media used features your browser did not support.">
-        <source>The media playback was aborted due to a corruption problem or because the media used features your browser did not support.</source>
-        <target>Odtwarzanie zostało przerwane ze względu na uszkodzenie pliku lub przez brak wsparcia funkcji multimediów przez Twoją przeglądarkę.</target>
-      </trans-unit>
-      <trans-unit id="No compatible source was found for this media.">
-        <source>No compatible source was found for this media.</source>
-        <target>Nie znaleziono kompatybilnego źródła dla tego media.</target>
-      </trans-unit>
-      <trans-unit id="The media is encrypted and we do not have the keys to decrypt it.">
-        <source>The media is encrypted and we do not have the keys to decrypt it.</source>
-        <target>Zawartość multimedialna jest zaszyfrowana, a klucz do jej odszyfrowania jest nieznany.</target>
-      </trans-unit>
-      <trans-unit id="Play Video">
-        <source>Play Video</source>
-        <target>Odtwórz film</target>
-      </trans-unit>
-      <trans-unit id="Close">
-        <source>Close</source>
-        <target>Zamknij</target>
-      </trans-unit>
-      <trans-unit id="Close Modal Dialog">
-        <source>Close Modal Dialog</source>
-        <target>Zamknij okno modalne</target>
-      </trans-unit>
-      <trans-unit id="Modal Window">
-        <source>Modal Window</source>
-        <target>Okno modalne</target>
-      </trans-unit>
-      <trans-unit id="This is a modal window">
-        <source>This is a modal window</source>
-        <target>To jest okno modalne</target>
-      </trans-unit>
-      <trans-unit id="This modal can be closed by pressing the Escape key or activating the close button.">
-        <source>This modal can be closed by pressing the Escape key or activating the close button.</source>
-        <target>To okno może zostać zamknięte klawiszem Escape lub przyciskiem zamykania.</target>
-      </trans-unit>
-      <trans-unit id=", opens captions settings dialog">
-        <source>, opens captions settings dialog</source>
-        <target>, otwiera okno ustawień CC</target>
-      </trans-unit>
-      <trans-unit id=", opens subtitles settings dialog">
-        <source>, opens subtitles settings dialog</source>
-        <target>, otwiera okno ustawień napisów</target>
-      </trans-unit>
-      <trans-unit id=", opens descriptions settings dialog">
-        <source>, opens descriptions settings dialog</source>
-        <target>, otwiera okno ustawień opisów</target>
-      </trans-unit>
-      <trans-unit id=", selected">
-        <source>, selected</source>
-        <target>, zaznaczone</target>
-      </trans-unit>
-      <trans-unit id="captions settings">
-        <source>captions settings</source>
-        <target>ustawienia CC</target>
-      </trans-unit>
-      <trans-unit id="subtitles settings">
-        <source>subititles settings</source>
-        <target>ustawienia napisów</target>
-      </trans-unit>
-      <trans-unit id="descriptions settings">
-        <source>descriptions settings</source>
-        <target>ustawienia opisów</target>
-      </trans-unit>
-      <trans-unit id="Text">
-        <source>Text</source>
-        <target>Tekst</target>
-      </trans-unit>
-      <trans-unit id="White">
-        <source>White</source>
-        <target>Biały</target>
-      </trans-unit>
-      <trans-unit id="Black">
-        <source>Black</source>
-        <target>Czarny</target>
-      </trans-unit>
-      <trans-unit id="Red">
-        <source>Red</source>
-        <target>Czerwony</target>
-      </trans-unit>
-      <trans-unit id="Green">
-        <source>Green</source>
-        <target>Zielony</target>
-      </trans-unit>
-      <trans-unit id="Blue">
-        <source>Blue</source>
-        <target>Niebieski</target>
-      </trans-unit>
-      <trans-unit id="Yellow">
-        <source>Yellow</source>
-        <target>Żółty</target>
-      </trans-unit>
-      <trans-unit id="Magenta">
-        <source>Magenta</source>
-        <target>Magenta</target>
-      </trans-unit>
-      <trans-unit id="Cyan">
-        <source>Cyan</source>
-        <target>Cyjanowy</target>
-      </trans-unit>
-      <trans-unit id="Background">
-        <source>Background</source>
-        <target>Tło</target>
-      </trans-unit>
-      <trans-unit id="Window">
-        <source>Window</source>
-        <target>Okno</target>
-      </trans-unit>
-      <trans-unit id="Transparent">
-        <source>Transparent</source>
-        <target>Przezroczyste</target>
-      </trans-unit>
-      <trans-unit id="Semi-Transparent">
-        <source>Semi-Transparent</source>
-        <target>Półprzezroczyste</target>
-      </trans-unit>
-      <trans-unit id="Opaque">
-        <source>Opaque</source>
-        <target>Widoczne</target>
-      </trans-unit>
-      <trans-unit id="Font Size">
-        <source>Font Size</source>
-        <target>Rozmiar czcionki</target>
-      </trans-unit>
-      <trans-unit id="Text Edge Style">
-        <source>Text Edge Style</source>
-        <target>Styl krawędzi tekstu</target>
-      </trans-unit>
-      <trans-unit id="None">
-        <source>None</source>
-        <target>Brak</target>
-      </trans-unit>
-      <trans-unit id="Raised">
-        <source>Raised</source>
-        <target>Wypukłe</target>
-      </trans-unit>
-      <trans-unit id="Depressed">
-        <source>Depressed</source>
-        <target>Wgłębione</target>
-      </trans-unit>
-      <trans-unit id="Uniform">
-        <source>Uniform</source>
-        <target>Jednolity</target>
-      </trans-unit>
-      <trans-unit id="Dropshadow">
-        <source>Dropshadow</source>
-        <target>Cień</target>
-      </trans-unit>
-      <trans-unit id="Font Family">
-        <source>Font Family</source>
-        <target>Rodzina czcionek</target>
-      </trans-unit>
-      <trans-unit id="Proportional Sans-Serif">
-        <source>Proportional Sans-Serif</source>
-        <target>Proporcjonalne bezszeryfowe</target>
-      </trans-unit>
-      <trans-unit id="Monospace Sans-Serif">
-        <source>Monospace Sans-Serif</source>
-        <target>Bezszeryfowe o stałej szerokości</target>
-      </trans-unit>
-      <trans-unit id="Proportional Serif">
-        <source>Proportional Serif</source>
-        <target>Proporcjonalne szeryfowe</target>
-      </trans-unit>
-      <trans-unit id="Monospace Serif">
-        <source>Monospace Serif</source>
-        <target>Szeryfowe o stałej szerokości</target>
-      </trans-unit>
-      <trans-unit id="Casual">
-        <source>Casual</source>
-        <target>Ozdobne</target>
-      </trans-unit>
-      <trans-unit id="Script">
-        <source>Script</source>
-        <target>Pismo odręczne</target>
-      </trans-unit>
-      <trans-unit id="Small Caps">
-        <source>Small Caps</source>
-        <target>Kapitaliki</target>
-      </trans-unit>
-      <trans-unit id="Reset">
-        <source>Reset</source>
-        <target>Resetuj</target>
-      </trans-unit>
-      <trans-unit id="restore all settings to the default values">
-        <source>restore all settings to the default values</source>
-        <target>przywróć wszystkie ustawienia do wartości domyślnych</target>
-      </trans-unit>
-      <trans-unit id="Done">
-        <source>Done</source>
-        <target>Gotowe</target>
-      </trans-unit>
-      <trans-unit id="Caption Settings Dialog">
-        <source>Caption Settings Dialog</source>
-        <target>Okno ustawień napisów</target>
-      </trans-unit>
-      <trans-unit id="Beginning of dialog window. Escape will cancel and close the window.">
-        <source>Beginning of dialog window. Escape will cancel and close the window.</source>
-        <target>Początek okna dialogowego. Przycisk Escape anuluje i zamknie okno.</target>
-      </trans-unit>
-      <trans-unit id="End of dialog window.">
-        <source>End of dialog window.</source>
-        <target>Koniec okna dialogowego.</target>
-      </trans-unit>
-      <trans-unit id="{1} is loading.">
-        <source>{1} is loading.</source>
-        <target>{1} ładuje się.</target>
-      </trans-unit>
-      <trans-unit id="Quality">
-        <source>Quality</source>
-        <target>Jakość</target>
-      </trans-unit>
-      <trans-unit id="Auto">
-        <source>Auto</source>
-        <target>Automatyczna</target>
-      </trans-unit>
-      <trans-unit id="Speed">
-        <source>Speed</source>
-        <target>Prędkość</target>
-      </trans-unit>
-      <trans-unit id="Subtitles/CC">
-        <source>Subtitles/CC</source>
-        <target>Napisy/CC</target>
-      </trans-unit>
-      <trans-unit id="peers">
-        <source>peers</source>
-        <target>peers</target>
-      </trans-unit>
-      <trans-unit id="Go to the video page">
-        <source>Go to the video page</source>
-        <target>Przejdź na stronę filmu</target>
-      </trans-unit>
-      <trans-unit id="Settings">
-        <source>Settings</source>
-        <target>Ustawienia</target>
-      </trans-unit>
-      <trans-unit id="Uses P2P, others may know you are watching this video.">
-        <source>Uses P2P, others may know you are watching this video.</source>
-        <target>Korzysta z P2P, inni mogą dowiedzieć się, że oglądasz ten film.</target>
-      </trans-unit>
-      <trans-unit id="Copy the video URL">
-        <source>Copy the video URL</source>
-        <target>Skopiuj adres URL filmu</target>
-      </trans-unit>
-      <trans-unit id="Copy the video URL at the current time">
-        <source>Copy the video URL at the current time</source>
-        <target>Skopiuj adres URL filmu z obecnym czasem</target>
-      </trans-unit>
-      <trans-unit id="Copy embed code">
-        <source>Copy embed code</source>
-        <target>Skopiuj kod do osadzenia</target>
-      </trans-unit>
-    </body>
-  </file></xliff>
\ No newline at end of file
diff --git a/client/src/locale/target/player_ru_RU.json b/client/src/locale/target/player_ru_RU.json
new file mode 100644 (file)
index 0000000..e2d8d49
--- /dev/null
@@ -0,0 +1 @@
+{"Audio Player":"Аудиоплеер","Video Player":"Видеоплеер","Play":"Воспроизвести","Pause":"Пауза","Replay":"Воспроизвести  снова","Current Time":"Текущий момент","Duration":"Продолжительность","Remaining Time":"Оставшееся время","Stream Type":"Тип потока","LIVE":"Прямой эфир","Loaded":"Загружено","Progress":"Ход выполнения","Progress Bar":"Индикатор выполнения","progress bar timing: currentTime={1} duration={2}":"{1} из {2}","Fullscreen":"Полный экран","Non-Fullscreen":"Окно","Mute":"Без звука","Unmute":"Со звуком","Playback Rate":" Скорость воспроизведения","Subtitles":"Субтитры","subtitles off":"Без субтитров","Captions":"Сопроводительные надписи","captions off":"Без сопроводительных надписей","Chapters":"Главы","Descriptions":"Описание","descriptions off":"без описаний","Audio Track":"Аудиодорожка","Volume Level":"Громкость","You aborted the media playback":"Вы отменили воспроизведение медиафайла","A network error caused the media download to fail part-way.":"Ошибка сети стала причиной неудачного воспроизведения медиафайла","The media could not be loaded, either because the server or network failed or because the format is not supported.":"Медиафайл не может быть воспроизведен: ошибки сервера или сети; или не поддерживается формат медиафайла","The media playback was aborted due to a corruption problem or because the media used features your browser did not support.":"Воспроизведение отменено: файл испорчен или использует инструменты, которые ваш навигатор не поддерживает  ","No compatible source was found for this media.":"Не найдено совместимого источника для загружения медиафайла","The media is encrypted and we do not have the keys to decrypt it.":"Этот медиафайл зашифрован и у нас нет ключей для его расшифровки ","Play Video":"Воспроизвести видео","Close":"Закрыть","Close Modal Dialog":"Закрыть модальное диалоговое окно","Modal Window":"Модальное окно","This is a modal window":"Это модальное окно","This modal can be closed by pressing the Escape key or activating the close button.":"Это модальное окно можно закрыть нажав на кнопку Escape или на кнопку закрыть",", opens captions settings dialog":", открывает окно настроек сопроводительных надписей",", opens subtitles settings dialog":", открывает окно настроек субтитров",", opens descriptions settings dialog":", открывает окно настроек описаний",", selected":", выделено ","captions settings":"Настройки сопроводительных надписей","subtitles settings":"Настройки субтитров","descriptions settings":"Настройки описаний ","Text":"Текст","White":"Белый","Black":"Черный","Red":"Красный","Green":"Зеленый","Blue":"Синий","Yellow":"Желтый","Magenta":"Пурпурный ","Cyan":"Голубой","Background":"Фон","Window":"Окно","Transparent":"Прозрачный","Semi-Transparent":"Полупрозрачный ","Opaque":"Непрозрачный","Font Size":"Размер шрифта ","None":"Отсутствует","Uniform":"Однообразный","Dropshadow":"Падающая тень","Font Family":"Шрифт","Proportional Sans-Serif":"Пропорциональный без засечек","Monospace Sans-Serif":"Фиксированной ширины без засечек","Proportional Serif":"Пропорциональный с засечками","Monospace Serif":"Фиксированной ширины с засечками","Casual":"Свободный","Script":"рукописный шрифт","Small Caps":" Уменьшенные заглавные буквы","Reset":"Восстановить ","restore all settings to the default values":"Восстановить настройки по умолчанию ","Done":"Выполнено","Caption Settings Dialog":"Окно настроек сопроводительных надписей","Beginning of dialog window. Escape will cancel and close the window.":"Начало диалогового окна. Клавиша  Escape отменит действие и закроет окно","End of dialog window.":"Конец диалогового окна","{1} is loading.":"{1} в процессе загрузки","Quality":"Качество","Auto":"Авто","Speed":"Скорость","Subtitles/CC":"Субтитры","peers":"Партнер","Go to the video page":"Перейти на страницу с видео","Settings":"Настройки","Uses P2P, others may know you are watching this video.":"Использует P2P, другие могут знать, что вы просматриваете это видео.","Copy the video URL":"Скопировать URL видео","Copy the video URL at the current time":"Скопировать URL видео на текущем моменте ","Copy embed code":"Скопировать встроенный код"}
\ No newline at end of file
index 060658c801648b3a71b0766e2f43db81b7f577de..e013a910f1233da8dc070f93ea0865efda3efb74 100644 (file)
@@ -1 +1 @@
-{"Music":"Hudba","Films":"Filmy","Vehicles":"Auta","Art":"Umění","Sports":"Sport","Travels":"Cestování","Gaming":"Hry","People":"Lidé","Comedy":"Komedie","Entertainment":"Zábava","News & Politics":"Zprávy a politika","How To":"Jak na to","Education":"Výukové","Activism":"Aktivismus","Science & Technology":"Věda a technologie","Animals":"Zvířata","Kids":"Děti","Food":"Jídlo a vaření","Attribution":"Uveďte autora","Attribution - Share Alike":"Uveďte autora - Zachovejte licenci","Attribution - No Derivatives":"Uveďte autora - Nezpracovávejte","Attribution - Non Commercial":"Uveďte autora - Nešiřte dílo komerčně","Attribution - Non Commercial - Share Alike":"Uveďte autora - Nešiřte dílo komerčně - Zachovejte licenci","Attribution - Non Commercial - No Derivatives":"Uveďte autora - Nešiřte dílo komerčně - Nezpracovávejte","Public Domain Dedication":"Volné dílo","Public":"Veřejné","Unlisted":"Nezobrazeno","Private":"Soukromé","Published":"Publikované","Pending":"Čekající","Success":"Úspěch","Failed":"Neúspěch","Misc":"Různé","Unknown":"Neznámé","Afar":"Afarština","Abkhazian":"Abcházština","Afrikaans":"Afrikánština","Akan":"Akanština","Amharic":"Amharština","Arabic":"Arabština","Aragonese":"Aragonština","American Sign Language":"Americká znaková řeč","Assamese":"Ásámština","Avaric":"Avarština","Kotava":"Kotava","Aymara":"Ajmarština","Azerbaijani":"Ázerbájdžánština","Bashkir":"Baškirština","Bambara":"Bambarština","Belarusian":"Běloruština","Bengali":"Bengálština","British Sign Language":"Britská znaková řeč","Bislama":"Bislamština","Tibetan":"Tibetština","Bosnian":"Bosenština","Breton":"Bretonština","Bulgarian":"Bulharština","Brazilian Sign Language":"Brazilská znaková řeč","Catalan":"Katalánština","Czech":"Čeština","Chamorro":"Chamorro","Chechen":"Čečenština","Chuvash":"Čuvaština","Cornish":"Kornština","Corsican":"Korsičtina","Cree":"Kríjština","Czech Sign Language":"Česká znaková řeč","Chinese Sign Language":"Čínská znaková řeč","Welsh":"Velština","Danish":"Dánština","German":"Němčina","Dhivehi":"Maledivština","Danish Sign Language":"Dánská znaková řeč","Dzongkha":"Dzongkä","Modern Greek (1453-)":"Moderní řečtina","English":"Angličtina","Esperanto":"Esperanto","Estonian":"Estonština","Basque":"Baskičtina","Ewe":"Eveština","Faroese":"Faerština","Persian":"Perština","Fijian":"Fidžijština","Finnish":"Finština","French":"Francouzština","Western Frisian":"Západofríština","French Sign Language":"Francouzská znaková řeč","Fulah":"Fulbština","Scottish Gaelic":"Skotská gaelština","Irish":"Irština","Galician":"Galicijština","Manx":"Manština","Guarani":"Guaranština","German Sign Language":"Německá znaková řeč","Gujarati":"Gudžarátština","Haitian":"Haitská kreolština","Hausa":"Hauština","Serbo-Croatian":"Srcbochorvatšinta","Hebrew":"Hebrejština","Herero":"Herero","Hindi":"Hindština","Hiri Motu":"Hiri Motu","Croatian":"Chorvatština","Hungarian":"Maďarština","Armenian":"Arménština","Igbo":"Igboština","Sichuan Yi":"Nuosu","Inuktitut":"Inuktitutština","Indonesian":"Indonéština","Inupiaq":"Inupiaq","Icelandic":"Islandština","Italian":"Italština","Javanese":"Javánština","Lojban":"Lojban","Japanese":"Japonština","Japanese Sign Language":"Japonská znaková řeč","Kalaallisut":"Grónština","Kannada":"Kannadština","Kashmiri":"Kašmírština","Georgian":"Gruzínština","Kanuri":"Kanurijština","Kazakh":"Kazaština","Khmer":"Khmerština","Kikuyu":"Kikujština","Kinyarwanda":"Rwandština","Kirghiz":"Kyrgyzština","Komi":"Komi","Kongo":"Konžština","Korean":"Korejština","Kuanyama":"Kuanyama","Kurdish":"Kurdština","Lao":"Laoština","Latvian":"Lotyština","Limburgan":"Limburština","Lingala":"Ngalština","Lithuanian":"Litevština","Luxembourgish":"Lucemburština","Luba-Katanga":"Luba-Katanga","Ganda":"Gandština","Marshallese":"Maršálština","Malayalam":"Malajálamština","Marathi":"Maráthština","Macedonian":"Makedonština","Malagasy":"Malgaština","Maltese":"Maltština","Mongolian":"Mongolština","Maori":"Maorština","Malay (macrolanguage)":"Malajština","Burmese":"Barmština","Nauru":"Naurština","Navajo":"Navažština","South Ndebele":"Jižní ndebelština","North Ndebele":"Severní ndebelština","Ndonga":"Ndondština","Nepali (macrolanguage)":"Nepálština","Dutch":"Dánština","Norwegian Nynorsk":"Norština Nynorsk","Norwegian Bokmål":"Norština Bokmål","Norwegian":"Norština ","Nyanja":"Čičevština","Occitan":"Okcitánština","Ojibwa":"Ojibwa","Oriya (macrolanguage)":"Urijština","Oromo":"Oromština","Ossetian":"Osetština","Panjabi":"Paňdžábština","Pakistan Sign Language":"Pakistánská znaková řeč","Polish":"Polština","Portuguese":"Portugalština","Pushto":"Paštština","Quechua":"Kečuánština","Romansh":"Rétorománština","Romanian":"Rumunština","Russian Sign Language":"Ruská znaková řeč","Rundi":"Kirundi","Russian":"Ruština","Sango":"Sango","Saudi Arabian Sign Language":"Saudská arabská znaková řeč","South African Sign Language":"Jihoafrická znaková řeč","Sinhala":"Sinhálština","Slovak":"Slovenština","Slovenian":"Slovinština","Northern Sami":"Severní sámština","Samoan":"Samojština","Shona":"Shona","Sindhi":"Sindhština","Somali":"Somálština","Southern Sotho":"Jižní sotština","Spanish":"Španělština","Albanian":"Albánština","Sardinian":"Sardínština","Serbian":"Srbština","Swati":"Swati","Sundanese":"Sundština","Swahili (macrolanguage)":"Svahilština","Swedish":"Švédština","Swedish Sign Language":"Švédská znaková řeč","Tahitian":"Tahitština","Tamil":"Tamilština","Tatar":"Tatarština","Telugu":"Telugština","Tajik":"Tádžičtina","Tagalog":"Tagalog","Thai":"Thajština","Tigrinya":"Tigrinya","Klingon":"Klingonština","Tonga (Tonga Islands)":"Tongánština","Tswana":"Setswanština","Tsonga":"Tsongština","Turkmen":"Turkmenština","Turkish":"Turečtina","Twi":"Twi","Uighur":"Ujgurština","Ukrainian":"Ukrajinština","Urdu":"Urdština","Uzbek":"Uzbečtina","Venda":"Vendština","Vietnamese":"Vietnamština","Walloon":"Valonština","Wolof":"Wolof ","Xhosa":"Xhoština","Yiddish":"Jidiš","Yoruba":"Jorubština","Zhuang":"Čuangština","Chinese":"Čínština","Zulu":"Zuluština"}
\ No newline at end of file
+{"Music":"Hudba","Films":"Filmy","Vehicles":"Auta","Art":"Umění","Sports":"Sport","Travels":"Cestování","Gaming":"Hry","People":"Lidé","Comedy":"Komedie","Entertainment":"Zábava","News & Politics":"Zprávy a politika","How To":"Jak na to","Education":"Výukové","Activism":"Aktivismus","Science & Technology":"Věda a technologie","Animals":"Zvířata","Kids":"Děti","Food":"Jídlo a vaření","Attribution":"Uveďte autora","Attribution - Share Alike":"Uveďte autora - Zachovejte licenci","Attribution - No Derivatives":"Uveďte autora - Nezpracovávejte","Attribution - Non Commercial":"Uveďte autora - Nešiřte dílo komerčně","Attribution - Non Commercial - Share Alike":"Uveďte autora - Nešiřte dílo komerčně - Zachovejte licenci","Attribution - Non Commercial - No Derivatives":"Uveďte autora - Nešiřte dílo komerčně - Nezpracovávejte","Public Domain Dedication":"Volné dílo","Public":"Veřejné","Unlisted":"Nezobrazeno","Private":"Soukromé","Published":"Publikované","To transcode":"K transkódování","To import":"To import","Pending":"Čekající","Success":"Úspěch","Failed":"Neúspěch","Misc":"Různé","Unknown":"Neznámé","Afar":"Afarština","Abkhazian":"Abcházština","Afrikaans":"Afrikánština","Akan":"Akanština","Amharic":"Amharština","Arabic":"Arabština","Aragonese":"Aragonština","American Sign Language":"Americká znaková řeč","Assamese":"Ásámština","Avaric":"Avarština","Kotava":"Kotava","Aymara":"Ajmarština","Azerbaijani":"Ázerbájdžánština","Bashkir":"Baškirština","Bambara":"Bambarština","Belarusian":"Běloruština","Bengali":"Bengálština","British Sign Language":"Britská znaková řeč","Bislama":"Bislamština","Tibetan":"Tibetština","Bosnian":"Bosenština","Breton":"Bretonština","Bulgarian":"Bulharština","Brazilian Sign Language":"Brazilská znaková řeč","Catalan":"Katalánština","Czech":"Čeština","Chamorro":"Chamorro","Chechen":"Čečenština","Chuvash":"Čuvaština","Cornish":"Kornština","Corsican":"Korsičtina","Cree":"Kríjština","Czech Sign Language":"Česká znaková řeč","Chinese Sign Language":"Čínská znaková řeč","Welsh":"Velština","Danish":"Dánština","German":"Němčina","Dhivehi":"Maledivština","Danish Sign Language":"Dánská znaková řeč","Dzongkha":"Dzongkä","Modern Greek (1453-)":"Moderní řečtina","English":"Angličtina","Esperanto":"Esperanto","Estonian":"Estonština","Basque":"Baskičtina","Ewe":"Eveština","Faroese":"Faerština","Persian":"Perština","Fijian":"Fidžijština","Finnish":"Finština","French":"Francouzština","Western Frisian":"Západofríština","French Sign Language":"Francouzská znaková řeč","Fulah":"Fulbština","Scottish Gaelic":"Skotská gaelština","Irish":"Irština","Galician":"Galicijština","Manx":"Manština","Guarani":"Guaranština","German Sign Language":"Německá znaková řeč","Gujarati":"Gudžarátština","Haitian":"Haitská kreolština","Hausa":"Hauština","Serbo-Croatian":"Srcbochorvatšinta","Hebrew":"Hebrejština","Herero":"Herero","Hindi":"Hindština","Hiri Motu":"Hiri Motu","Croatian":"Chorvatština","Hungarian":"Maďarština","Armenian":"Arménština","Igbo":"Igboština","Sichuan Yi":"Nuosu","Inuktitut":"Inuktitutština","Indonesian":"Indonéština","Inupiaq":"Inupiaq","Icelandic":"Islandština","Italian":"Italština","Javanese":"Javánština","Lojban":"Lojban","Japanese":"Japonština","Japanese Sign Language":"Japonská znaková řeč","Kalaallisut":"Grónština","Kannada":"Kannadština","Kashmiri":"Kašmírština","Georgian":"Gruzínština","Kanuri":"Kanurijština","Kazakh":"Kazaština","Khmer":"Khmerština","Kikuyu":"Kikujština","Kinyarwanda":"Rwandština","Kirghiz":"Kyrgyzština","Komi":"Komi","Kongo":"Konžština","Korean":"Korejština","Kuanyama":"Kuanyama","Kurdish":"Kurdština","Lao":"Laoština","Latvian":"Lotyština","Limburgan":"Limburština","Lingala":"Ngalština","Lithuanian":"Litevština","Luxembourgish":"Lucemburština","Luba-Katanga":"Luba-Katanga","Ganda":"Gandština","Marshallese":"Maršálština","Malayalam":"Malajálamština","Marathi":"Maráthština","Macedonian":"Makedonština","Malagasy":"Malgaština","Maltese":"Maltština","Mongolian":"Mongolština","Maori":"Maorština","Malay (macrolanguage)":"Malajština","Burmese":"Barmština","Nauru":"Naurština","Navajo":"Navažština","South Ndebele":"Jižní ndebelština","North Ndebele":"Severní ndebelština","Ndonga":"Ndondština","Nepali (macrolanguage)":"Nepálština","Dutch":"Dánština","Norwegian Nynorsk":"Norština Nynorsk","Norwegian Bokmål":"Norština Bokmål","Norwegian":"Norština ","Nyanja":"Čičevština","Occitan":"Okcitánština","Ojibwa":"Ojibwa","Oriya (macrolanguage)":"Urijština","Oromo":"Oromština","Ossetian":"Osetština","Panjabi":"Paňdžábština","Pakistan Sign Language":"Pakistánská znaková řeč","Polish":"Polština","Portuguese":"Portugalština","Pushto":"Paštština","Quechua":"Kečuánština","Romansh":"Rétorománština","Romanian":"Rumunština","Russian Sign Language":"Ruská znaková řeč","Rundi":"Kirundi","Russian":"Ruština","Sango":"Sango","Saudi Arabian Sign Language":"Saudská arabská znaková řeč","South African Sign Language":"Jihoafrická znaková řeč","Sinhala":"Sinhálština","Slovak":"Slovenština","Slovenian":"Slovinština","Northern Sami":"Severní sámština","Samoan":"Samojština","Shona":"Shona","Sindhi":"Sindhština","Somali":"Somálština","Southern Sotho":"Jižní sotština","Spanish":"Španělština","Albanian":"Albánština","Sardinian":"Sardínština","Serbian":"Srbština","Swati":"Swati","Sundanese":"Sundština","Swahili (macrolanguage)":"Svahilština","Swedish":"Švédština","Swedish Sign Language":"Švédská znaková řeč","Tahitian":"Tahitština","Tamil":"Tamilština","Tatar":"Tatarština","Telugu":"Telugština","Tajik":"Tádžičtina","Tagalog":"Tagalog","Thai":"Thajština","Tigrinya":"Tigrinya","Klingon":"Klingonština","Tonga (Tonga Islands)":"Tongánština","Tswana":"Setswanština","Tsonga":"Tsongština","Turkmen":"Turkmenština","Turkish":"Turečtina","Twi":"Twi","Uighur":"Ujgurština","Ukrainian":"Ukrajinština","Urdu":"Urdština","Uzbek":"Uzbečtina","Venda":"Vendština","Vietnamese":"Vietnamština","Walloon":"Valonština","Wolof":"Wolof ","Xhosa":"Xhoština","Yiddish":"Jidiš","Yoruba":"Jorubština","Zhuang":"Čuangština","Chinese":"Čínština","Zulu":"Zuluština"}
\ No newline at end of file
index eebc2f96e6bcd4b464bcb641fb2b53a0523bf4e5..64762b5752fd88fb1fa1de842bf8a27dec05fbde 100644 (file)
@@ -1 +1 @@
-{"Music":"Musiques","Films":"Films","Vehicles":"Transport","Art":"Art","Sports":"Sports","Travels":"Voyages","Gaming":"Jeux vidéos","People":"Personnalités","Comedy":"Humour","Entertainment":"Divertissement","News & Politics":"Actualité & Politique","How To":"Tutoriels","Education":"Éducation","Activism":"Militantisme","Science & Technology":"Science & Technologie","Animals":"Animaux","Kids":"Enfants","Food":"Cuisine","Attribution":"Attribution","Attribution - Share Alike":"Attribution - Partage dans les mêmes conditions","Attribution - No Derivatives":"Attribution - Pas d’œuvre dérivée","Attribution - Non Commercial":"Attribution - Utilisation non commerciale","Attribution - Non Commercial - Share Alike":"Attribution - Utilisation non commerciale - Partage dans les mêmes conditions","Attribution - Non Commercial - No Derivatives":"Attribution - Utilisation non commerciale - Pas d’œuvre dérivée","Public Domain Dedication":"Domaine public","Public":"Publique","Unlisted":"Non listée","Private":"Privée","Published":"Publiée","To transcode":"À transcoder","To import":"À importer","Pending":"En cours","Success":"Succès","Failed":"Échoué","Misc":"Divers","Unknown":"Inconnu","Afar":"Afar","Abkhazian":"Abkhaze","Afrikaans":"Afrikaans","Akan":"Akan","Amharic":"Amharique","Arabic":"Arabe","Aragonese":"Aragonais","American Sign Language":"Langue des signes américaine","Assamese":"Assamais","Avaric":"Avar","Kotava":"Kotava","Aymara":"Aymara","Azerbaijani":"Azéri","Bashkir":"Bachkir","Bambara":"Bambara","Belarusian":"Biélorusse","Bengali":"Bengali","British Sign Language":"Langue des signes britannique","Bislama":"Bichlamar","Tibetan":"Tibétain","Bosnian":"Bosniaque","Breton":"Breton","Bulgarian":"Bulgare","Brazilian Sign Language":"Langue des signes brésilienne","Catalan":"Catalan","Czech":"Tchèque","Chamorro":"Chamorro","Chechen":"Tchétchène","Chuvash":"Tchouvache","Cornish":"Cornique","Corsican":"Corse","Cree":"Cree","Czech Sign Language":"Langue des signes tchèque","Chinese Sign Language":"Langue des signes chinoise","Welsh":"Gallois","Danish":"Danois","German":"Allemand","Dhivehi":"Maldivien","Danish Sign Language":"Langue des signes danoise","Dzongkha":"Dzongkha","Modern Greek (1453-)":"Grec moderne (après 1453)","English":"Anglais","Esperanto":"Espéranto","Estonian":"Estonien","Basque":"Basque","Ewe":"Éwé","Faroese":"Féroïen","Persian":"Persan","Fijian":"Fidjien","Finnish":"Finnois","French":"Français","Western Frisian":"Frison occidental","French Sign Language":"Langue des signes française","Fulah":"Peul","Scottish Gaelic":"Gaélique","Irish":"Irlandais","Galician":"Galicien","Manx":"Manx","Guarani":"Guarani","German Sign Language":"Langue des signes allemande","Gujarati":"Goudjrati","Haitian":"Haïtien","Hausa":"Haoussa","Serbo-Croatian":"Serbo-croate","Hebrew":"Hébreu","Herero":"Herero","Hindi":"Hindi","Hiri Motu":"Hiri motu","Croatian":"Croate","Hungarian":"Hongrois","Armenian":"Arménien","Igbo":"Igbo","Sichuan Yi":"Yi de Sichuan","Inuktitut":"Inuktitut","Indonesian":"Indonésien","Inupiaq":"Inupiaq","Icelandic":"Islandais","Italian":"Italien","Javanese":"Javanais","Lojban":"Lojban","Japanese":"Japonais","Japanese Sign Language":"Langue des signes japonaise","Kalaallisut":"Groenlandais","Kannada":"Kannada","Kashmiri":"Kashmiri","Georgian":"Géorgien","Kanuri":"Kanouri","Kazakh":"Kazakh","Khmer":"Khmer central","Kikuyu":"Kikuyu","Kinyarwanda":"Rwanda","Kirghiz":"Kirghiz","Komi":"Kom","Kongo":"Kongo","Korean":"Coréen","Kuanyama":"Kuanyama","Kurdish":"Kurde","Lao":"Lao","Latvian":"Letton","Limburgan":"Limbourgeois","Lingala":"Lingala","Lithuanian":"Lituanien","Luxembourgish":"Luxembourgeois","Luba-Katanga":"Luba-katanga","Ganda":"Ganda","Marshallese":"Marshall","Malayalam":"Malayalam","Marathi":"Marathe","Macedonian":"Macédonien","Malagasy":"Malgache","Maltese":"Maltais","Mongolian":"Mongol","Maori":"Maori","Malay (macrolanguage)":"Malais","Burmese":"Birman","Nauru":"Nauruan","Navajo":"Navaho","South Ndebele":"Ndébélé du Sud","North Ndebele":"Ndébélé du Nord","Ndonga":"Ndonga","Nepali (macrolanguage)":"Népalais","Dutch":"Néerlandais","Norwegian Nynorsk":"Norvégien nynorsk","Norwegian Bokmål":"Norvégien bokmål","Norwegian":"Norvégien","Nyanja":"Chichewa","Occitan":"Occitane","Ojibwa":"Ojibwa","Oriya (macrolanguage)":"Oriya","Oromo":"Galla","Ossetian":"Ossète","Panjabi":"Pendjabi","Pakistan Sign Language":"Langue des signes pakistanaise","Polish":"Polonais","Portuguese":"Portugais","Pushto":"Pachto","Quechua":"Quechua","Romansh":"Romanche","Romanian":"Roumain","Russian Sign Language":"Langue des signes russe","Rundi":"Rundi","Russian":"Russe","Sango":"Sango","Saudi Arabian Sign Language":"Langue des signes saoudienne","South African Sign Language":"Langue des signes sud-africaine","Sinhala":"Singhalais","Slovak":"Slovaque","Slovenian":"Slovène","Northern Sami":"Sami du Nord","Samoan":"Samoan","Shona":"Shona","Sindhi":"Sindhi","Somali":"Somali","Southern Sotho":"Sotho du Sud","Spanish":"Espagnol","Albanian":"Albanais","Sardinian":"Sarde","Serbian":"Serbe","Swati":"Swati","Sundanese":"Soundanais","Swahili (macrolanguage)":"Swahili","Swedish":"Suédois","Swedish Sign Language":"Langue des signes suédoise","Tahitian":"Tahitien","Tamil":"Tamoul","Tatar":"Tatar","Telugu":"Télougou","Tajik":"Tadjik","Tagalog":"Tagalog","Thai":"Thaï","Tigrinya":"Tigrigna","Klingon":"Klingon","Tonga (Tonga Islands)":"Tongan (Îles Tonga)","Tswana":"Tswana","Tsonga":"Tsonga","Turkmen":"Turkmène","Turkish":"Turc","Twi":"Twi","Uighur":"Ouïgour","Ukrainian":"Ukrainien","Urdu":"Ourdou","Uzbek":"Ouszbek","Venda":"Venda","Vietnamese":"Vietnamien","Walloon":"Wallon","Wolof":"Wolof","Xhosa":"Xhosa","Yiddish":"Yiddish","Yoruba":"Yoruba","Zhuang":"Zhuang","Chinese":"Chinois","Zulu":"Zoulou"}
\ No newline at end of file
+{"Music":"Musiques","Films":"Films","Vehicles":"Transport","Art":"Art","Sports":"Sports","Travels":"Voyages","Gaming":"Jeux vidéos","People":"Personnalités","Comedy":"Humour","Entertainment":"Divertissement","News & Politics":"Actualité & Politique","How To":"Tutoriels","Education":"Éducation","Activism":"Militantisme","Science & Technology":"Science & Technologie","Animals":"Animaux","Kids":"Enfants","Food":"Cuisine","Attribution":"Attribution","Attribution - Share Alike":"Attribution - Partage dans les mêmes conditions","Attribution - No Derivatives":"Attribution - Pas d’œuvre dérivée","Attribution - Non Commercial":"Attribution - Utilisation non commerciale","Attribution - Non Commercial - Share Alike":"Attribution - Utilisation non commerciale - Partage dans les mêmes conditions","Attribution - Non Commercial - No Derivatives":"Attribution - Utilisation non commerciale - Pas d’œuvre dérivée","Public Domain Dedication":"Domaine public","Public":"Publique","Unlisted":"Non listée","Private":"Privée","Published":"Publiée","To transcode":"À transcoder","To import":"À importer","Pending":"En cours","Success":"Succès","Failed":"Échoué","This video does not exist.":"Cette vidéo n'existe pas.","We cannot fetch the video. Please try again later.":"Nous ne pouvons pas récupérer la vidéo. Merci de réessayer plus tard.","Sorry":"Désolé","This video is not available because the remote instance is not responding.":"Cette vidéo n'est pas disponible car l'instance distante ne répond pas.","Misc":"Divers","Unknown":"Inconnu","Afar":"Afar","Abkhazian":"Abkhaze","Afrikaans":"Afrikaans","Akan":"Akan","Amharic":"Amharique","Arabic":"Arabe","Aragonese":"Aragonais","American Sign Language":"Langue des signes américaine","Assamese":"Assamais","Avaric":"Avar","Kotava":"Kotava","Aymara":"Aymara","Azerbaijani":"Azéri","Bashkir":"Bachkir","Bambara":"Bambara","Belarusian":"Biélorusse","Bengali":"Bengali","British Sign Language":"Langue des signes britannique","Bislama":"Bichlamar","Tibetan":"Tibétain","Bosnian":"Bosniaque","Breton":"Breton","Bulgarian":"Bulgare","Brazilian Sign Language":"Langue des signes brésilienne","Catalan":"Catalan","Czech":"Tchèque","Chamorro":"Chamorro","Chechen":"Tchétchène","Chuvash":"Tchouvache","Cornish":"Cornique","Corsican":"Corse","Cree":"Cree","Czech Sign Language":"Langue des signes tchèque","Chinese Sign Language":"Langue des signes chinoise","Welsh":"Gallois","Danish":"Danois","German":"Allemand","Dhivehi":"Maldivien","Danish Sign Language":"Langue des signes danoise","Dzongkha":"Dzongkha","Modern Greek (1453-)":"Grec moderne (après 1453)","English":"Anglais","Esperanto":"Espéranto","Estonian":"Estonien","Basque":"Basque","Ewe":"Éwé","Faroese":"Féroïen","Persian":"Persan","Fijian":"Fidjien","Finnish":"Finnois","French":"Français","Western Frisian":"Frison occidental","French Sign Language":"Langue des signes française","Fulah":"Peul","Scottish Gaelic":"Gaélique","Irish":"Irlandais","Galician":"Galicien","Manx":"Manx","Guarani":"Guarani","German Sign Language":"Langue des signes allemande","Gujarati":"Goudjrati","Haitian":"Haïtien","Hausa":"Haoussa","Serbo-Croatian":"Serbo-croate","Hebrew":"Hébreu","Herero":"Herero","Hindi":"Hindi","Hiri Motu":"Hiri motu","Croatian":"Croate","Hungarian":"Hongrois","Armenian":"Arménien","Igbo":"Igbo","Sichuan Yi":"Yi de Sichuan","Inuktitut":"Inuktitut","Indonesian":"Indonésien","Inupiaq":"Inupiaq","Icelandic":"Islandais","Italian":"Italien","Javanese":"Javanais","Lojban":"Lojban","Japanese":"Japonais","Japanese Sign Language":"Langue des signes japonaise","Kalaallisut":"Groenlandais","Kannada":"Kannada","Kashmiri":"Kashmiri","Georgian":"Géorgien","Kanuri":"Kanouri","Kazakh":"Kazakh","Khmer":"Khmer central","Kikuyu":"Kikuyu","Kinyarwanda":"Rwanda","Kirghiz":"Kirghiz","Komi":"Kom","Kongo":"Kongo","Korean":"Coréen","Kuanyama":"Kuanyama","Kurdish":"Kurde","Lao":"Lao","Latvian":"Letton","Limburgan":"Limbourgeois","Lingala":"Lingala","Lithuanian":"Lituanien","Luxembourgish":"Luxembourgeois","Luba-Katanga":"Luba-katanga","Ganda":"Ganda","Marshallese":"Marshall","Malayalam":"Malayalam","Marathi":"Marathe","Macedonian":"Macédonien","Malagasy":"Malgache","Maltese":"Maltais","Mongolian":"Mongol","Maori":"Maori","Malay (macrolanguage)":"Malais","Burmese":"Birman","Nauru":"Nauruan","Navajo":"Navaho","South Ndebele":"Ndébélé du Sud","North Ndebele":"Ndébélé du Nord","Ndonga":"Ndonga","Nepali (macrolanguage)":"Népalais","Dutch":"Néerlandais","Norwegian Nynorsk":"Norvégien nynorsk","Norwegian Bokmål":"Norvégien bokmål","Norwegian":"Norvégien","Nyanja":"Chichewa","Occitan":"Occitane","Ojibwa":"Ojibwa","Oriya (macrolanguage)":"Oriya","Oromo":"Galla","Ossetian":"Ossète","Panjabi":"Pendjabi","Pakistan Sign Language":"Langue des signes pakistanaise","Polish":"Polonais","Portuguese":"Portugais","Pushto":"Pachto","Quechua":"Quechua","Romansh":"Romanche","Romanian":"Roumain","Russian Sign Language":"Langue des signes russe","Rundi":"Rundi","Russian":"Russe","Sango":"Sango","Saudi Arabian Sign Language":"Langue des signes saoudienne","South African Sign Language":"Langue des signes sud-africaine","Sinhala":"Singhalais","Slovak":"Slovaque","Slovenian":"Slovène","Northern Sami":"Sami du Nord","Samoan":"Samoan","Shona":"Shona","Sindhi":"Sindhi","Somali":"Somali","Southern Sotho":"Sotho du Sud","Spanish":"Espagnol","Albanian":"Albanais","Sardinian":"Sarde","Serbian":"Serbe","Swati":"Swati","Sundanese":"Soundanais","Swahili (macrolanguage)":"Swahili","Swedish":"Suédois","Swedish Sign Language":"Langue des signes suédoise","Tahitian":"Tahitien","Tamil":"Tamoul","Tatar":"Tatar","Telugu":"Télougou","Tajik":"Tadjik","Tagalog":"Tagalog","Thai":"Thaï","Tigrinya":"Tigrigna","Klingon":"Klingon","Tonga (Tonga Islands)":"Tongan (Îles Tonga)","Tswana":"Tswana","Tsonga":"Tsonga","Turkmen":"Turkmène","Turkish":"Turc","Twi":"Twi","Uighur":"Ouïgour","Ukrainian":"Ukrainien","Urdu":"Ourdou","Uzbek":"Ouszbek","Venda":"Venda","Vietnamese":"Vietnamien","Walloon":"Wallon","Wolof":"Wolof","Xhosa":"Xhosa","Yiddish":"Yiddish","Yoruba":"Yoruba","Zhuang":"Zhuang","Chinese":"Chinois","Zulu":"Zoulou"}
\ No newline at end of file
diff --git a/client/src/locale/target/server_it_IT.json b/client/src/locale/target/server_it_IT.json
new file mode 100644 (file)
index 0000000..6586f62
--- /dev/null
@@ -0,0 +1 @@
+{"Music":"Musica","Films":"Film","Vehicles":"Veicoli","Art":"Arte","Sports":"Sport","Travels":"Viaggi","Gaming":"Giochi","People":"Persone","Comedy":"Commedia","Entertainment":"Intrattenimento","News & Politics":"Notizie & Politica","How To":"Come fare","Education":"Educazione","Activism":"Attivismo","Science & Technology":"Scienza & Tecnologia","Animals":"Animali","Kids":"Bambini","Food":"Cibo","Attribution":"Attribuzione","Attribution - Share Alike":"Attribuzione - Condividi Allo Stesso Modo","Attribution - No Derivatives":"Attribuzione - Non Opere Derivate","Attribution - Non Commercial":"Attribuzione - Non Commerciale","Attribution - Non Commercial - Share Alike":"Attribuzione - Non Commerciale - Condividi Allo Stesso Modo","Attribution - Non Commercial - No Derivatives":"Attribuzione - Non Commerciale - Non Opere Derivate","Public Domain Dedication":"Pubblico Dominio","Public":"Pubblico","Unlisted":"Non elencato","Private":"Privato","Published":"Pubblicato","To transcode":"Da codificare","To import":"Da importare","Pending":"In sospeso","Success":"Successo","Failed":"Fallito","Misc":"Altro","Unknown":"Sconosciuto","Afar":"Afar","Abkhazian":"Abcaso","Afrikaans":"Afrikaans","Akan":"Akan","Amharic":"Amarico","Arabic":"Arabo","Aragonese":"Aragonese","American Sign Language":"Lingua dei Segni Americana","Assamese":"Assamese","Avaric":"Avarico","Kotava":"Kotava","Aymara":"Aymara","Azerbaijani":"Azero","Bashkir":"Bashkir","Bambara":"Bambara","Belarusian":"Bielorusso","Bengali":"Bengalese","British Sign Language":"Lingua dei Segni Britannica","Bislama":"Bislama","Tibetan":"Tibetano","Bosnian":"Bosniaco","Breton":"Bretone","Bulgarian":"Bulgaro","Brazilian Sign Language":"Lingua dei Segni Brasiliana","Catalan":"Catalano","Czech":"Ceco","Chamorro":"Chamorro","Chechen":"Ceceno","Chuvash":"Ciuvascio","Cornish":"Cornico","Corsican":"Corso","Cree":"Cree","Czech Sign Language":"Lingua dei Segni Ceca","Chinese Sign Language":"Lingua dei Segni Cinese","Welsh":"Gallese","Danish":"Danese","German":"Tedesco","Dhivehi":"Dhivehi","Danish Sign Language":"Lingua dei Segni Danese","Dzongkha":"Dzongkha","Modern Greek (1453-)":"Greco Moderno (1453-)","English":"Inglese","Esperanto":"Esperanto","Estonian":"Estone","Basque":"Basco","Ewe":"Ewe","Faroese":"Faroese","Persian":"Persiano","Fijian":"Fijiano","Finnish":"Finlandese","French":"Francese","Western Frisian":"Frisone Occidentale","French Sign Language":"Lingua dei Segni Francese","Fulah":"Fula","Scottish Gaelic":"Gaelico Scozzese","Irish":"Irlandese","Galician":"Galiziano","Manx":"Mannese","Guarani":"Guarani","German Sign Language":"Lingua dei Segni Tedesca","Gujarati":"Gujarati","Haitian":"Haitiano","Hausa":"Hausa","Serbo-Croatian":"Serbocroato","Hebrew":"Ebraico","Herero":"Herero","Hindi":"Hindi","Hiri Motu":"Hiri Motu","Croatian":"Croato","Hungarian":"Ungherese","Armenian":"Armeno","Igbo":"Igbo","Sichuan Yi":"Sichuan Yi","Inuktitut":"Inuktitut","Indonesian":"Indonesiano","Inupiaq":"Inupiaq","Icelandic":"Islandese","Italian":"Italiano","Javanese":"Giavanese","Lojban":"Lojban","Japanese":"Giapponese","Japanese Sign Language":"Lingua dei Segni Giapponese","Kalaallisut":"Kalaallisut","Kannada":"Kannada","Kashmiri":"Kashmiri","Georgian":"Georgiano","Kanuri":"Kanuri","Kazakh":"Kazako","Khmer":"Khmer","Kikuyu":"Kikuyu","Kinyarwanda":"Kinyarwanda","Kirghiz":"Kirghiso","Komi":"Komi","Kongo":"Kongo","Korean":"Coreano","Kuanyama":"Kuanyama","Kurdish":"Curdo","Lao":"Lao","Latvian":"Lettone","Limburgan":"Limburghese","Lingala":"Lingala","Lithuanian":"Lituano","Luxembourgish":"Lussemburghese","Luba-Katanga":"Luba-Katanga","Ganda":"Ganda","Marshallese":"Marshallese","Malayalam":"Malayalam","Marathi":"Marathi","Macedonian":"Macedone","Malagasy":"Malgascio","Maltese":"Maltese","Mongolian":"Mongolo","Maori":"Maori","Malay (macrolanguage)":"Malay (macrolinguaggio)","Burmese":"Birmano","Nauru":"Nauru","Navajo":"Navajo","South Ndebele":"Ndebele Meridionale","North Ndebele":"Ndebele Settentrionale","Ndonga":"Ndonga","Nepali (macrolanguage)":"Nepalese (macrolinguaggio)","Dutch":"Olandese","Norwegian Nynorsk":"Norvegese Nynorsk","Norwegian Bokmål":"Norvegese Bokmål","Norwegian":"Norvegese","Nyanja":"Chewa","Occitan":"Occitano","Ojibwa":"Ojibwa","Oriya (macrolanguage)":"Oriya (macrolinguaggio)","Oromo":"Oromo","Ossetian":"Osseto","Panjabi":"Punjabi","Pakistan Sign Language":"Lingua dei Segni Pakistana","Polish":"Polacco","Portuguese":"Portoghese","Pushto":"Pashto","Quechua":"Quechua","Romansh":"Romancio","Romanian":"Romeno","Russian Sign Language":"Lingua dei Segni Russa","Rundi":"Rundi","Russian":"Russo","Sango":"Sango","Saudi Arabian Sign Language":"Lingua dei Segni dell'Arabia Saudita","South African Sign Language":"Lingua dei Segni Sudafricana","Sinhala":"Singalese","Slovak":"Slovacco","Slovenian":"Sloveno","Northern Sami":"Sami Settentrionale","Samoan":"Samoano","Shona":"Shona","Sindhi":"Sindhi","Somali":"Somalo","Southern Sotho":"Sotho Meridionale","Spanish":"Spagnolo","Albanian":"Albanese","Sardinian":"Sardo","Serbian":"Serbo","Swati":"Swati","Sundanese":"Sondanese","Swahili (macrolanguage)":"Swahili (macrolinguaggio)","Swedish":"Svedese","Swedish Sign Language":"Lingua dei Segni Svedese","Tahitian":"Tahitiano","Tamil":"Tamil","Tatar":"Tataro","Telugu":"Telugu","Tajik":"Tagico","Tagalog":"Tagalog","Thai":"Thai","Tigrinya":"Tigrino","Klingon":"Klingon","Tonga (Tonga Islands)":"Tonga (Isole delle Tonga)","Tswana":"Tswana","Tsonga":"Tsonga","Turkmen":"Turcmeno","Turkish":"Turco","Twi":"Twi","Uighur":"Uighuro","Ukrainian":"Ucraino","Urdu":"Urdu","Uzbek":"Uzbeco","Venda":"Venda","Vietnamese":"Vietnamita","Walloon":"Vallone","Wolof":"Wolof","Xhosa":"Xhosa","Yiddish":"Yiddish","Yoruba":"Yoruba","Zhuang":"Zhuang","Chinese":"Cinese","Zulu":"Zulu"}
\ No newline at end of file
index 797d022c50bc3d13a28c95392032209f5baf9fe9..a53a905888420d968f33b2441fe8d73c25eb96a2 100644 (file)
         <source>Entertainment</source>
         <target>Entertainment</target>
       </trans-unit>
+      <trans-unit id="News &amp; Politics">
+        <source>News &amp; Politics</source>
+        <target>Nieuws en Politiek</target>
+      </trans-unit>
       <trans-unit id="How To">
         <source>How To</source>
         <target>Tutorials</target>
@@ -57,7 +61,7 @@
       </trans-unit>
       <trans-unit id="Science &amp; Technology">
         <source>Science &amp; Technology</source>
-        <target>Wetenschap &amp; technologie</target>
+        <target>Wetenschap en technologie</target>
       </trans-unit>
       <trans-unit id="Animals">
         <source>Animals</source>
@@ -77,7 +81,7 @@
       </trans-unit>
       <trans-unit id="Attribution - Share Alike">
         <source>Attribution - Share Alike</source>
-        <target>Naamsvermelding – Gelijk Delen</target>
+        <target>Naamsvermelding – Gelijken Delen</target>
       </trans-unit>
       <trans-unit id="Attribution - No Derivatives">
         <source>Attribution - No Derivatives</source>
         <source>Private</source>
         <target>Privé</target>
       </trans-unit>
+      <trans-unit id="Published">
+        <source>Published</source>
+        <target>Gepubliceerd</target>
+      </trans-unit>
+      <trans-unit id="To transcode">
+        <source>To transcode</source>
+        <target>Transcoden</target>
+      </trans-unit>
+      <trans-unit id="To import">
+        <source>To import</source>
+        <target>Importeren</target>
+      </trans-unit>
+      <trans-unit id="Pending">
+        <source>Pending</source>
+        <target>In behandeling</target>
+      </trans-unit>
+      <trans-unit id="Success">
+        <source>Success</source>
+        <target>Success</target>
+      </trans-unit>
+      <trans-unit id="Failed">
+        <source>Failed</source>
+        <target>Gefaald</target>
+      </trans-unit>
       <trans-unit id="Misc">
         <source>Misc</source>
-        <target>Diverse</target>
+        <target>Varia</target>
       </trans-unit>
       <trans-unit id="Unknown">
         <source>Unknown</source>
diff --git a/client/src/locale/target/server_pl_PL.json b/client/src/locale/target/server_pl_PL.json
new file mode 100644 (file)
index 0000000..90973bf
--- /dev/null
@@ -0,0 +1 @@
+{"Music":"Muzyka","Films":"Filmy","Vehicles":"Pojazdy","Art":"Sztuka","Sports":"Sport","Travels":"Podróże","Gaming":"Gry","People":"Ludzie","Comedy":"Komedia","Entertainment":"Rozrywka","How To":"Poradniki","Education":"Edukacja","Activism":"Aktywizm","Science & Technology":"Nauka i technologia","Animals":"Zwierzęta","Kids":"Dzieci","Food":"Jedzenie","Attribution":"Uznanie autostwa","Attribution - Share Alike":"Uznanie autorstwa - Na tych samych warunkach","Attribution - No Derivatives":"Uznanie autorstwa - Bez utworów zależnych","Attribution - Non Commercial":"Uznanie autorstwa - Użycie niekomercyjne","Attribution - Non Commercial - Share Alike":"Uznanie autorstwa - Użycie niekomercyjne - Na tych samych warunkach","Attribution - Non Commercial - No Derivatives":"Uznanie autorstwa - Użycie niekomercyjne - Bez utworów zależnych","Public Domain Dedication":"Przekazanie do Domeny Publicznej","Public":"Publiczne","Unlisted":"Niewypisane","Private":"Prywatne","Published":"Opublikowano","To transcode":"Transkodować","To import":"Importować","Pending":"Oczekiwanie","Success":"Sukces","Failed":"Niepowodzenie","Misc":"Różne","Unknown":"Nieznane","Afar":"Afar","Abkhazian":"Abchaski","Afrikaans":"Afrikaans","Akan":"Akan","Amharic":"Amharski","Arabic":"Arabski","American Sign Language":"Amerykański Język Migowy","Avaric":"Awarski","Bashkir":"Baszkirski","Bambara":"Bambara","Belarusian":"Białoruski","Bengali":"Bengalski","British Sign Language":"Brytyjski Język Migowy","Bislama":"Bislama","Tibetan":"Tybetański","Bosnian":"Bośniacki","Breton":"Bretoński","Bulgarian":"Bułgarski","Catalan":"Kataloński","Czech":"Czeski","Cornish":"Kornijski","Corsican":"Korsykański","Cree":"Kri","Czech Sign Language":"Czeski Język Migowy","Chinese Sign Language":"Chiński Język Migowy","Welsh":"Walijski","Danish":"Duński","German":"Niemiecki","Danish Sign Language":"Duński Język Migowy","Dzongkha":"Dzongkha","Modern Greek (1453-)":"Nowogrecki (1453-)","English":"Angielski","Estonian":"Estoński","Basque":"Baskijski","Ewe":"Ewe","Persian":"Perski","Fijian":"Fidżyjski","Finnish":"Fiński","French":"Francuski","French Sign Language":"Francuski Język Migowy","Fulah":"Ful","Irish":"Irlandzki","Galician":"Galicyjski","German Sign Language":"Niemiecki Język Migowy","Hausa":"Hausa","Serbo-Croatian":"Serbsko-Chorwacki","Hindi":"Hindi","Croatian":"Chorwacki","Hungarian":"Węgierski","Armenian":"Ormański","Igbo":"Igbo","Indonesian":"Indonezyjski","Icelandic":"Islandzki","Italian":"Włoski","Javanese":"Jawajski","Japanese":"Japoński","Japanese Sign Language":"Japoński Język Migowy","Komi":"Komi","Kongo":"Kongo","Korean":"Koreański","Kurdish":"Kurdyjski","Lao":"Laotański","Latvian":"Łotewski","Lithuanian":"Litewski","Luxembourgish":"Luksemburski","Nauru":"Naurański","Dutch":"Holenderski","Norwegian Nynorsk":"Norweski Nynorsk","Norwegian Bokmål":"Norweski Bokmål","Norwegian":"Norweski","Oromo":"Oromo","Polish":"Polski","Portuguese":"Portugalski","Pushto":"Paszto","Romansh":"Romansz","Romanian":"Rumuński","Russian Sign Language":"Rosyjski Język Migowy","Rundi":"Rundi","Russian":"Rosyjski","Sango":"Sango","Slovak":"Słowacki","Slovenian":"Słoweński","Samoan":"Samoański","Shona":"Shona","Sindhi":"Sindhi","Somali":"Somalijski","Spanish":"Hiszpański","Serbian":"Serbski","Swedish":"Szwedzki","Swedish Sign Language":"Szwedzki Język Migowy","Tamil":"Tamilski","Tatar":"Tatarski","Telugu":"Telugu","Tajik":"Tadżycki","Tagalog":"Tagalski","Thai":"Tajski","Turkmen":"Turkmeński","Turkish":"Turecki","Twi":"Twi","Ukrainian":"Ukraiński","Urdu":"Urdu","Uzbek":"Uzbecki","Venda":"Venda","Vietnamese":"Wietnamski","Walloon":"Waloński","Wolof":"Wolof","Xhosa":"Xhosa","Yiddish":"Jidysz","Yoruba":"Joruba","Zhuang":"Zhuang","Chinese":"Chiński","Zulu":"Zulu"}
\ No newline at end of file
diff --git a/client/src/locale/target/server_pl_PL.xml b/client/src/locale/target/server_pl_PL.xml
deleted file mode 100644 (file)
index f5ce3f9..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--XLIFF document generated by Zanata. Visit http://zanata.org for more infomation.-->
-<xliff xmlns="urn:oasis:names:tc:xliff:document:1.1" xmlns:xyz="urn:appInfo:Items" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.1 http://www.oasis-open.org/committees/xliff/documents/xliff-core-1.1.xsd" version="1.1">
-  <file source-language="en-US" datatype="plaintext" original="" target-language="pl-PL">
-    <body>
-      <trans-unit id="Music">
-        <source>Music</source>
-        <target>Muzyka</target>
-      </trans-unit>
-      <trans-unit id="Films">
-        <source>Films</source>
-        <target>Filmy</target>
-      </trans-unit>
-      <trans-unit id="Vehicles">
-        <source>Vehicles</source>
-        <target>Pojazdy</target>
-      </trans-unit>
-      <trans-unit id="Art">
-        <source>Art</source>
-        <target>Sztuka</target>
-      </trans-unit>
-      <trans-unit id="Sports">
-        <source>Sports</source>
-        <target>Sport</target>
-      </trans-unit>
-      <trans-unit id="Travels">
-        <source>Travels</source>
-        <target>Podróże</target>
-      </trans-unit>
-      <trans-unit id="Gaming">
-        <source>Gaming</source>
-        <target>Gry</target>
-      </trans-unit>
-      <trans-unit id="People">
-        <source>People</source>
-        <target>Ludzie</target>
-      </trans-unit>
-      <trans-unit id="Comedy">
-        <source>Comedy</source>
-        <target>Komedia</target>
-      </trans-unit>
-      <trans-unit id="Entertainment">
-        <source>Entertainment</source>
-        <target>Rozrywka</target>
-      </trans-unit>
-      <trans-unit id="How To">
-        <source>How To</source>
-        <target>Poradniki</target>
-      </trans-unit>
-      <trans-unit id="Education">
-        <source>Education</source>
-        <target>Edukacja</target>
-      </trans-unit>
-      <trans-unit id="Activism">
-        <source>Activism</source>
-        <target>Aktywizm</target>
-      </trans-unit>
-      <trans-unit id="Science &amp; Technology">
-        <source>Science &amp; Technology</source>
-        <target>Nauka i technologia</target>
-      </trans-unit>
-      <trans-unit id="Animals">
-        <source>Animals</source>
-        <target>Zwierzęta</target>
-      </trans-unit>
-      <trans-unit id="Kids">
-        <source>Kids</source>
-        <target>Dzieci</target>
-      </trans-unit>
-      <trans-unit id="Food">
-        <source>Food</source>
-        <target>Jedzenie</target>
-      </trans-unit>
-      <trans-unit id="Attribution">
-        <source>Attribution</source>
-        <target>Uznanie autostwa</target>
-      </trans-unit>
-      <trans-unit id="Attribution - Share Alike">
-        <source>Attribution - Share Alike</source>
-        <target>Uznanie autorstwa - Na tych samych warunkach</target>
-      </trans-unit>
-      <trans-unit id="Attribution - No Derivatives">
-        <source>Attribution - No Derivatives</source>
-        <target>Uznanie autorstwa - Bez utworów zależnych</target>
-      </trans-unit>
-      <trans-unit id="Attribution - Non Commercial">
-        <source>Attribution - Non Commercial</source>
-        <target>Uznanie autorstwa - Użycie niekomercyjne</target>
-      </trans-unit>
-      <trans-unit id="Attribution - Non Commercial - Share Alike">
-        <source>Attribution - Non Commercial - Share Alike</source>
-        <target>Uznanie autorstwa - Użycie niekomercyjne - Na tych samych warunkach</target>
-      </trans-unit>
-      <trans-unit id="Attribution - Non Commercial - No Derivatives">
-        <source>Attribution - Non Commercial - No Derivatives</source>
-        <target>Uznanie autorstwa - Użycie niekomercyjne - Bez utworów zależnych</target>
-      </trans-unit>
-      <trans-unit id="Public Domain Dedication">
-        <source>Public Domain Dedication</source>
-        <target>Przekazanie do Domeny Publicznej</target>
-      </trans-unit>
-      <trans-unit id="Public">
-        <source>Public</source>
-        <target>Publiczne</target>
-      </trans-unit>
-      <trans-unit id="Unlisted">
-        <source>Unlisted</source>
-        <target>Niewypisane</target>
-      </trans-unit>
-      <trans-unit id="Private">
-        <source>Private</source>
-        <target>Prywatne</target>
-      </trans-unit>
-      <trans-unit id="Published">
-        <source>Published</source>
-        <target>Opublikowano</target>
-      </trans-unit>
-      <trans-unit id="To transcode">
-        <source>To transcode</source>
-        <target>Transkodować</target>
-      </trans-unit>
-      <trans-unit id="To import">
-        <source>To import</source>
-        <target>Importować</target>
-      </trans-unit>
-      <trans-unit id="Pending">
-        <source>Pending</source>
-        <target>Oczekiwanie</target>
-      </trans-unit>
-      <trans-unit id="Success">
-        <source>Success</source>
-        <target>Sukces</target>
-      </trans-unit>
-      <trans-unit id="Failed">
-        <source>Failed</source>
-        <target>Niepowodzenie</target>
-      </trans-unit>
-      <trans-unit id="Misc">
-        <source>Misc</source>
-        <target>Różne</target>
-      </trans-unit>
-      <trans-unit id="Unknown">
-        <source>Unknown</source>
-        <target>Nieznane</target>
-      </trans-unit>
-    </body>
-  </file></xliff>
\ No newline at end of file
diff --git a/client/src/locale/target/server_ru_RU.json b/client/src/locale/target/server_ru_RU.json
new file mode 100644 (file)
index 0000000..e62f2ae
--- /dev/null
@@ -0,0 +1 @@
+{"Music":"Музыка","Films":"Филмы","Vehicles":"Транспортные средства","Art":"Искусство","Sports":"Спорт","Travels":"Путешествия","Gaming":"Видеоигры","People":"Люди","Comedy":"Комедия","Entertainment":"Развлечения","How To":"Как","Education":"Образование","Activism":"Активизм","Science & Technology":"Наука и Технология","Animals":"Животные ","Kids":"Дети","Food":"Еда","Attribution":"Атрибуция","Attribution - Share Alike":" Атрибуция - публикация с одинаковыми условиями ","Attribution - No Derivatives":"Атрибуция - без права изменения ","Attribution - Non Commercial":"Атрибуция - не коммерческое использование","Attribution - Non Commercial - Share Alike":"Атрибуция - не коммерческое использование - публикация с одинаковыми условиями","Attribution - Non Commercial - No Derivatives":"Атрибуция - не коммерческое использование - без права изменения","Public Domain Dedication":"Безлицензионный","Public":"Общественный","Unlisted":" Ее включённый в список","Private":"Личный","Published":"Опубликованный","To transcode":"Перекодировать","To import":"Импортировать","Pending":"В ожидании","Success":"Удачное завершение","Failed":"Неудачно","Misc":"Разное","Unknown":"Неизвестное","Afar":"Афарский","Abkhazian":"Абхазский","Afrikaans":"Африкаанс","Akan":"Акан","Amharic":"Амхарский","Arabic":"Арабский","Aragonese":"Арагонский","American Sign Language":"Амслен","Assamese":"Ассамский","Avaric":"Аварский","Kotava":"Котава","Aymara":"Аймара","Azerbaijani":"Азербайджанский","Bashkir":"Башкирский","Bambara":"Бамана","Belarusian":"Белорусский","Bengali":"Бенгальский","British Sign Language":"Британский жестовый","Bislama":"Бислама","Tibetan":"Тибетский","Bosnian":"Боснийский","Breton":"Бретонский","Bulgarian":"Болгарский","Brazilian Sign Language":"Бразильский жестовый","Catalan":"Каталанский","Czech":"Чешский","Chamorro":"Чаморро","Chechen":"Чеченский","Chuvash":"Чувашский","Cornish":"Корнский","Corsican":"Корсиканский","Cree":"Кри","Czech Sign Language":"Чешский жестовый","Chinese Sign Language":"Китайский жестовый","Welsh":"Уэлш","Danish":"Датский","German":"Немецкий","Dhivehi":"Дивехи","Danish Sign Language":"Датский жестовый ","Dzongkha":"Дзонг-кэ","Modern Greek (1453-)":"Современный греческий","English":"Английский","Esperanto":"Эсперанто","Estonian":"Эстонский","Basque":"Баскский","Ewe":"Эве","Faroese":"Фарерский","Persian":"Персидский","Fijian":"Фиджийский","Finnish":"Финский","French":"Французский","Western Frisian":"Западнофризский","French Sign Language":"Французский жестовый","Fulah":"Фула","Scottish Gaelic":"Шотландский","Irish":"Ирландский","Galician":"Галисийский","Manx":"Мэнский","Guarani":"Гуарани","German Sign Language":"Немецкий жестовый","Gujarati":"Гуджарати","Haitian":"Гаитянский креольский","Hausa":"Хауса","Serbo-Croatian":"Сербохорватский","Hebrew":"Иврит","Herero":"Гереро","Hindi":"Хинди","Hiri Motu":"Хири-моту","Croatian":"Хорватский","Hungarian":"Венгерский","Armenian":"Армянский","Igbo":"Игбо","Sichuan Yi":"Носу","Inuktitut":"Инуктитут","Indonesian":"Индонезийский","Inupiaq":"Аляскинско-инуитские","Icelandic":"Исландский","Italian":"Итальянский","Javanese":"Яванский","Lojban":"Ложбан","Japanese":"Японский","Japanese Sign Language":"Японский жестовый","Kalaallisut":"Гренландский","Kannada":"Каннада","Kashmiri":"Кашмирский","Georgian":"Грузинский","Kanuri":"Канури","Kazakh":"Казахский","Khmer":"Кхмерский","Kikuyu":"Кикуйю","Kinyarwanda":"Руанда","Kirghiz":"Киргизский","Komi":"Коми","Kongo":"Конго","Korean":"Корейский","Kuanyama":"Кваньяма","Kurdish":"Курдские","Lao":"Лаосский","Latvian":"Латышский","Limburgan":"Лимбургский","Lingala":"Лингала","Lithuanian":"Литовский","Luxembourgish":"Люксембургский","Luba-Katanga":"Луба-катанга","Ganda":"Луганда","Marshallese":"Маршалльский","Malayalam":"Малаялам","Marathi":"Маратхи","Macedonian":"Македонский","Malagasy":"Малагасийский","Maltese":"Мальтийский","Mongolian":"Монгольский","Maori":"Маори","Malay (macrolanguage)":"Малайский","Burmese":"Бирманский","Nauru":"Науруанский","Navajo":"Навахо","South Ndebele":"Южный ндебеле","North Ndebele":"Северный ндебеле","Ndonga":"Ндонга","Nepali (macrolanguage)":"Непальский","Dutch":"Нидерландский","Norwegian Nynorsk":"Новонорвежский","Norwegian Bokmål":"Букмол","Norwegian":"Норвежский","Nyanja":"Ньянджа","Ojibwa":"Оджибве","Oriya (macrolanguage)":"Ория","Oromo":"Оромо","Ossetian":"Осетинский","Panjabi":"Панджаби","Pakistan Sign Language":"Дагестанский ","Polish":"Польский","Portuguese":"Португальский","Pushto":"Пушту","Quechua":"Ке́чуа","Romansh":"Романшский","Romanian":"Румынский","Russian Sign Language":"Русский жестовый","Rundi":"Рунди","Russian":"Русский","Sango":"Санго","Saudi Arabian Sign Language":"Арабский жестовый","South African Sign Language":"Жестовый Южной Африки","Sinhala":"Сингальский","Slovak":"Словацкий","Slovenian":"Словенский","Northern Sami":"Северносаамский","Samoan":"Самоанский","Shona":"Шона","Sindhi":"Синдхи","Somali":"Сомалийский","Southern Sotho":"Сесото","Spanish":"Испанский","Albanian":"Албанский","Sardinian":"Сардинский","Serbian":"Сербский","Swati":"Свати","Sundanese":"Сунданский","Swahili (macrolanguage)":"Суахили","Swedish":"Шведский","Swedish Sign Language":"Шведский жестовый","Tahitian":"Таитянский","Tamil":"Тамильский","Tatar":"Татарский","Telugu":"Телугу","Tajik":"Таджикский","Tagalog":"Тагальский","Thai":"Тайский","Tigrinya":"Тигринья","Klingon":"Клингонский","Tonga (Tonga Islands)":"Тонганский","Tswana":"Тсвана","Tsonga":"Тсонга","Turkmen":"Туркменский","Turkish":"Турецкий","Twi":"Чви","Uighur":"Уйгурский","Ukrainian":"Украинский","Urdu":"Урду","Uzbek":"Узбекский","Venda":"Венда","Vietnamese":"Вьетнамский","Walloon":"Валлонский","Wolof":"Волоф","Xhosa":"Коса","Yiddish":"Идиш","Yoruba":"Йоруба","Zhuang":"Чжуанский","Chinese":"Китайский","Zulu":"Зулу"}
\ No newline at end of file
index 3d3c7d6d5ec838163d28be7b590a16bf9288b272..851e098a809790a4930ecff8a7017137458cda82 100644 (file)
@@ -43,5 +43,5 @@
   ],
   "name": "PeerTube",
   "short_name": "PeerTube",
-  "start_url": "/videos/trending"
+  "start_url": "/"
 }
index 5dff18632b0f4df145c13bbea325ea60227de0b7..3689084324c021070ed6a08241f68e581104fdd6 100644 (file)
@@ -45,7 +45,13 @@ import 'core-js/es7/object'
 /** IE10 and IE11 requires the following for the Reflect API. */
 
 // For Google Bot
-import 'core-js/es6/reflect'
+// import 'core-js/es6/reflect'; // --> dealt with in src/environment.ts
+
+/**
+ * Evergreen browsers require these.
+ */
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
+// import 'core-js/es7/reflect' // --> dealt with in src/environment.ts
 
 /**
  * Required to support Web Animations `@angular/platform-browser/animations`.
index 2356f98371a4ad64c88cdb68f58ed2fe46106a25..478737a434dec20f0c0a428fff82078a79938782 100644 (file)
@@ -23,7 +23,7 @@ body {
   // now beware node-sass requires interpolation
   // for css custom properties #{$var}
   --mainColor: #{$orange-color};
-  --mainHoverColor: #{$orange-hoover-color};
+  --mainHoverColor: #{$orange-hover-color};
   --mainBackgroundColor: #{$bg-color};
   --mainForegroundColor: #{$fg-color};
   --menuBackgroundColor: #{$menu-background};
@@ -229,13 +229,12 @@ label {
       font-weight: $font-semibold;
     }
 
-    .close {
+    my-global-icon {
       @include icon(24px);
 
       position: relative;
       top: 3px;
       float: right;
-      background-image: url('../assets/images/global/cross.svg');
 
       margin: 0;
       padding: 0;
@@ -293,6 +292,10 @@ ngb-tabset.bootstrap {
       color: var(--mainForegroundColor) !important;
     }
   }
+
+  .nav-pills .nav-link.active {
+    color: #000 !important;
+  }
 }
 
 .nav-tabs .nav-link.active {
@@ -324,7 +327,7 @@ ngb-tabset.bootstrap {
 table {
   .action-button-edit, .action-button-delete {
     &:hover, &:active, &:focus, &[disabled], &.disabled {
-      background-color: $grey-color !important;
+      background-color: $grey-background-color !important;
     }
   }
 }
@@ -389,4 +392,4 @@ table {
       }
     }
   }
-}
\ No newline at end of file
+}
index 77a20cfe160a13e2da9bb2186d9f1fa5921e3e23..7f413836b56388cf32d93582f55c4bcfa5a4c749 100644 (file)
@@ -31,4 +31,5 @@ $input-focus-border-color: #ced4da;
 $nav-pills-link-active-bg: #F0F0F0;
 $nav-pills-link-active-color: #000;
 
-$zindex-dropdown: 10000;
\ No newline at end of file
+$zindex-dropdown: 10000;
+$zindex-popover: 10000;
index d6f391a457158a3e0c910ec0be55f160ec1f4f8d..e18e9ae9d7990e0cff85cd9ac88ad62d57698829 100644 (file)
   hyphens: auto;
 }
 
+@mixin apply-svg-color ($color) {
+  /deep/ svg {
+    path[fill="#000000"], g[fill="#000000"], rect[fill="#000000"], circle[fill="#000000"] {
+      fill: $color;
+    }
+
+    path[stroke="#000000"], g[stroke="#000000"], rect[stroke="#000000"], circle[stroke="#000000"] {
+      stroke: $color;
+    }
+  }
+}
+
 @mixin peertube-input-text($width) {
   display: inline-block;
   height: $button-height;
@@ -64,6 +76,7 @@
   border-radius: 3px;
   padding-left: 15px;
   padding-right: 15px;
+  font-size: 15px;
 
   &::placeholder {
     color: var(--inputPlaceholderColor);
     color: #fff;
     background-color: #C6C6C6;
   }
+
+  my-global-icon {
+    @include apply-svg-color(#fff)
+  }
 }
 
 @mixin grey-button {
   &, &:active, &:focus {
-    background-color: $grey-color;
-    color: #585858;
+    background-color: $grey-background-color;
+    color: $grey-foreground-color;
   }
 
   &:hover, &:active, &:focus, &[disabled], &.disabled {
-    color: #585858;
-    background-color: $grey-hoover-color;
+    color: $grey-foreground-color;
+    background-color: $grey-background-hover-color;
   }
 
   &[disabled], &.disabled {
     cursor: default;
   }
+
+  my-global-icon {
+    @include apply-svg-color($grey-foreground-color)
+  }
 }
 
 @mixin peertube-button {
   @include peertube-button;
 }
 
+@mixin button-with-icon($width: 20px, $margin-right: 3px, $top: -1px) {
+  my-global-icon {
+    position: relative;
+    width: $width;
+    margin-right: $margin-right;
+    top: $top;
+  }
+}
+
 @mixin peertube-button-file ($width) {
   position: relative;
   overflow: hidden;
       color: transparent;
       text-shadow: 0 0 0 #000;
     }
+
+    option {
+      color: #000;
+    }
   }
 }
 
   }
 }
 
-@mixin create-button ($imageUrl) {
+@mixin create-button {
   @include peertube-button-link;
   @include orange-button;
-
-  .icon.icon-add {
-    @include icon(20px);
-
-    position: relative;
-    top: -1px;
-    margin-right: 5px;
-    background-image: url($imageUrl);
-  }
+  @include button-with-icon(20px, 5px, -1px);
 }
 
 @mixin row-blocks {
index fdf33b12a9b36ec0181998df2b2950754d695338..3780b750120150ac463948a9c4e058cfb2f584c5 100644 (file)
@@ -6,10 +6,13 @@ $font-regular: 400;
 $font-semibold: 600;
 $font-bold: 700;
 
-$grey-color: #E5E5E5;
-$grey-hoover-color: #EFEFEF;;
+$grey-background-color: #E5E5E5;
+$grey-background-hover-color: #EFEFEF;
+$grey-foreground-color: #585858;
+$grey-foreground-hover-color: #303030;
+
 $orange-color: #F1680D;
-$orange-hoover-color: #F97D46;
+$orange-hover-color: #F97D46;
 
 $bg-color: #fff;
 $fg-color: #000;
index 0568de4e2b04a6cbd68a69e0c577fc98ee867bfd..6e502b0288592e6b4a58692f826942d0251f1f15 100644 (file)
@@ -2,7 +2,7 @@
 @import '_mixins';
 
 @import '~primeng/resources/primeng.css';
-@import '~primeng/resources/themes/bootstrap/theme.css';
+@import '~primeng/resources/themes/nova-light/theme.css';
 
 @mixin glyphicon-light {
   font-family: 'Glyphicons Halflings';
 
 // data table customizations
 p-table {
-  font-size: 15px !important;
-
   .ui-table-caption {
-    border: none;
+    border: none !important;
+    background-color: var(--mainBackgroundColor) !important;
 
     .caption {
       height: 40px;
@@ -24,6 +23,17 @@ p-table {
     }
   }
 
+  th {
+    background-color: var(--mainBackgroundColor) !important;
+    outline: 0;
+  }
+
+  td, th {
+    font-family: $main-fonts;
+    font-size: 15px !important;
+    color: var(--mainForegroundColor) !important;
+  }
+
   td {
     padding-left: 15px !important;
 
@@ -35,12 +45,16 @@ p-table {
   }
 
   tr {
+    outline: 0;
     background-color: var(--mainBackgroundColor) !important;
     height: 46px;
 
     &.ui-state-highlight {
-      background-color:var(--submenuColor) !important;
-      color:var(--mainForegroundColor) !important;
+      background-color: var(--submenuColor) !important;
+
+      td, td > a {
+        color: var(--mainForegroundColor) !important;
+      }
     }
   }
 
@@ -56,6 +70,10 @@ p-table {
         }
       }
 
+      td {
+        border: none !important;
+      }
+
       &:first-child td {
         border-top: none !important;
       }
@@ -93,21 +111,25 @@ p-table {
     }
 
     &.ui-state-highlight {
-      background-color:var(--submenuColor) !important;
+      background-color: var(--submenuColor) !important;
 
       .pi {
         @extend .glyphicon;
 
-        color: #000;
-        font-size: 11px;
-        top: 0;
+        color: #000 !important;
+        font-size: 11px !important;
+        top: 0 !important;
 
         &.pi-sort-up {
           @extend .glyphicon-triangle-top;
+
+          color: var(--mainForegroundColor) !important;
         }
 
         &.pi-sort-down {
           @extend .glyphicon-triangle-bottom;
+
+          color: var(--mainForegroundColor) !important;
         }
       }
     }
@@ -175,13 +197,14 @@ p-table {
         height: auto !important;
 
         a {
-          color: #000 !important;
+          color: var(--mainForegroundColor) !important;
           font-weight: $font-semibold !important;
-          margin: 0 10px !important;
+          margin: 0 5px !important;
           outline: 0 !important;
           border-radius: 3px !important;
           padding: 5px 2px !important;
           height: auto !important;
+          line-height: initial !important;
 
           &.ui-state-active {
             &, &:hover, &:active, &:focus {
@@ -210,11 +233,25 @@ p-calendar .ui-datepicker {
     .ui-datepicker-next {
       @extend .glyphicon-chevron-right;
       @include glyphicon-light;
+
+      color: #000 !important;
+      text-align: right;
+
+      .pi.pi-chevron-right {
+        display: none !important;
+      }
     }
 
     .ui-datepicker-prev {
       @extend .glyphicon-chevron-left;
       @include glyphicon-light;
+
+      color: #000 !important;
+      text-align: left;
+
+      .pi.pi-chevron-left {
+        display: none !important;
+      }
     }
   }
 
@@ -223,39 +260,118 @@ p-calendar .ui-datepicker {
     .pi.pi-chevron-up {
       @extend .glyphicon-chevron-up;
       @include glyphicon-light;
+
+      color: #000 !important;
     }
 
     .pi.pi-chevron-down {
       @extend .glyphicon-chevron-down;
       @include glyphicon-light;
+
+      color: #000 !important;
+    }
+  }
+}
+
+.ui-chkbox {
+
+  &, .ui-chkbox-box {
+    width: 18px !important;
+    height: 18px !important;
+  }
+
+  .ui-chkbox-box {
+    &.ui-state-active {
+      border-color: var(--mainColor) !important;
+      background-color: var(--mainColor) !important;
+    }
+
+    .ui-chkbox-icon {
+      position: relative;
+      overflow: visible !important;
+
+      &:after {
+        content: '';
+        position: absolute;
+        top: 1px;
+        left: 6px;
+        width: 5px;
+        height: 12px;
+        opacity: 0;
+        transform: rotate(45deg) scale(0);
+        border-right: 2px solid var(--mainBackgroundColor);
+        border-bottom: 2px solid var(--mainBackgroundColor);
+      }
+
+      &.pi-check:after {
+        opacity: 1;
+        transform: rotate(45deg) scale(1);
+      }
     }
   }
 }
 
-.ui-chkbox-box {
-  &.ui-state-active {
-    border-color: var(--mainColor) !important;
+p-inputswitch {
+  .ui-inputswitch-checked .ui-inputswitch-slider {
     background-color: var(--mainColor) !important;
   }
+}
+
+p-toast {
+  .ui-toast {
+    // Modal is 10005
+    z-index: 10010 !important;
+  }
+
+  .ui-toast-message {
+    font-family: $main-fonts;
+
+    &.ui-toast-message-success {
+      color: #fff !important;
+      background-color: #8BC34A !important;
+    }
+
+    &.ui-toast-message-error {
+      color: #fff !important;
+      background-color: #F44336 !important;
+    }
 
-  .ui-chkbox-icon {
-    position: relative;
-
-    &:after {
-      content: '';
-      position: absolute;
-      left: 5px;
-      width: 5px;
-      height: 12px;
-      opacity: 0;
-      transform: rotate(45deg) scale(0);
-      border-right: 2px solid var(--mainBackgroundColor);
-      border-bottom: 2px solid var(--mainBackgroundColor);
+    &.ui-toast-message-info {
+      color: #fff !important;
+      background-color: #03A9F4 !important;
     }
 
-    &.pi-check:after {
-      opacity: 1;
-      transform: rotate(45deg) scale(1);
+    &.ui-toast-message-info {
+      color: #fff !important;
+      background-color: #03A9F4 !important;
+    }
+
+    .notification-block {
+      display: flex;
+      align-items: center;
+      padding: 5px;
+
+      .message {
+        flex-grow: 1;
+
+        h3 {
+          font-size: 21px;
+        }
+
+        p {
+          font-size: 15px;
+          margin-bottom: 0;
+        }
+      }
+
+      .glyphicon {
+        font-size: 32px;
+        margin-right: 5px;
+      }
     }
   }
-}
\ No newline at end of file
+}
+
+.ui-widget {
+  font-family: $main-fonts !important;
+}
index b7cf13ec27e0ee530d7c38f26a2bd52676911854..f79cf68df5c73e97ff2ac5ce42aeae18145633d9 100644 (file)
@@ -13,7 +13,7 @@
   <body>
 
     <div id="error-block">
-      <h1 id="error-title">Sorry</h1>
+      <h1 id="error-title"></h1>
 
       <div id="error-content"></div>
     </div>
index 7daa03f23b631058ba2ea0aab641c17c4b390b89..54b8fb54300133ca09b635fde06edd335733e6b2 100644 (file)
@@ -157,10 +157,11 @@ class PeerTubeEmbed {
   player: any
   playerOptions: any
   api: PeerTubeEmbedApi = null
-  autoplay = false
-  controls = true
-  muted = false
-  loop = false
+  autoplay: boolean
+  controls: boolean
+  muted: boolean
+  loop: boolean
+  subtitle: string
   enableApi = false
   startTime: number | string = 0
   scope = 'peertube'
@@ -191,34 +192,40 @@ class PeerTubeEmbed {
     element.parentElement.removeChild(element)
   }
 
-  displayError (text: string) {
+  displayError (text: string, translations?: { [ id: string ]: string }) {
     // Remove video element
     if (this.videoElement) this.removeElement(this.videoElement)
 
-    document.title = 'Sorry - ' + text
+    const translatedText = peertubeTranslate(text, translations)
+    const translatedSorry = peertubeTranslate('Sorry', translations)
+
+    document.title = translatedSorry + ' - ' + translatedText
 
     const errorBlock = document.getElementById('error-block')
     errorBlock.style.display = 'flex'
 
+    const errorTitle = document.getElementById('error-title')
+    errorTitle.innerHTML = peertubeTranslate('Sorry', translations)
+
     const errorText = document.getElementById('error-content')
-    errorText.innerHTML = text
+    errorText.innerHTML = translatedText
   }
 
-  videoNotFound () {
+  videoNotFound (translations?: { [ id: string ]: string }) {
     const text = 'This video does not exist.'
-    this.displayError(text)
+    this.displayError(text, translations)
   }
 
-  videoFetchError () {
+  videoFetchError (translations?: { [ id: string ]: string }) {
     const text = 'We cannot fetch the video. Please try again later.'
-    this.displayError(text)
+    this.displayError(text, translations)
   }
 
-  getParamToggle (params: URLSearchParams, name: string, defaultValue: boolean) {
+  getParamToggle (params: URLSearchParams, name: string, defaultValue?: boolean) {
     return params.has(name) ? (params.get(name) === '1' || params.get(name) === 'true') : defaultValue
   }
 
-  getParamString (params: URLSearchParams, name: string, defaultValue: string) {
+  getParamString (params: URLSearchParams, name: string, defaultValue?: string) {
     return params.has(name) ? params.get(name) : defaultValue
   }
 
@@ -241,15 +248,15 @@ class PeerTubeEmbed {
     try {
       let params = new URL(window.location.toString()).searchParams
 
-      this.autoplay = this.getParamToggle(params, 'autoplay', this.autoplay)
-      this.controls = this.getParamToggle(params, 'controls', this.controls)
-      this.muted = this.getParamToggle(params, 'muted', this.muted)
-      this.loop = this.getParamToggle(params, 'loop', this.loop)
+      this.autoplay = this.getParamToggle(params, 'autoplay')
+      this.controls = this.getParamToggle(params, 'controls')
+      this.muted = this.getParamToggle(params, 'muted')
+      this.loop = this.getParamToggle(params, 'loop')
       this.enableApi = this.getParamToggle(params, 'api', this.enableApi)
-      this.scope = this.getParamString(params, 'scope', this.scope)
 
-      const startTimeParamString = params.get('start')
-      if (startTimeParamString) this.startTime = startTimeParamString
+      this.scope = this.getParamString(params, 'scope', this.scope)
+      this.subtitle = this.getParamString(params, 'subtitle')
+      this.startTime = this.getParamString(params, 'start')
     } catch (err) {
       console.error('Cannot get params from URL.', err)
     }
@@ -267,9 +274,9 @@ class PeerTubeEmbed {
     ])
 
     if (!videoResponse.ok) {
-      if (videoResponse.status === 404) return this.videoNotFound()
+      if (videoResponse.status === 404) return this.videoNotFound(serverTranslations)
 
-      return this.videoFetchError()
+      return this.videoFetchError(serverTranslations)
     }
 
     const videoInfo: VideoDetails = await videoResponse.json()
@@ -291,6 +298,7 @@ class PeerTubeEmbed {
       muted: this.muted,
       loop: this.loop,
       startTime: this.startTime,
+      subtitle: this.subtitle,
 
       videoCaptions,
       inactivityTimeout: 1500,
@@ -306,7 +314,7 @@ class PeerTubeEmbed {
 
     this.playerOptions = videojsOptions
     this.player = vjs(this.videoContainerId, videojsOptions, () => {
-      this.player.on('customError', (event: any, data: any) => this.handleError(data.err))
+      this.player.on('customError', (event: any, data: any) => this.handleError(data.err, serverTranslations))
 
       window[ 'videojsPlayer' ] = this.player
 
@@ -323,11 +331,11 @@ class PeerTubeEmbed {
     })
   }
 
-  private handleError (err: Error) {
+  private handleError (err: Error, translations?: { [ id: string ]: string }) {
     if (err.message.indexOf('from xs param') !== -1) {
       this.player.dispose()
       this.videoElement = null
-      this.displayError('This video is not available because the remote instance is not responding.')
+      this.displayError('This video is not available because the remote instance is not responding.', translations)
       return
     }
   }
index 928dec01e239fa7fda3b0043ff01237dc96a4ba7..dee67c41429ebd02c2d7f5067c065cc38a61ff98 100644 (file)
@@ -2,26 +2,26 @@
 # yarn lockfile v1
 
 
-"@angular-devkit/architect@0.10.6":
-  version "0.10.6"
-  resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.10.6.tgz#7007e7591be21eeb478951106c84c83802ca21a4"
-  integrity sha512-IygpkXNn946vVUFFWKWEDxRqRy888vOAUWcmkZzqPEBYkuwWt7WnLfe8Sjw4fH/+HLWEMS8RXbdSTHiiaP9qOg==
+"@angular-devkit/architect@0.11.1":
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/@angular-devkit/architect/-/architect-0.11.1.tgz#fb8429b583d4d7efafe5ff551ffdd30a705b9ab0"
+  integrity sha512-MdcZ5KclwL2SBXCQSn8uI2hakBX58EyuAwFWsM/pKrNt9j8RqIk93l4amd2OkaMtZRFP5zWodyf/3qOwacjuQg==
   dependencies:
-    "@angular-devkit/core" "7.0.6"
+    "@angular-devkit/core" "7.1.1"
     rxjs "6.3.3"
 
-"@angular-devkit/build-angular@~0.10.0":
-  version "0.10.6"
-  resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.10.6.tgz#9c713a786de89a68063bd9e86516eb450f2dac72"
-  integrity sha512-Lbx6rjIGB2mMmkTCaolrQ86OfPxO/qfb4l2RvPiSyx06MEZfmFWKGeJzqCYKBRQajziX3Yc3AFzAPecoCkbIGA==
-  dependencies:
-    "@angular-devkit/architect" "0.10.6"
-    "@angular-devkit/build-optimizer" "0.10.6"
-    "@angular-devkit/build-webpack" "0.10.6"
-    "@angular-devkit/core" "7.0.6"
-    "@ngtools/webpack" "7.0.6"
+"@angular-devkit/build-angular@~0.11.1":
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/@angular-devkit/build-angular/-/build-angular-0.11.1.tgz#a828797d9177227aee70a65bb06d4b189b1404cd"
+  integrity sha512-hA/3GVMmRwOPXWhImrBG9gZTdERr937NMuedKhTXuNj6TNMNjk9XQ+q2erd0LZVbgfhL/nC0wHnpy0dUWXu8jA==
+  dependencies:
+    "@angular-devkit/architect" "0.11.1"
+    "@angular-devkit/build-optimizer" "0.11.1"
+    "@angular-devkit/build-webpack" "0.11.1"
+    "@angular-devkit/core" "7.1.1"
+    "@ngtools/webpack" "7.1.1"
     ajv "6.5.3"
-    autoprefixer "9.1.5"
+    autoprefixer "9.3.1"
     circular-dependency-plugin "5.0.2"
     clean-css "4.2.1"
     copy-webpack-plugin "4.5.4"
@@ -34,7 +34,7 @@
     less-loader "4.1.0"
     license-webpack-plugin "2.0.2"
     loader-utils "1.1.0"
-    mini-css-extract-plugin "0.4.3"
+    mini-css-extract-plugin "0.4.4"
     minimatch "3.0.4"
     opn "5.3.0"
     parse5 "4.0.0"
     semver "5.5.1"
     source-map-loader "0.2.4"
     source-map-support "0.5.9"
-    speed-measure-webpack-plugin "^1.2.3"
+    speed-measure-webpack-plugin "1.2.3"
     stats-webpack-plugin "0.7.0"
-    style-loader "0.23.0"
+    style-loader "0.23.1"
     stylus "0.54.5"
     stylus-loader "3.0.2"
     terser-webpack-plugin "1.1.0"
     tree-kill "1.2.0"
-    webpack "4.19.1"
-    webpack-dev-middleware "3.3.0"
-    webpack-dev-server "3.1.8"
+    webpack "4.23.1"
+    webpack-dev-middleware "3.4.0"
+    webpack-dev-server "3.1.10"
     webpack-merge "4.1.4"
-    webpack-sources "1.2.0"
+    webpack-sources "1.3.0"
     webpack-subresource-integrity "1.1.0-rc.6"
   optionalDependencies:
-    node-sass "4.9.3"
+    node-sass "4.10.0"
 
-"@angular-devkit/build-optimizer@0.10.6":
-  version "0.10.6"
-  resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.10.6.tgz#ca7db9b3d5378b2759509692f02a5fb5af273dd0"
-  integrity sha512-oedg8F++8zZTmoTt141k3nlyPtrSSsQUZI9TFbSdfR1D5WDflwOlkLyRb5WoC53HSoQnagKxY2qzd7khVah//Q==
+"@angular-devkit/build-optimizer@0.11.1":
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.11.1.tgz#1079737a44b26b39e35cea7f966a39cd11bbbf48"
+  integrity sha512-pyFP6ykZf8Iq8nRkgP2XKq8knpIG6ye0qYklnBC9815AC5RAO126Y4fmtd6tnH+5p1mQxnt5HegG0j5xOCgDRw==
   dependencies:
     loader-utils "1.1.0"
     source-map "0.5.6"
     typescript "3.1.6"
     webpack-sources "1.2.0"
 
-"@angular-devkit/build-webpack@0.10.6":
-  version "0.10.6"
-  resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.10.6.tgz#d3acb781f97406a49a3e3adfcc49a8518d33e291"
-  integrity sha512-tPv23KKw3iAGCTF6noD7zdHbufny4A3d+mlX1VoJDiAa6jqmuFxhY2fALymc11MRY4HVtMF5J1kQy9BLGCDbQg==
+"@angular-devkit/build-webpack@0.11.1":
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/@angular-devkit/build-webpack/-/build-webpack-0.11.1.tgz#bd98ff3dea633c5b77671b471e72cf6c91f6c679"
+  integrity sha512-p7fPHOi2Wfq2VPtnRVowg3n99MujghpOp6zW0gBJQD1TQhGVzPK6AX42S0NA4d05ahNBCDU2n7Y+5TjNJRIGJw==
   dependencies:
-    "@angular-devkit/architect" "0.10.6"
-    "@angular-devkit/core" "7.0.6"
+    "@angular-devkit/architect" "0.11.1"
+    "@angular-devkit/core" "7.1.1"
     rxjs "6.3.3"
 
-"@angular-devkit/core@7.0.6":
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.0.6.tgz#26c4cd4d271e8cd03f6e50b4ec30cbc606f3346e"
-  integrity sha512-RPSXUtLrpYDTqAEL0rCyDKxES76EomsPBvUUZTD6UkE2pihoh9ZIxkzhzlE+HU/xdqm28+smQYFhvvEAXFWwSQ==
+"@angular-devkit/core@7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.1.1.tgz#ce0a674f16188072988502cc3f073b15efcfe194"
+  integrity sha512-rODqECpOiV6vX+L1qd63GLiF3SG+V1O+d8WYtnKPOxnsMM9yWpWmqmroHtXfisjucu/zwoqj8HoO/noJZCfynw==
   dependencies:
     ajv "6.5.3"
     chokidar "2.0.4"
     rxjs "6.3.3"
     source-map "0.7.3"
 
-"@angular-devkit/schematics@7.0.6":
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.0.6.tgz#97fca028bd937e2319d9d34c12b82e8d1d99de23"
-  integrity sha512-S/3CrBDoh/BD4mBq8RNGQ8sgNFDsveCuFHDkOyct8+NDg2wcRkEGigyq8eZwVN/iVKCwjxc0I/bC336edoNMIQ==
+"@angular-devkit/schematics@7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.1.1.tgz#328ec6071c5ef3b1588a9f4bc97f5edfc3620b09"
+  integrity sha512-yjzTw8ZWMPg0Fc9VQCHNpUCAH7aiNxrUDs0IbhdC0CyKTBoqH+cx2xP4Z6ECf4uNwceLKJlE0l3ot42Ypnlziw==
   dependencies:
-    "@angular-devkit/core" "7.0.6"
+    "@angular-devkit/core" "7.1.1"
     rxjs "6.3.3"
 
-"@angular/animations@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.0.4.tgz#f53fc9f1bce3bf1afe60dcbc08b6863472f519e4"
-  integrity sha512-QfFikT0FzYNMjdVg0LWTBijdu9JDJyzejnhCFlXxv+KR4zolpRK98/rU7CFW1Fg2jjL3/yL9PT1sf5I0fTJZYA==
+"@angular/animations@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-7.1.1.tgz#8fecbd19417364946a9ea40c8fdf32462110232f"
+  integrity sha512-iTNxhPPraCZsE4rgM23lguT1kDV4mfYAr+Bsi5J0+v9ZJA+VaKvi6eRW8ZGrx4/rDz6hzTnBn1jgPppHFbsOcw==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/cli@~7.0.4":
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-7.0.6.tgz#f97bc9ca92785c7ce2e5f20819c48265a1da5b53"
-  integrity sha512-f76kq8AQMkloeojIffeT7DYLXT/J4DRhYoAPQR4E09V7lkigFCILiYzQs5RtCAX6EjlPxlrZKkdfnBn0OUPnig==
-  dependencies:
-    "@angular-devkit/architect" "0.10.6"
-    "@angular-devkit/core" "7.0.6"
-    "@angular-devkit/schematics" "7.0.6"
-    "@schematics/angular" "7.0.6"
-    "@schematics/update" "0.10.6"
+"@angular/cli@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-7.1.1.tgz#c5dd2b92c5c3391f20262b5e530813e4e2d31777"
+  integrity sha512-lPVKsk035T5Ls0Mf83OngrNoLZu/ucZSjRLN/GWZK1O/YYVmb/dTgVl/a7HC+G480tWQ34nlqnCRbrP7sE9v7g==
+  dependencies:
+    "@angular-devkit/architect" "0.11.1"
+    "@angular-devkit/core" "7.1.1"
+    "@angular-devkit/schematics" "7.1.1"
+    "@schematics/angular" "7.1.1"
+    "@schematics/update" "0.11.1"
     inquirer "6.2.0"
     opn "5.3.0"
-    rxjs "6.3.3"
     semver "5.5.1"
     symbol-observable "1.2.0"
 
-"@angular/common@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.0.4.tgz#aafb26ce59c967daa5b122393e1933208a247f72"
-  integrity sha512-akQojdqY/RBlItkDWAPI3k0Llk1wnbAp+f47yySi3cgQz9SaZ1/RLNWZV84I/cKrksb4ehorT/lTqRBojsAD1A==
+"@angular/common@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/common/-/common-7.1.1.tgz#f78f884614ef81ab2fd648f1aa3e83aae370a6c8"
+  integrity sha512-SngekFx9v39sjgi9pON0Wehxpu+NdUk7OEebw4Fa8dKqTgydTkuhmnNH+9WQe264asoeCt51oufPRjIqMLNohA==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/compiler-cli@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-7.0.4.tgz#f96fc1c0aec27ee97ec5f13a8eb72bc8b964db97"
-  integrity sha512-kvhWt6OTb1Uduns9Vm+Dwd/UUBNSEU6Jgu+QOPeHr7lg+4NTyr9uQLU0DtfBP0ljOlds8esmfii5IIFTeUQw1Q==
+"@angular/compiler-cli@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-7.1.1.tgz#c5f6225fb72b56f42fa78c332fdee9755c64604e"
+  integrity sha512-4NXlkDhOEQgaP3Agigqw93CvXJvsfnXa0xiglq9e/wjL+6XbtM9WcDb5lfRQz41N9RSkO3pEHGvKMweKZGgogA==
   dependencies:
     canonical-path "1.0.0"
     chokidar "^1.4.2"
     tslib "^1.9.0"
     yargs "9.0.1"
 
-"@angular/compiler@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.0.4.tgz#df91dab990c46b464705b0901d70d1cfdfd190e1"
-  integrity sha512-ExDhH1cJkuJkUsgNRZyZBse0a7wWkQyG5O8HONi3Rzig9dalFEuve9jD04zfA1Jx1GTXhovqtGnF72x4kw0V8Q==
+"@angular/compiler@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-7.1.1.tgz#4efbcad27ab43d4cd36d936a8df2e073f6d02d0a"
+  integrity sha512-oJvBe8XZ+DXF/W/DxWBTbBcixJTuPeZWdkcZIGWhJoQP7K5GnGnj8ffP9Lp6Dh4TKv85awtC6OfIKhbHxa650Q==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/core@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.0.4.tgz#98340a1bdb53f0bbecfcfc9831a7a22a1540d79b"
-  integrity sha512-17SSmCz1wQoZKnVHF/T8UkWYPpDm5kPyoc1okkTTv8ZA2EAMMuZFFnRSAxEL5i7mNB9z5CvRqF2tRx/DbgbIRA==
+"@angular/core@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/core/-/core-7.1.1.tgz#9748b0103cd86226554e1ccbd0f43dd8c46f1ed1"
+  integrity sha512-Osig5SRgDRQ+Hec/liN7nq/BCJieB+4/pqRh9rFbOXezb2ptgRZqdXOXN8P17i4AwPVf308Mh55V0niJ5Eu3Rw==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/forms@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.0.4.tgz#5f2328d297f5c7f9f3af81e1f76a13e1546c743f"
-  integrity sha512-W3nN9n1VY9On9+9f7PDRbzJUg+mMq1bjkhWsk/b7DfaYdmlzpG+Wd6OfArob2edsqGqH1dvTM8q8aGbWiFZ7dA==
+"@angular/forms@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-7.1.1.tgz#d16ef10a901c007062fd19144cd77917ef55ee24"
+  integrity sha512-yCWuPjpu23Wc3XUw7v/ACNn/e249oT0bYlM8aaMQ1F5OwrmmC4NJC12Rpl9Ihza61RIHIKzNcHVEgzc7WhcSag==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/http@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/http/-/http-7.0.4.tgz#445d6a812d25ea1656fc3e3381ef82b3f98fccb4"
-  integrity sha512-oUGT7xS7FZYajuHq0DP6MgahacB5sJTRgxiUU4uhQ/mqV7aREODVJJgw7oHDhM7Cnyzzo0B9D0zpEljKmeCLWQ==
+"@angular/http@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/http/-/http-7.1.1.tgz#f19f17ad42e7f3cdabcf1250ca757640d0f02219"
+  integrity sha512-pRk+c/kz9aJ8te5xzCxlPLpFnwB0d/E9YkOo3/ydaXF9vZw13RTzk00YyzJ41PDzJf8oPDdXtueTQ+vtJ7Srtw==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/language-service@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-7.0.4.tgz#db221f183725ff54c1188aec7acb2948e29f4c50"
-  integrity sha512-CuJ2Ii97sNoN1HOZOLxG1lEHsQFi8K/RSB/k2suWPKzdM53ldSkKoYRac38zW/uqNABYItgvxb7w0Vi7HhxLsg==
+"@angular/language-service@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-7.1.1.tgz#6bbe35b2430ad54618a1803f881efb5894b296c9"
+  integrity sha512-X+5g20PMtNRGZIa3svMv4PLJdJehn4wqrS8nwOtzH5XkSn5vA3IxjsJVdSzAy2AN0/sKKJK5jmQorPtKO4saJg==
 
-"@angular/platform-browser-dynamic@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.0.4.tgz#69abb8c784bb71a660a0c824ca4a1a4960811a33"
-  integrity sha512-k1I53zIg8YWhtQizLfq/tWrUUdY5vHV8pGHyt0/UTGDqat5TORd6LDFfzCSux0r3qZujCOGNi9f4/AbyV8B9lw==
+"@angular/platform-browser-dynamic@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-7.1.1.tgz#6945298446173338782f437a996226110cda0d3e"
+  integrity sha512-ZIu48Vn4S6gjD7CMbGlKGaPQ8v9rYkWzlNYi4vTYzgiqKKNC3hqLsVESU3mSvr5oeQBxSIBidTdHSyafHFrA2w==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/platform-browser@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.0.4.tgz#57dfaa23f8a3d678bad6ca110051e3ac6622ff3d"
-  integrity sha512-4brYZZgsCJk1/a6JoSwaiVWO9+/T4iyE27dAgstao1nOf/jrBNKW2HnZtkWZmCCBK0WIk15wlB0Xr87OZbjNVA==
+"@angular/platform-browser@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-7.1.1.tgz#a6bd408f656dc43ee5a2d8af3dfaa786c7c1dfca"
+  integrity sha512-I6OPjecynGJSbPtzu0gvEgSmIR6X6/xEAhg4L9PycW1ryjzptTC9klWRTWIqsIBqMxhVnY44uKLeRNrDwMOwyA==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/router@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/router/-/router-7.0.4.tgz#ae2c32cc6a29bfe6eb909b9c63257d187075f4ff"
-  integrity sha512-nt1jJsxN+JmYZ6URamMdULUpH4aHdnNVKjWtjDI0OpdZvPx7PMFD8cfc92q0tavy2KqqexcceIb4BIC965gtpA==
+"@angular/router@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/router/-/router-7.1.1.tgz#80a4cdffc03a529b73485c2ad63a30ec435364ea"
+  integrity sha512-jbnqEq/1iDBkeH8Vn13hauGPTzhwllWM+MLfmdNGTiMzGRx4pmkWa57seDOeBF/GNYBL9JjkWTCrkKFAc2FJKw==
   dependencies:
     tslib "^1.9.0"
 
-"@angular/service-worker@~7.0.2":
-  version "7.0.4"
-  resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-7.0.4.tgz#be274843ae29cb69ac969c078edd8ae5b25e3e61"
-  integrity sha512-vBA9T1xeCP6QesOYhMyVpXTUVdXU4eMYdoZItHncyom8AxS2a26FB8zLW72VXdEfZ7xnJgcDtsYzYzVi+3DXsQ==
+"@angular/service-worker@~7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@angular/service-worker/-/service-worker-7.1.1.tgz#c9e6f0265d7e102d8271483519cf09a180f0e08b"
+  integrity sha512-xX00x0XMW47jEfYTZLwdJCqkmPE7+mdtlSeOGpjaKv6Y2hqZodz80RYgH5JltM4RKEzOvQolR6KmdKcw1ANs9Q==
   dependencies:
     tslib "^1.9.0"
 
     "@babel/highlight" "^7.0.0"
 
 "@babel/generator@^7.0.0", "@babel/generator@^7.1.6":
-  version "7.1.6"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.6.tgz#001303cf87a5b9d093494a4bf251d7b5d03d3999"
-  integrity sha512-brwPBtVvdYdGxtenbQgfCdDPmtkmUBZPjUoK5SXJEBuHaA5BCubh9ly65fzXz7R6o5rA76Rs22ES8Z+HCc0YIQ==
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.0.tgz#eaf3821fa0301d9d4aef88e63d4bcc19b73ba16c"
+  integrity sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==
   dependencies:
-    "@babel/types" "^7.1.6"
+    "@babel/types" "^7.2.0"
     jsesc "^2.5.1"
     lodash "^4.17.10"
     source-map "^0.5.0"
     js-tokens "^4.0.0"
 
 "@babel/parser@^7.0.0", "@babel/parser@^7.1.2", "@babel/parser@^7.1.6":
-  version "7.1.6"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.6.tgz#16e97aca1ec1062324a01c5a6a7d0df8dd189854"
-  integrity sha512-dWP6LJm9nKT6ALaa+bnL247GHHMWir3vSlZ2+IHgHgktZQx0L3Uvq2uAWcuzIe+fujRsYWBW2q622C5UvGK9iQ==
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.0.tgz#02d01dbc330b6cbf36b76ac93c50752c69027065"
+  integrity sha512-M74+GvK4hn1eejD9lZ7967qAwvqTZayQa3g10ag4s9uewgR7TKjeaT0YMyoq+gVfKYABiWZ4MQD701/t5e1Jhg==
 
 "@babel/runtime@^7.0.0":
-  version "7.1.5"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.1.5.tgz#4170907641cf1f61508f563ece3725150cc6fe39"
-  integrity sha512-xKnPpXG/pvK1B90JkwwxSGii90rQGKtzcMt2gI5G6+M0REXaq6rOHsGC2ay6/d0Uje7zzvSzjEzfR3ENhFlrfA==
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f"
+  integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg==
   dependencies:
     regenerator-runtime "^0.12.0"
 
     globals "^11.1.0"
     lodash "^4.17.10"
 
-"@babel/types@^7.0.0", "@babel/types@^7.1.2", "@babel/types@^7.1.6":
-  version "7.1.6"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.6.tgz#0adb330c3a281348a190263aceb540e10f04bcce"
-  integrity sha512-DMiUzlY9DSjVsOylJssxLHSgj6tWM9PRFJOGW/RaOglVOK9nzTxoOMfTfRQXGUCUQ/HmlG2efwC+XqUEJ5ay4w==
+"@babel/types@^7.0.0", "@babel/types@^7.1.2", "@babel/types@^7.1.6", "@babel/types@^7.2.0":
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.0.tgz#7941c5b2d8060e06f9601d6be7c223eef906d5d8"
+  integrity sha512-b4v7dyfApuKDvmPb+O488UlGuR1WbwMXFsO/cyqMrnfvRAChZKJAYeeglWTjUO1b9UghKKgepAQM5tsvBJca6A==
   dependencies:
     esutils "^2.0.2"
     lodash "^4.17.10"
   dependencies:
     tslib "^1.9.0"
 
-"@ngtools/webpack@7.0.6":
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-7.0.6.tgz#cc4f4189765f6743417b4eee946fb69590f93ce1"
-  integrity sha512-lOHpVqr30QXPuaOxSRasHv6ybDj4a1jVwSOk+W4aGqVlLi0bsngt9HrvgR+FALEoG9P520bytz16wma81Y2Aeg==
+"@ngtools/webpack@7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-7.1.1.tgz#c418e1cb0d70a77d06e8c32500fe2e92e606ea52"
+  integrity sha512-XW/YDjiDZlwOYK4YvGAIKIVEkqtdwPLwTWAmDbnfpEHQc8UALsBrzGdjze0jSfXQdQxkbmXo0aolZgNc7uL/wQ==
   dependencies:
-    "@angular-devkit/core" "7.0.6"
+    "@angular-devkit/core" "7.1.1"
     enhanced-resolve "4.1.0"
     rxjs "6.3.3"
     tree-kill "1.2.0"
     webpack-sources "1.2.0"
 
-"@ngx-loading-bar/core@2.2.0", "@ngx-loading-bar/core@^2.2.0":
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/@ngx-loading-bar/core/-/core-2.2.0.tgz#ad313bbbd69e4c52cc2d6f0a8b5911272371d16a"
-  integrity sha512-0jcnEzuhqE/c+4iAumJ/0D4GBWm4RRVas0+qXpX4Wm225SJoE5KupUOlMrvLnJNK2bn8NW31dEj80kJ+UzhE5A==
+"@ngx-loading-bar/core@3.0.0", "@ngx-loading-bar/core@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@ngx-loading-bar/core/-/core-3.0.0.tgz#86c6d036b5ad50950b5ea526db585f39d44f396a"
+  integrity sha512-DBH+bKf8M9uSk2791HbtN/JvcEmBxEbUCiOJ6PYrjbginH6dUn2NsSxnv3myu0lpx+7K3MusXLNImkB5JUh2QA==
   dependencies:
     tslib "^1.7.1"
 
-"@ngx-loading-bar/http-client@^2.2.0":
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/@ngx-loading-bar/http-client/-/http-client-2.2.0.tgz#4b5443feed5c53bc5b5f06119f771edbe89799f4"
-  integrity sha512-+eilxs10KncQWg7DQJLK2AoWnmTPidhVHNxfTOPHJVnmcyAFmTtk+lQbf5Ke3aC4d/KXZklkRyBizqDfvRvc9w==
+"@ngx-loading-bar/http-client@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@ngx-loading-bar/http-client/-/http-client-3.0.0.tgz#2fce2f60da37a48f2173ef4f08189eecf98d9215"
+  integrity sha512-7AHM3tmA2FDFXsKbL09vnRqqxdztpjilkP3U9zXB5Lkv3XtlJnZoQKFCSbmEusw30k3oAmY5id/ZE9X83uekZg==
   dependencies:
-    "@ngx-loading-bar/core" "2.2.0"
+    "@ngx-loading-bar/core" "3.0.0"
     tslib "^1.7.1"
 
-"@ngx-loading-bar/router@^2.2.0":
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/@ngx-loading-bar/router/-/router-2.2.0.tgz#c13c1a05c620a9da102102322685b671d3c9a1ba"
-  integrity sha512-/lrWc0ZwGcpmuoa26/h0rC7SRVKgCtsikhy0mVXwrb1VVJ+sRU8vNKbq7aidcvEY5vdi3l0Z7DcVq9+JV/i/BQ==
+"@ngx-loading-bar/router@^3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@ngx-loading-bar/router/-/router-3.0.0.tgz#627e73be406dfdff48175f75c110bebd4f6fa588"
+  integrity sha512-UL3GaFyFfHwgGeAdSEG800Rl3GuBfKydPp21lD/paqdsrUT2Z1cBLAiVFw7U4QdnSLaPzngYr9bzgDFdy4GqYQ==
   dependencies:
-    "@ngx-loading-bar/core" "2.2.0"
+    "@ngx-loading-bar/core" "3.0.0"
     tslib "^1.7.1"
 
 "@ngx-meta/core@^6.0.0-rc.1":
     tslib "^1.9.0"
     yargs "10.0.3"
 
-"@schematics/angular@7.0.6":
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.0.6.tgz#1173c201d118cf38d1afdb382e9a916e4b540a17"
-  integrity sha512-jOHL+vSu1cqAo3kRNDmgkq/GR2EDkJx5/h0VXGlbtdEpq892LipKHtyPgXa269AABgPKb3TSNBwZls6g2L9FCw==
+"@schematics/angular@7.1.1":
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-7.1.1.tgz#4ee17a17d221eaf48009db0b991766d1074d0b4f"
+  integrity sha512-jMaj8y3rNTQQXuH38uoWfAOmwYjtzqo1RelNfACnT54mfO/Dat+k7WasBLHWuvzvnN4/Ga3kXL7sJpkeMciiIg==
   dependencies:
-    "@angular-devkit/core" "7.0.6"
-    "@angular-devkit/schematics" "7.0.6"
+    "@angular-devkit/core" "7.1.1"
+    "@angular-devkit/schematics" "7.1.1"
     typescript "3.1.6"
 
-"@schematics/update@0.10.6":
-  version "0.10.6"
-  resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.10.6.tgz#616e6c321cd51468eacda7fc8a0306b37f8b4ca2"
-  integrity sha512-Yy/M4JosrVDb5tbpmi+v1uTHSmBYISOiuFVuxtpMN5DWdDNq/JTBEw2jy3quelGWHCU06rbGo578Ml3azGZ+9g==
-  dependencies:
-    "@angular-devkit/core" "7.0.6"
-    "@angular-devkit/schematics" "7.0.6"
-    npm-registry-client "8.6.0"
+"@schematics/update@0.11.1":
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.11.1.tgz#5129a800043dc38ee1f1c879865e0df82ddac7ed"
+  integrity sha512-IzPXamoMpDb2eY2zSW4fPuuH+7RfJLte9XVzQM2y3ZTBhlJQFLqx7qJtOXdcXUboonC6o61KCayNDERFnDUdPg==
+  dependencies:
+    "@angular-devkit/core" "7.1.1"
+    "@angular-devkit/schematics" "7.1.1"
+    "@yarnpkg/lockfile" "1.1.0"
+    ini "1.3.5"
+    pacote "9.1.1"
     rxjs "6.3.3"
     semver "5.5.1"
     semver-intersect "1.4.0"
   resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.0.tgz#35cc282488de6f10af1d92902899a3b8ca3fbc47"
   integrity sha512-qjkHL3wF0JMHMqgm/kmL8Pf8rIiqvueEiZ0g6NVTcBX1WN46GWDr+V5z+gsHUeL0n8TfAmXnYmF7ajsxmBp4PQ==
 
-"@types/jasmine@*", "@types/jasmine@^2.8.7":
-  version "2.8.11"
-  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.11.tgz#0b5eba9e02616736b1a189112eacc163c3773b7b"
-  integrity sha512-ITPYT5rkV9S0BcucyBwXIUzqzSODVhvAzhOGV0bwZMuqWJeU0Kfdd6IJeJjGI8Gob+lDyAtKaWUfhG6QXJIPRg==
+"@types/jasmine@*":
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-3.3.1.tgz#b6c4f356013364e98b583647c7b3b6de6fccd2cc"
+  integrity sha512-JnKB+cEIFuQZXizZP6N0zxma+JlvowkjefWuL61otVmXN7Ebbs4ka3IbDVIz1pc+TCiT00q925jANz3gQJ9qXw==
+
+"@types/jasmine@^2.8.7":
+  version "2.8.12"
+  resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.12.tgz#dfe606b07686c977f54d17cb8ebe6cae2e26f8ff"
+  integrity sha512-eE+xeiGBPgQsNcyg61JBqQS6NtxC+s2yfOikMCnc0Z4NqKujzmSahmtjLCKVQU/AyrTEQ76TOwQBnr8wGP2bmA==
 
 "@types/jasminewd2@^2.0.3":
   version "2.0.6"
     "@types/jasmine" "*"
 
 "@types/jest@^23.3.1":
-  version "23.3.9"
-  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.9.tgz#c16b55186ee73ae65e001fbee69d392c51337ad1"
-  integrity sha512-wNMwXSUcwyYajtbayfPp55tSayuDVU6PfY5gzvRSj80UvxdXEJOVPnUVajaOp7NgXLm+1e2ZDLULmpsU9vDvQw==
+  version "23.3.10"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-23.3.10.tgz#4897974cc317bf99d4fe6af1efa15957fa9c94de"
+  integrity sha512-DC8xTuW/6TYgvEg3HEXS7cu9OijFqprVDXXiOcdOKZCU/5PJNLZU37VVvmZHdtMiGOa8wAA/We+JzbdxFzQTRQ==
 
 "@types/jschannel@^1.0.0":
   version "1.0.1"
   integrity sha512-Jn2cF8X6RAMiSmJaATGjf2r3GzIfpZQpvnQhKprQ5sAbMaNXc7hc9sA2XHdMl3bEMEQhTV79JVW7n4Pgg7sjtg==
 
 "@types/node@*", "@types/node@^10.9.2":
-  version "10.12.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.8.tgz#d0a3ab5a6e61458c492304e2776ac136b81db927"
-  integrity sha512-INamyRZG4rW3lDCUmwVd5Xho/bXvQm/v1yP8V0UN1RuInU7RoWoaO570b+yLX4Ia/0szsx1wa8VzcsVlsvbWLA==
+  version "10.12.12"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47"
+  integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==
 
 "@types/node@^6.0.46":
   version "6.14.2"
   dependencies:
     "@types/node" "*"
 
+"@types/socket.io-client@^1.4.32":
+  version "1.4.32"
+  resolved "https://registry.yarnpkg.com/@types/socket.io-client/-/socket.io-client-1.4.32.tgz#988a65a0386c274b1c22a55377fab6a30789ac14"
+  integrity sha512-Vs55Kq8F+OWvy1RLA31rT+cAyemzgm0EWNeax6BWF8H7QiiOYMJIdcwSDdm5LVgfEkoepsWkS+40+WNb7BUMbg==
+
 "@types/video.js@^7.2.5":
   version "7.2.5"
   resolved "https://registry.yarnpkg.com/@types/video.js/-/video.js-7.2.5.tgz#20896c81141d3517c3a89bb6eb97c6a191aa5d4c"
     url-toolkit "^2.1.3"
     video.js "^6.8.0 || ^7.0.0"
 
-"@webassemblyjs/ast@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
-  integrity sha512-ZEzy4vjvTzScC+SH8RBssQUawpaInUdMTYwYYLh54/s8TuT0gBLuyUnppKsVyZEi876VmmStKsUs28UxPgdvrA==
-  dependencies:
-    "@webassemblyjs/helper-module-context" "1.7.11"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
-    "@webassemblyjs/wast-parser" "1.7.11"
-
-"@webassemblyjs/ast@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.6.tgz#3ef8c45b3e5e943a153a05281317474fef63e21e"
-  integrity sha512-8nkZS48EVsMUU0v6F1LCIOw4RYWLm2plMtbhFTjNgeXmsTNLuU3xTRtnljt9BFQB+iPbLRobkNrCWftWnNC7wQ==
-  dependencies:
-    "@webassemblyjs/helper-module-context" "1.7.6"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
-    "@webassemblyjs/wast-parser" "1.7.6"
-    mamacro "^0.0.3"
-
-"@webassemblyjs/floating-point-hex-parser@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.11.tgz#a69f0af6502eb9a3c045555b1a6129d3d3f2e313"
-  integrity sha512-zY8dSNyYcgzNRNT666/zOoAyImshm3ycKdoLsyDw/Bwo6+/uktb7p4xyApuef1dwEBo/U/SYQzbGBvV+nru2Xg==
-
-"@webassemblyjs/floating-point-hex-parser@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.6.tgz#7cb37d51a05c3fe09b464ae7e711d1ab3837801f"
-  integrity sha512-VBOZvaOyBSkPZdIt5VBMg3vPWxouuM13dPXGWI1cBh3oFLNcFJ8s9YA7S9l4mPI7+Q950QqOmqj06oa83hNWBA==
-
-"@webassemblyjs/helper-api-error@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.11.tgz#c7b6bb8105f84039511a2b39ce494f193818a32a"
-  integrity sha512-7r1qXLmiglC+wPNkGuXCvkmalyEstKVwcueZRP2GNC2PAvxbLYwLLPr14rcdJaE4UtHxQKfFkuDFuv91ipqvXg==
-
-"@webassemblyjs/helper-api-error@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.6.tgz#99b7e30e66f550a2638299a109dda84a622070ef"
-  integrity sha512-SCzhcQWHXfrfMSKcj8zHg1/kL9kb3aa5TN4plc/EREOs5Xop0ci5bdVBApbk2yfVi8aL+Ly4Qpp3/TRAUInjrg==
-
-"@webassemblyjs/helper-buffer@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.11.tgz#3122d48dcc6c9456ed982debe16c8f37101df39b"
-  integrity sha512-MynuervdylPPh3ix+mKZloTcL06P8tenNH3sx6s0qE8SLR6DdwnfgA7Hc9NSYeob2jrW5Vql6GVlsQzKQCa13w==
-
-"@webassemblyjs/helper-buffer@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.6.tgz#ba0648be12bbe560c25c997e175c2018df39ca3e"
-  integrity sha512-1/gW5NaGsEOZ02fjnFiU8/OEEXU1uVbv2um0pQ9YVL3IHSkyk6xOwokzyqqO1qDZQUAllb+V8irtClPWntbVqw==
-
-"@webassemblyjs/helper-code-frame@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.11.tgz#cf8f106e746662a0da29bdef635fcd3d1248364b"
-  integrity sha512-T8ESC9KMXFTXA5urJcyor5cn6qWeZ4/zLPyWeEXZ03hj/x9weSokGNkVCdnhSabKGYWxElSdgJ+sFa9G/RdHNw==
-  dependencies:
-    "@webassemblyjs/wast-printer" "1.7.11"
-
-"@webassemblyjs/helper-code-frame@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.6.tgz#5a94d21b0057b69a7403fca0c253c3aaca95b1a5"
-  integrity sha512-+suMJOkSn9+vEvDvgyWyrJo5vJsWSDXZmJAjtoUq4zS4eqHyXImpktvHOZwXp1XQjO5H+YQwsBgqTQEc0J/5zg==
-  dependencies:
-    "@webassemblyjs/wast-printer" "1.7.6"
-
-"@webassemblyjs/helper-fsm@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.11.tgz#df38882a624080d03f7503f93e3f17ac5ac01181"
-  integrity sha512-nsAQWNP1+8Z6tkzdYlXT0kxfa2Z1tRTARd8wYnc/e3Zv3VydVVnaeePgqUzFrpkGUyhUUxOl5ML7f1NuT+gC0A==
-
-"@webassemblyjs/helper-fsm@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.6.tgz#ae1741c6f6121213c7a0b587fb964fac492d3e49"
-  integrity sha512-HCS6KN3wgxUihGBW7WFzEC/o8Eyvk0d56uazusnxXthDPnkWiMv+kGi9xXswL2cvfYfeK5yiM17z2K5BVlwypw==
-
-"@webassemblyjs/helper-module-context@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.11.tgz#d874d722e51e62ac202476935d649c802fa0e209"
-  integrity sha512-JxfD5DX8Ygq4PvXDucq0M+sbUFA7BJAv/GGl9ITovqE+idGX+J3QSzJYz+LwQmL7fC3Rs+utvWoJxDb6pmC0qg==
-
-"@webassemblyjs/helper-module-context@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.6.tgz#116d19a51a6cebc8900ad53ca34ff8269c668c23"
-  integrity sha512-e8/6GbY7OjLM+6OsN7f2krC2qYVNaSr0B0oe4lWdmq5sL++8dYDD1TFbD1TdAdWMRTYNr/Qq7ovXWzia2EbSjw==
-  dependencies:
-    mamacro "^0.0.3"
-
-"@webassemblyjs/helper-wasm-bytecode@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.11.tgz#dd9a1e817f1c2eb105b4cf1013093cb9f3c9cb06"
-  integrity sha512-cMXeVS9rhoXsI9LLL4tJxBgVD/KMOKXuFqYb5oCJ/opScWpkCMEz9EJtkonaNcnLv2R3K5jIeS4TRj/drde1JQ==
-
-"@webassemblyjs/helper-wasm-bytecode@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.6.tgz#98e515eaee611aa6834eb5f6a7f8f5b29fefb6f1"
-  integrity sha512-PzYFCb7RjjSdAOljyvLWVqd6adAOabJW+8yRT+NWhXuf1nNZWH+igFZCUK9k7Cx7CsBbzIfXjJc7u56zZgFj9Q==
-
-"@webassemblyjs/helper-wasm-section@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.11.tgz#9c9ac41ecf9fbcfffc96f6d2675e2de33811e68a"
-  integrity sha512-8ZRY5iZbZdtNFE5UFunB8mmBEAbSI3guwbrsCl4fWdfRiAcvqQpeqd5KHhSWLL5wuxo53zcaGZDBU64qgn4I4Q==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/helper-buffer" "1.7.11"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
-    "@webassemblyjs/wasm-gen" "1.7.11"
-
-"@webassemblyjs/helper-wasm-section@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.6.tgz#783835867bdd686df7a95377ab64f51a275e8333"
-  integrity sha512-3GS628ppDPSuwcYlQ7cDCGr4W2n9c4hLzvnRKeuz+lGsJSmc/ADVoYpm1ts2vlB1tGHkjtQMni+yu8mHoMlKlA==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/helper-buffer" "1.7.6"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
-    "@webassemblyjs/wasm-gen" "1.7.6"
-
-"@webassemblyjs/ieee754@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.11.tgz#c95839eb63757a31880aaec7b6512d4191ac640b"
-  integrity sha512-Mmqx/cS68K1tSrvRLtaV/Lp3NZWzXtOHUW2IvDvl2sihAwJh4ACE0eL6A8FvMyDG9abes3saB6dMimLOs+HMoQ==
-  dependencies:
-    "@xtuc/ieee754" "^1.2.0"
-
-"@webassemblyjs/ieee754@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.6.tgz#c34fc058f2f831fae0632a8bb9803cf2d3462eb1"
-  integrity sha512-V4cIp0ruyw+hawUHwQLn6o2mFEw4t50tk530oKsYXQhEzKR+xNGDxs/SFFuyTO7X3NzEu4usA3w5jzhl2RYyzQ==
+"@webassemblyjs/ast@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.10.tgz#0cfc61d61286240b72fc522cb755613699eea40a"
+  integrity sha512-wTUeaByYN2EA6qVqhbgavtGc7fLTOx0glG2IBsFlrFG51uXIGlYBTyIZMf4SPLo3v1bgV/7lBN3l7Z0R6Hswew==
+  dependencies:
+    "@webassemblyjs/helper-module-context" "1.7.10"
+    "@webassemblyjs/helper-wasm-bytecode" "1.7.10"
+    "@webassemblyjs/wast-parser" "1.7.10"
+
+"@webassemblyjs/floating-point-hex-parser@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.7.10.tgz#ee63d729c6311a85863e369a473f9983f984e4d9"
+  integrity sha512-gMsGbI6I3p/P1xL2UxqhNh1ga2HCsx5VBB2i5VvJFAaqAjd2PBTRULc3BpTydabUQEGlaZCzEUQhLoLG7TvEYQ==
+
+"@webassemblyjs/helper-api-error@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.7.10.tgz#bfcb3bbe59775357475790a2ad7b289f09b2f198"
+  integrity sha512-DoYRlPWtuw3yd5BOr9XhtrmB6X1enYF0/54yNvQWGXZEPDF5PJVNI7zQ7gkcKfTESzp8bIBWailaFXEK/jjCsw==
+
+"@webassemblyjs/helper-buffer@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.7.10.tgz#0a8c624c67ad0b214d2e003859921a1988cb151b"
+  integrity sha512-+RMU3dt/dPh4EpVX4u5jxsOlw22tp3zjqE0m3ftU2tsYxnPULb4cyHlgaNd2KoWuwasCQqn8Mhr+TTdbtj3LlA==
+
+"@webassemblyjs/helper-code-frame@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.7.10.tgz#0ab7e22fad0241a173178c73976fc0edf50832ce"
+  integrity sha512-UiytbpKAULOEab2hUZK2ywXen4gWJVrgxtwY3Kn+eZaaSWaRM8z/7dAXRSoamhKFiBh1uaqxzE/XD9BLlug3gw==
+  dependencies:
+    "@webassemblyjs/wast-printer" "1.7.10"
+
+"@webassemblyjs/helper-fsm@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.7.10.tgz#0915e7713fbbb735620a9d3e4fa3d7951f97ac64"
+  integrity sha512-w2vDtUK9xeSRtt5+RnnlRCI7wHEvLjF0XdnxJpgx+LJOvklTZPqWkuy/NhwHSLP19sm9H8dWxKeReMR7sCkGZA==
+
+"@webassemblyjs/helper-module-context@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.7.10.tgz#9beb83f72740f5ac8075313b5cac5e796510f755"
+  integrity sha512-yE5x/LzZ3XdPdREmJijxzfrf+BDRewvO0zl8kvORgSWmxpRrkqY39KZSq6TSgIWBxkK4SrzlS3BsMCv2s1FpsQ==
+
+"@webassemblyjs/helper-wasm-bytecode@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.7.10.tgz#797b1e734bbcfdea8399669cdc58308ef1c7ffc0"
+  integrity sha512-u5qy4SJ/OrxKxZqJ9N3qH4ZQgHaAzsopsYwLvoWJY6Q33r8PhT3VPyNMaJ7ZFoqzBnZlCcS/0f4Sp8WBxylXfg==
+
+"@webassemblyjs/helper-wasm-section@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.7.10.tgz#c0ea3703c615d7bc3e3507c3b7991c8767b2f20e"
+  integrity sha512-Ecvww6sCkcjatcyctUrn22neSJHLN/TTzolMGG/N7S9rpbsTZ8c6Bl98GpSpV77EvzNijiNRHBG0+JO99qKz6g==
+  dependencies:
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/helper-buffer" "1.7.10"
+    "@webassemblyjs/helper-wasm-bytecode" "1.7.10"
+    "@webassemblyjs/wasm-gen" "1.7.10"
+
+"@webassemblyjs/ieee754@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.7.10.tgz#62c1728b7ef0f66ef8221e2966a0afd75db430df"
+  integrity sha512-HRcWcY+YWt4+s/CvQn+vnSPfRaD4KkuzQFt5MNaELXXHSjelHlSEA8ZcqT69q0GTIuLWZ6JaoKar4yWHVpZHsQ==
   dependencies:
     "@xtuc/ieee754" "^1.2.0"
 
-"@webassemblyjs/leb128@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.11.tgz#d7267a1ee9c4594fd3f7e37298818ec65687db63"
-  integrity sha512-vuGmgZjjp3zjcerQg+JA+tGOncOnJLWVkt8Aze5eWQLwTQGNgVLcyOTqgSCxWTR4J42ijHbBxnuRaL1Rv7XMdw==
-  dependencies:
-    "@xtuc/long" "4.2.1"
-
-"@webassemblyjs/leb128@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.6.tgz#197f75376a29f6ed6ace15898a310d871d92f03b"
-  integrity sha512-ojdlG8WpM394lBow4ncTGJoIVZ4aAtNOWHhfAM7m7zprmkVcKK+2kK5YJ9Bmj6/ketTtOn7wGSHCtMt+LzqgYQ==
+"@webassemblyjs/leb128@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.7.10.tgz#167e0bb4b06d7701585772a73fba9f4df85439f6"
+  integrity sha512-og8MciYlA8hvzCLR71hCuZKPbVBfLQeHv7ImKZ4nlyxrYbG7uJHYtHiHu6OV9SqrGuD03H/HtXC4Bgdjfm9FHw==
   dependencies:
     "@xtuc/long" "4.2.1"
 
-"@webassemblyjs/utf8@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.11.tgz#06d7218ea9fdc94a6793aa92208160db3d26ee82"
-  integrity sha512-C6GFkc7aErQIAH+BMrIdVSmW+6HSe20wg57HEC1uqJP8E/xpMjXqQUxkQw07MhNDSDcGpxI9G5JSNOQCqJk4sA==
-
-"@webassemblyjs/utf8@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.6.tgz#eb62c66f906af2be70de0302e29055d25188797d"
-  integrity sha512-oId+tLxQ+AeDC34ELRYNSqJRaScB0TClUU6KQfpB8rNT6oelYlz8axsPhf6yPTg7PBJ/Z5WcXmUYiHEWgbbHJw==
-
-"@webassemblyjs/wasm-edit@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.11.tgz#8c74ca474d4f951d01dbae9bd70814ee22a82005"
-  integrity sha512-FUd97guNGsCZQgeTPKdgxJhBXkUbMTY6hFPf2Y4OedXd48H97J+sOY2Ltaq6WGVpIH8o/TGOVNiVz/SbpEMJGg==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/helper-buffer" "1.7.11"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
-    "@webassemblyjs/helper-wasm-section" "1.7.11"
-    "@webassemblyjs/wasm-gen" "1.7.11"
-    "@webassemblyjs/wasm-opt" "1.7.11"
-    "@webassemblyjs/wasm-parser" "1.7.11"
-    "@webassemblyjs/wast-printer" "1.7.11"
-
-"@webassemblyjs/wasm-edit@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.6.tgz#fa41929160cd7d676d4c28ecef420eed5b3733c5"
-  integrity sha512-pTNjLO3o41v/Vz9VFLl+I3YLImpCSpodFW77pNoH4agn5I6GgSxXHXtvWDTvYJFty0jSeXZWLEmbaSIRUDlekg==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/helper-buffer" "1.7.6"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
-    "@webassemblyjs/helper-wasm-section" "1.7.6"
-    "@webassemblyjs/wasm-gen" "1.7.6"
-    "@webassemblyjs/wasm-opt" "1.7.6"
-    "@webassemblyjs/wasm-parser" "1.7.6"
-    "@webassemblyjs/wast-printer" "1.7.6"
-
-"@webassemblyjs/wasm-gen@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.11.tgz#9bbba942f22375686a6fb759afcd7ac9c45da1a8"
-  integrity sha512-U/KDYp7fgAZX5KPfq4NOupK/BmhDc5Kjy2GIqstMhvvdJRcER/kUsMThpWeRP8BMn4LXaKhSTggIJPOeYHwISA==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
-    "@webassemblyjs/ieee754" "1.7.11"
-    "@webassemblyjs/leb128" "1.7.11"
-    "@webassemblyjs/utf8" "1.7.11"
-
-"@webassemblyjs/wasm-gen@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.6.tgz#695ac38861ab3d72bf763c8c75e5f087ffabc322"
-  integrity sha512-mQvFJVumtmRKEUXMohwn8nSrtjJJl6oXwF3FotC5t6e2hlKMh8sIaW03Sck2MDzw9xPogZD7tdP5kjPlbH9EcQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
-    "@webassemblyjs/ieee754" "1.7.6"
-    "@webassemblyjs/leb128" "1.7.6"
-    "@webassemblyjs/utf8" "1.7.6"
-
-"@webassemblyjs/wasm-opt@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.11.tgz#b331e8e7cef8f8e2f007d42c3a36a0580a7d6ca7"
-  integrity sha512-XynkOwQyiRidh0GLua7SkeHvAPXQV/RxsUeERILmAInZegApOUAIJfRuPYe2F7RcjOC9tW3Cb9juPvAC/sCqvg==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/helper-buffer" "1.7.11"
-    "@webassemblyjs/wasm-gen" "1.7.11"
-    "@webassemblyjs/wasm-parser" "1.7.11"
-
-"@webassemblyjs/wasm-opt@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.6.tgz#fbafa78e27e1a75ab759a4b658ff3d50b4636c21"
-  integrity sha512-go44K90fSIsDwRgtHhX14VtbdDPdK2sZQtZqUcMRvTojdozj5tLI0VVJAzLCfz51NOkFXezPeVTAYFqrZ6rI8Q==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/helper-buffer" "1.7.6"
-    "@webassemblyjs/wasm-gen" "1.7.6"
-    "@webassemblyjs/wasm-parser" "1.7.6"
-
-"@webassemblyjs/wasm-parser@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.11.tgz#6e3d20fa6a3519f6b084ef9391ad58211efb0a1a"
-  integrity sha512-6lmXRTrrZjYD8Ng8xRyvyXQJYUQKYSXhJqXOBLw24rdiXsHAOlvw5PhesjdcaMadU/pyPQOJ5dHreMjBxwnQKg==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/helper-api-error" "1.7.11"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.11"
-    "@webassemblyjs/ieee754" "1.7.11"
-    "@webassemblyjs/leb128" "1.7.11"
-    "@webassemblyjs/utf8" "1.7.11"
-
-"@webassemblyjs/wasm-parser@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.6.tgz#84eafeeff405ad6f4c4b5777d6a28ae54eed51fe"
-  integrity sha512-t1T6TfwNY85pDA/HWPA8kB9xA4sp9ajlRg5W7EKikqrynTyFo+/qDzIpvdkOkOGjlS6d4n4SX59SPuIayR22Yg==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/helper-api-error" "1.7.6"
-    "@webassemblyjs/helper-wasm-bytecode" "1.7.6"
-    "@webassemblyjs/ieee754" "1.7.6"
-    "@webassemblyjs/leb128" "1.7.6"
-    "@webassemblyjs/utf8" "1.7.6"
-
-"@webassemblyjs/wast-parser@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.11.tgz#25bd117562ca8c002720ff8116ef9072d9ca869c"
-  integrity sha512-lEyVCg2np15tS+dm7+JJTNhNWq9yTZvi3qEhAIIOaofcYlUp0UR5/tVqOwa/gXYr3gjwSZqw+/lS9dscyLelbQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/floating-point-hex-parser" "1.7.11"
-    "@webassemblyjs/helper-api-error" "1.7.11"
-    "@webassemblyjs/helper-code-frame" "1.7.11"
-    "@webassemblyjs/helper-fsm" "1.7.11"
-    "@xtuc/long" "4.2.1"
-
-"@webassemblyjs/wast-parser@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.6.tgz#ca4d20b1516e017c91981773bd7e819d6bd9c6a7"
-  integrity sha512-1MaWTErN0ziOsNUlLdvwS+NS1QWuI/kgJaAGAMHX8+fMJFgOJDmN/xsG4h/A1Gtf/tz5VyXQciaqHZqp2q0vfg==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/floating-point-hex-parser" "1.7.6"
-    "@webassemblyjs/helper-api-error" "1.7.6"
-    "@webassemblyjs/helper-code-frame" "1.7.6"
-    "@webassemblyjs/helper-fsm" "1.7.6"
+"@webassemblyjs/utf8@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.7.10.tgz#b6728f5b6f50364abc155be029f9670e6685605a"
+  integrity sha512-Ng6Pxv6siyZp635xCSnH3mKmIFgqWPCcGdoo0GBYgyGdxu7cUj4agV7Uu1a8REP66UYUFXJLudeGgd4RvuJAnQ==
+
+"@webassemblyjs/wasm-edit@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.7.10.tgz#83fe3140f5a58f5a30b914702be9f0e59a399092"
+  integrity sha512-e9RZFQlb+ZuYcKRcW9yl+mqX/Ycj9+3/+ppDI8nEE/NCY6FoK8f3dKBcfubYV/HZn44b+ND4hjh+4BYBt+sDnA==
+  dependencies:
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/helper-buffer" "1.7.10"
+    "@webassemblyjs/helper-wasm-bytecode" "1.7.10"
+    "@webassemblyjs/helper-wasm-section" "1.7.10"
+    "@webassemblyjs/wasm-gen" "1.7.10"
+    "@webassemblyjs/wasm-opt" "1.7.10"
+    "@webassemblyjs/wasm-parser" "1.7.10"
+    "@webassemblyjs/wast-printer" "1.7.10"
+
+"@webassemblyjs/wasm-gen@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.7.10.tgz#4de003806ae29c97ab3707782469b53299570174"
+  integrity sha512-M0lb6cO2Y0PzDye/L39PqwV+jvO+2YxEG5ax+7dgq7EwXdAlpOMx1jxyXJTScQoeTpzOPIb+fLgX/IkLF8h2yw==
+  dependencies:
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/helper-wasm-bytecode" "1.7.10"
+    "@webassemblyjs/ieee754" "1.7.10"
+    "@webassemblyjs/leb128" "1.7.10"
+    "@webassemblyjs/utf8" "1.7.10"
+
+"@webassemblyjs/wasm-opt@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.7.10.tgz#d151e31611934a556c82789fdeec41a814993c2a"
+  integrity sha512-R66IHGCdicgF5ZliN10yn5HaC7vwYAqrSVJGjtJJQp5+QNPBye6heWdVH/at40uh0uoaDN/UVUfXK0gvuUqtVg==
+  dependencies:
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/helper-buffer" "1.7.10"
+    "@webassemblyjs/wasm-gen" "1.7.10"
+    "@webassemblyjs/wasm-parser" "1.7.10"
+
+"@webassemblyjs/wasm-parser@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.7.10.tgz#0367be7bf8f09e3e6abc95f8e483b9206487ec65"
+  integrity sha512-AEv8mkXVK63n/iDR3T693EzoGPnNAwKwT3iHmKJNBrrALAhhEjuPzo/lTE4U7LquEwyvg5nneSNdTdgrBaGJcA==
+  dependencies:
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/helper-api-error" "1.7.10"
+    "@webassemblyjs/helper-wasm-bytecode" "1.7.10"
+    "@webassemblyjs/ieee754" "1.7.10"
+    "@webassemblyjs/leb128" "1.7.10"
+    "@webassemblyjs/utf8" "1.7.10"
+
+"@webassemblyjs/wast-parser@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.7.10.tgz#058f598b52f730b23fc874d4775b6286b6247264"
+  integrity sha512-YTPEtOBljkCL0VjDp4sHe22dAYSm3ZwdJ9+2NTGdtC7ayNvuip1wAhaAS8Zt9Q6SW9E5Jf5PX7YE3XWlrzR9cw==
+  dependencies:
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/floating-point-hex-parser" "1.7.10"
+    "@webassemblyjs/helper-api-error" "1.7.10"
+    "@webassemblyjs/helper-code-frame" "1.7.10"
+    "@webassemblyjs/helper-fsm" "1.7.10"
     "@xtuc/long" "4.2.1"
-    mamacro "^0.0.3"
 
-"@webassemblyjs/wast-printer@1.7.11":
-  version "1.7.11"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.11.tgz#c4245b6de242cb50a2cc950174fdbf65c78d7813"
-  integrity sha512-m5vkAsuJ32QpkdkDOUPGSltrg8Cuk3KBx4YrmAGQwCZPRdUHXxG4phIOuuycLemHFr74sWL9Wthqss4fzdzSwg==
+"@webassemblyjs/wast-printer@1.7.10":
+  version "1.7.10"
+  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.10.tgz#d817909d2450ae96c66b7607624d98a33b84223b"
+  integrity sha512-mJ3QKWtCchL1vhU/kZlJnLPuQZnlDOdZsyP0bbLWPGdYsQDnSBvyTLhzwBA3QAMlzEL9V4JHygEmK6/OTEyytA==
   dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/wast-parser" "1.7.11"
-    "@xtuc/long" "4.2.1"
-
-"@webassemblyjs/wast-printer@1.7.6":
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.7.6.tgz#a6002c526ac5fa230fe2c6d2f1bdbf4aead43a5e"
-  integrity sha512-vHdHSK1tOetvDcl1IV1OdDeGNe/NDDQ+KzuZHMtqTVP1xO/tZ/IKNpj5BaGk1OYFdsDWQqb31PIwdEyPntOWRQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/wast-parser" "1.7.6"
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/wast-parser" "1.7.10"
     "@xtuc/long" "4.2.1"
 
 "@xtuc/ieee754@^1.2.0":
   resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8"
   integrity sha512-FZdkNBDqBRHKQ2MEbSC17xnPFOhZxeJ2YGSfr2BKf3sujG49Qe3bB+rGCwQfIaA7WHnGeGkSijX4FuBCdrzW/g==
 
+"@yarnpkg/lockfile@1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+  integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
+JSONStream@^1.3.4:
+  version "1.3.5"
+  resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
+  integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
+  dependencies:
+    jsonparse "^1.2.0"
+    through ">=2.2.7 <3"
+
 abab@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
@@ -909,13 +786,20 @@ after@0.8.2:
   resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
   integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
 
-agent-base@^4.1.0:
+agent-base@4, agent-base@^4.1.0, agent-base@~4.2.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
   integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
   dependencies:
     es6-promisify "^5.0.0"
 
+agentkeepalive@^3.4.1:
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67"
+  integrity sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==
+  dependencies:
+    humanize-ms "^1.2.1"
+
 ajv-errors@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
@@ -944,7 +828,7 @@ ajv@^4.11.2:
     co "^4.6.0"
     json-stable-stringify "^1.0.1"
 
-ajv@^5.0.0, ajv@^5.1.0:
+ajv@^5.0.0:
   version "5.5.2"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
   integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=
@@ -955,9 +839,9 @@ ajv@^5.0.0, ajv@^5.1.0:
     json-schema-traverse "^0.3.0"
 
 ajv@^6.1.0, ajv@^6.5.5:
-  version "6.5.5"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1"
-  integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==
+  version "6.6.1"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
+  integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==
   dependencies:
     fast-deep-equal "^2.0.1"
     fast-json-stable-stringify "^2.0.0"
@@ -977,15 +861,10 @@ angular2-hotkeys@^2.1.2:
     "@types/mousetrap" "^1.6.0"
     mousetrap "^1.6.0"
 
-angular2-notifications@^1.0.2:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/angular2-notifications/-/angular2-notifications-1.0.4.tgz#7b3c449dbad45503965f8cd8ac00e998a4463544"
-  integrity sha512-DjazfwXtLY8BNXKIEw1oEEMy7G6fmldpzP1FYwyVGUwEtZPLQyYGu9MQYCjtVlZMljxpa3qvnv8l9ZUfXAarNA==
-
 ansi-colors@^3.0.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.1.tgz#9638047e4213f3428a11944a7d4b31cba0a3ff95"
-  integrity sha512-Xt+zb6nqgvV9SWAVp0EG3lRsHcbq5DDgqjPPz6pwgtj6RKz65zGXMNa82oJfOSBA/to6GmRP7Dr+6o+kbApTzQ==
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.2.tgz#e49349137dbeb6d381b91e607c189915e53265ba"
+  integrity sha512-kJmcp4PrviBBEx95fC3dYRiC/QSN3EBd0GU1XoNEk/IuUa92rsB6o90zP3w5VAyNznR38Vkc9i8vk5zK6T7TxA==
 
 ansi-escapes@^3.0.0:
   version "3.1.0"
@@ -1112,9 +991,9 @@ array-flatten@1.1.1:
   integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
 
 array-flatten@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
-  integrity sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099"
+  integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==
 
 array-slice@^0.2.3:
   version "0.2.3"
@@ -1238,17 +1117,17 @@ atob@^2.1.1:
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
-autoprefixer@9.1.5:
-  version "9.1.5"
-  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.1.5.tgz#8675fd8d1c0d43069f3b19a2c316f3524e4f6671"
-  integrity sha512-kk4Zb6RUc58ld7gdosERHMF3DzIYJc2fp5sX46qEsGXQQy5bXsu8qyLjoxuY1NuQ/cJuCYnx99BfjwnRggrYIw==
+autoprefixer@9.3.1:
+  version "9.3.1"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.3.1.tgz#71b622174de2b783d5fd99f9ad617b7a3c78443e"
+  integrity sha512-DY9gOh8z3tnCbJ13JIWaeQsoYncTGdsrgCceBaQSIL4nvdrLxgbRSBPevg2XbX7u4QCSfLheSJEEIUUSlkbx6Q==
   dependencies:
-    browserslist "^4.1.0"
-    caniuse-lite "^1.0.30000884"
+    browserslist "^4.3.3"
+    caniuse-lite "^1.0.30000898"
     normalize-range "^0.1.2"
     num2fraction "^1.2.2"
-    postcss "^7.0.2"
-    postcss-value-parser "^3.2.3"
+    postcss "^7.0.5"
+    postcss-value-parser "^3.3.1"
 
 awesome-typescript-loader@5.2.1:
   version "5.2.1"
@@ -1269,7 +1148,7 @@ aws-sign2@~0.7.0:
   resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
   integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
 
-aws4@^1.6.0, aws4@^1.8.0:
+aws4@^1.8.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
   integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
@@ -1631,7 +1510,7 @@ blocking-proxy@^1.0.0:
   dependencies:
     minimist "^1.2.0"
 
-bluebird@^3.3.0, bluebird@^3.5.1:
+bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.2:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
   integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
@@ -1800,14 +1679,14 @@ browserify-zlib@^0.2.0:
   dependencies:
     pako "~1.0.5"
 
-browserslist@^4.1.0:
-  version "4.3.4"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.4.tgz#4477b737db6a1b07077275b24791e680d4300425"
-  integrity sha512-u5iz+ijIMUlmV8blX82VGFrB9ecnUg5qEt55CMZ/YJEhha+d8qpBfOFuutJ6F/VKRXjZoD33b6uvarpPxcl3RA==
+browserslist@^4.3.3:
+  version "4.3.5"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.3.5.tgz#1a917678acc07b55606748ea1adf9846ea8920f7"
+  integrity sha512-z9ZhGc3d9e/sJ9dIx5NFXkKoaiQTnrvrMsN3R1fGb1tkWWNSz12UewJn9TNxGo1l7J23h0MRaPmk7jfeTZYs1w==
   dependencies:
-    caniuse-lite "^1.0.30000899"
-    electron-to-chromium "^1.3.82"
-    node-releases "^1.0.1"
+    caniuse-lite "^1.0.30000912"
+    electron-to-chromium "^1.3.86"
+    node-releases "^1.0.5"
 
 browserstack@^1.5.1:
   version "1.5.1"
@@ -1931,7 +1810,7 @@ cacache@^10.0.4:
     unique-filename "^1.1.0"
     y18n "^4.0.0"
 
-cacache@^11.0.2:
+cacache@^11.0.1, cacache@^11.0.2, cacache@^11.2.0:
   version "11.3.1"
   resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.1.tgz#d09d25f6c4aca7a6d305d141ae332613aa1d515f"
   integrity sha512-2PEw4cRRDu+iQvBTTuttQifacYjLPhET+SYO/gEFMy8uhi+jlJREDAjSF5FWSdV/Aw5h18caHA7vMTw2c+wDzA==
@@ -2014,10 +1893,15 @@ camelcase@^4.1.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
   integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
 
-caniuse-lite@^1.0.30000884, caniuse-lite@^1.0.30000899:
-  version "1.0.30000907"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000907.tgz#0b9899bde53fb1c30e214fb12402361e02ff5c42"
-  integrity sha512-No5sQ/OB2Nmka8MNOOM6nJx+Hxt6MQ6h7t7kgJFu9oTuwjykyKRSBP/+i/QAyFHxeHB+ddE0Da1CG5ihx9oehQ==
+camelcase@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
+  integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
+
+caniuse-lite@^1.0.30000898, caniuse-lite@^1.0.30000912:
+  version "1.0.30000914"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000914.tgz#f802b4667c24d0255f54a95818dcf8e1aa41f624"
+  integrity sha512-qqj0CL1xANgg6iDOybiPTIxtsmAnfIky9mBC35qgWrnK4WwmhqfpmkDYMYgwXJ8LRZ3/2jXlCntulO8mBaAgSg==
 
 canonical-path@1.0.0:
   version "1.0.0"
@@ -2276,7 +2160,7 @@ combine-lists@^1.0.0:
   dependencies:
     lodash "^4.5.0"
 
-combined-stream@^1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6:
+combined-stream@^1.0.6, combined-stream@~1.0.6:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
   integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==
@@ -2355,7 +2239,7 @@ concat-map@0.0.1:
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
   integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
 
-concat-stream@^1.5.0, concat-stream@^1.5.2:
+concat-stream@^1.5.0:
   version "1.6.2"
   resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
   integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -2622,6 +2506,14 @@ css-selector-tokenizer@^0.7.0:
     fastparse "^1.1.1"
     regexpu-core "^1.0.0"
 
+css-tree@^1.0.0-alpha.29:
+  version "1.0.0-alpha.29"
+  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39"
+  integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==
+  dependencies:
+    mdn-data "~1.1.0"
+    source-map "^0.5.3"
+
 css-what@2.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
@@ -2718,28 +2610,28 @@ debug@*, debug@^4.0.1, debug@^4.1.0:
   dependencies:
     ms "^2.1.1"
 
-debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
   dependencies:
     ms "2.0.0"
 
-debug@=3.1.0, debug@~3.1.0:
+debug@3.1.0, debug@=3.1.0, debug@~3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
   dependencies:
     ms "2.0.0"
 
-debug@^3.1.0:
+debug@^3.1.0, debug@^3.2.5:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
   dependencies:
     ms "^2.1.1"
 
-decamelize@^1.1.1, decamelize@^1.1.2:
+decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
@@ -3106,10 +2998,10 @@ ejs@^2.6.1:
   resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
   integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
 
-electron-to-chromium@^1.3.82:
-  version "1.3.84"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.84.tgz#2e55df59e818f150a9f61b53471ebf4f0feecc65"
-  integrity sha512-IYhbzJYOopiTaNWMBp7RjbecUBsbnbDneOP86f3qvS0G0xfzwNSvMJpTrvi5/Y1gU7tg2NAgeg8a8rCYvW9Whw==
+electron-to-chromium@^1.3.86:
+  version "1.3.88"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.88.tgz#f36ab32634f49ef2b0fdc1e82e2d1cc17feb29e7"
+  integrity sha512-UPV4NuQMKeUh1S0OWRvwg0PI8ASHN9kBC8yDTk1ROXLC85W5GnhTRu/MZu3Teqx3JjlQYuckuHYXSUSgtb3J+A==
 
 elliptic@^6.0.0:
   version "6.4.1"
@@ -3134,6 +3026,13 @@ encodeurl@~1.0.1, encodeurl@~1.0.2:
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
 
+encoding@^0.1.11:
+  version "0.1.12"
+  resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
+  integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=
+  dependencies:
+    iconv-lite "~0.4.13"
+
 end-of-stream@^1.0.0, end-of-stream@^1.1.0:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -3158,6 +3057,23 @@ engine.io-client@~3.2.0:
     xmlhttprequest-ssl "~1.5.4"
     yeast "0.1.2"
 
+engine.io-client@~3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.3.1.tgz#afedb4a07b2ea48b7190c3136bfea98fdd4f0f03"
+  integrity sha512-q66JBFuQcy7CSlfAz9L3jH+v7DTT3i6ZEadYcVj2pOs8/0uJHLxKX3WBkGTvULJMdz0tUCyJag0aKT/dpXL9BQ==
+  dependencies:
+    component-emitter "1.2.1"
+    component-inherit "0.0.3"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.1"
+    has-cors "1.1.0"
+    indexof "0.0.1"
+    parseqs "0.0.5"
+    parseuri "0.0.5"
+    ws "~6.1.0"
+    xmlhttprequest-ssl "~1.5.4"
+    yeast "0.1.2"
+
 engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6"
@@ -3200,6 +3116,11 @@ entities@^1.1.1, entities@~1.1.1:
   resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56"
   integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==
 
+err-code@^1.0.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960"
+  integrity sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=
+
 errno@^0.1.1, errno@^0.1.3, errno@~0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
@@ -3384,12 +3305,12 @@ events@^1.0.0:
   resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
   integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
 
-eventsource@0.1.6:
-  version "0.1.6"
-  resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232"
-  integrity sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=
+eventsource@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0"
+  integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==
   dependencies:
-    original ">=0.0.5"
+    original "^1.0.0"
 
 evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
   version "1.0.3"
@@ -3544,7 +3465,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
     assign-symbols "^1.0.0"
     is-extendable "^1.0.1"
 
-extend@^3.0.0, extend@~3.0.1, extend@~3.0.2:
+extend@^3.0.0, extend@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
   integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -3631,7 +3552,7 @@ faye-websocket@^0.10.0:
   dependencies:
     websocket-driver ">=0.5.1"
 
-faye-websocket@~0.11.0:
+faye-websocket@~0.11.1:
   version "0.11.1"
   resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38"
   integrity sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=
@@ -3645,7 +3566,7 @@ fb-watchman@^2.0.0:
   dependencies:
     bser "^2.0.0"
 
-figgy-pudding@^3.1.0, figgy-pudding@^3.5.1:
+figgy-pudding@^3.1.0, figgy-pudding@^3.4.1, figgy-pudding@^3.5.1:
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
   integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==
@@ -3780,6 +3701,11 @@ find-up@^3.0.0:
   dependencies:
     locate-path "^3.0.0"
 
+flatted@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
+  integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==
+
 flatten@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
@@ -3799,9 +3725,9 @@ focus-visible@^4.1.5:
   integrity sha512-yo/njtk/BB4Z2euzaZe3CZrg4u5s5uEi7ZwbHBJS2quHx51N0mmcx9nTIiImUGlgy+vf26d0CcQluahBBBL/Fw==
 
 follow-redirects@^1.0.0:
-  version "1.5.9"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.9.tgz#c9ed9d748b814a39535716e531b9196a845d89c6"
-  integrity sha512-Bh65EZI/RU8nx0wbYF9shkFZlqLP+6WT/5FnA3cE/djNSuKNHJEinGGZgu/cQEkeeb2GdFOgenAmn8qaqYke2w==
+  version "1.5.10"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+  integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
   dependencies:
     debug "=3.1.0"
 
@@ -3841,7 +3767,7 @@ forever-agent@~0.6.1:
   resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
   integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
 
-form-data@~2.3.1, form-data@~2.3.2:
+form-data@~2.3.2:
   version "2.3.3"
   resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
   integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==
@@ -3969,6 +3895,11 @@ gaze@^1.0.0:
   dependencies:
     globule "^1.0.0"
 
+genfun@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/genfun/-/genfun-5.0.0.tgz#9dd9710a06900a5c4a5bf57aca5da4e52fe76537"
+  integrity sha512-KGDOARWVga7+rnB3z9Sd2Letx515owfk0hSxHGuqjANb1M+x2bGZGqHLiozPsYMdM2OubeMni/Hpwmjq6qIUhA==
+
 get-browser-rtc@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/get-browser-rtc/-/get-browser-rtc-1.0.2.tgz#bbcd40c8451a7ed4ef5c373b8169a409dd1d11d9"
@@ -3994,6 +3925,13 @@ get-stream@^3.0.0:
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
   integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
 
+get-stream@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+  integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+  dependencies:
+    pump "^3.0.0"
+
 get-value@^2.0.3, get-value@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
@@ -4053,7 +3991,7 @@ glob@7.1.2:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1:
+glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
   version "7.1.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
   integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
@@ -4077,9 +4015,9 @@ glob@^5.0.15:
     path-is-absolute "^1.0.0"
 
 global-modules-path@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.0.tgz#b0e2bac6beac39745f7db5c59d26a36a0b94f7dc"
-  integrity sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag==
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.1.tgz#e541f4c800a1a8514a990477b267ac67525b9931"
+  integrity sha512-y+shkf4InI7mPRHSo2b/k6ix6+NLDtyccYv86whhxrSGX9wjPX1VMITmrDbE1eh7zkzhiWtW2sHklJYoQ62Cxg==
 
 global@4.3.2, global@^4.3.0, global@^4.3.1, global@^4.3.2, global@~4.3.0:
   version "4.3.2"
@@ -4182,14 +4120,6 @@ har-schema@^2.0.0:
   resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
   integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
 
-har-validator@~5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
-  integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=
-  dependencies:
-    ajv "^5.1.0"
-    har-schema "^2.0.0"
-
 har-validator@~5.1.0:
   version "5.1.3"
   resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
@@ -4284,9 +4214,9 @@ hash-base@^3.0.0:
     safe-buffer "^5.0.1"
 
 hash.js@^1.0.0, hash.js@^1.0.3:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812"
-  integrity sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
+  integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
   dependencies:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
@@ -4382,7 +4312,7 @@ html-webpack-plugin@^3.2.0:
     toposort "^1.0.0"
     util.promisify "1.0.0"
 
-htmlparser2@^3.9.0:
+htmlparser2@^3.10.0:
   version "3.10.0"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464"
   integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==
@@ -4404,6 +4334,11 @@ htmlparser2@~3.3.0:
     domutils "1.1"
     readable-stream "1.0"
 
+http-cache-semantics@^3.8.1:
+  version "3.8.1"
+  resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
+  integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
+
 http-deceiver@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
@@ -4424,6 +4359,14 @@ http-parser-js@>=0.4.0:
   resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8"
   integrity sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==
 
+http-proxy-agent@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
+  integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
+  dependencies:
+    agent-base "4"
+    debug "3.1.0"
+
 http-proxy-middleware@~0.18.0:
   version "0.18.0"
   resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab"
@@ -4465,6 +4408,13 @@ https-proxy-agent@^2.2.1:
     agent-base "^4.1.0"
     debug "^3.1.0"
 
+humanize-ms@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed"
+  integrity sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=
+  dependencies:
+    ms "^2.0.0"
+
 iconv-lite@0.4.23:
   version "0.4.23"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
@@ -4472,7 +4422,7 @@ iconv-lite@0.4.23:
   dependencies:
     safer-buffer ">= 2.1.2 < 3"
 
-iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
+iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -4603,7 +4553,7 @@ inherits@2.0.1:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
   integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
 
-ini@^1.3.4, ini@~1.3.0:
+ini@1.3.5, ini@^1.3.4, ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -5640,7 +5590,7 @@ jsesc@~0.5.0:
   resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
   integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
 
-json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
+json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
   integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
@@ -5701,6 +5651,11 @@ jsonify@~0.0.0:
   resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
   integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=
 
+jsonparse@^1.2.0:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
+  integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
+
 jsprim@^1.2.2:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@@ -5796,9 +5751,9 @@ karma-source-map-support@1.3.0:
     source-map-support "^0.5.5"
 
 karma@^3.0.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.1.tgz#94c8edd20fb9597ccde343326da009737fb0423a"
-  integrity sha512-NetT3wPCQMNB36uiL9LLyhrOt8SQwrEKt0xD3+KpTCfm0VxVyUJdPL5oTq2Ic5ouemgL/Iz4wqXEbF3zea9kQQ==
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/karma/-/karma-3.1.3.tgz#6e251648e3aff900927bc1126dbcbcb92d3edd61"
+  integrity sha512-JU4FYUtFEGsLZd6ZJzLrivcPj0TkteBiIRDcXWFsltPMGgZMDtby/MIzNOzgyZv/9dahs9vHpSxerC/ZfeX9Qw==
   dependencies:
     bluebird "^3.3.0"
     body-parser "^1.16.1"
@@ -5810,11 +5765,12 @@ karma@^3.0.0:
     di "^0.0.1"
     dom-serialize "^2.2.0"
     expand-braces "^0.1.1"
+    flatted "^2.0.0"
     glob "^7.1.1"
     graceful-fs "^4.1.2"
     http-proxy "^1.13.0"
     isbinaryfile "^3.0.0"
-    lodash "^4.17.4"
+    lodash "^4.17.5"
     log4js "^3.0.0"
     mime "^2.3.1"
     minimatch "^3.0.2"
@@ -5826,7 +5782,7 @@ karma@^3.0.0:
     socket.io "2.1.1"
     source-map "^0.6.1"
     tmp "0.0.33"
-    useragent "2.2.1"
+    useragent "2.3.0"
 
 killable@^1.0.0:
   version "1.0.1"
@@ -5939,9 +5895,9 @@ lie@~3.1.0:
     immediate "~3.0.5"
 
 linkify-it@^2.0.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f"
-  integrity sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db"
+  integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==
   dependencies:
     uc.micro "^1.0.1"
 
@@ -6061,7 +6017,7 @@ lodash.isstring@^4.0.1:
   resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
   integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
 
-lodash.mergewith@^4.6.0:
+lodash.mergewith@^4.6.0, lodash.mergewith@^4.6.1:
   version "4.6.1"
   resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
   integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==
@@ -6119,7 +6075,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
   dependencies:
     js-tokens "^3.0.0 || ^4.0.0"
 
-loud-rejection@^1.0.0, loud-rejection@^1.6.0:
+loud-rejection@^1.0.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
   integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=
@@ -6132,15 +6088,10 @@ lower-case@^1.1.1:
   resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
   integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
 
-lru-cache@2.2.x:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
-  integrity sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=
-
-lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.3:
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
-  integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==
+lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+  integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
   dependencies:
     pseudomap "^1.0.2"
     yallist "^2.1.2"
@@ -6184,6 +6135,23 @@ make-error@1.x:
   resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
   integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
 
+make-fetch-happen@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz#141497cb878f243ba93136c83d8aba12c216c083"
+  integrity sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==
+  dependencies:
+    agentkeepalive "^3.4.1"
+    cacache "^11.0.1"
+    http-cache-semantics "^3.8.1"
+    http-proxy-agent "^2.1.0"
+    https-proxy-agent "^2.2.1"
+    lru-cache "^4.1.2"
+    mississippi "^3.0.0"
+    node-fetch-npm "^2.0.2"
+    promise-retry "^1.1.1"
+    socks-proxy-agent "^4.0.0"
+    ssri "^6.0.0"
+
 makeerror@1.0.x:
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@@ -6191,11 +6159,6 @@ makeerror@1.0.x:
   dependencies:
     tmpl "1.0.x"
 
-mamacro@^0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4"
-  integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==
-
 map-age-cleaner@^0.1.1:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a"
@@ -6245,6 +6208,11 @@ md5.js@^1.3.4:
     inherits "^2.0.1"
     safe-buffer "^5.1.2"
 
+mdn-data@~1.1.0:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01"
+  integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==
+
 mdurl@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@@ -6400,9 +6368,9 @@ mime@^1.4.1:
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
 mime@^2.2.0, mime@^2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
-  integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6"
+  integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==
 
 mimic-fn@^1.0.0:
   version "1.2.0"
@@ -6421,10 +6389,10 @@ min-document@^2.19.0:
   dependencies:
     dom-walk "^0.1.0"
 
-mini-css-extract-plugin@0.4.3:
-  version "0.4.3"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.3.tgz#98d60fcc5d228c3e36a9bd15a1d6816d6580beb8"
-  integrity sha512-Mxs0nxzF1kxPv4TRi2NimewgXlJqh0rGE30vviCU2WHrpbta6wklnUV9dr9FUtoAHmB3p3LeXEC+ZjgHvB0Dzg==
+mini-css-extract-plugin@0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.4.tgz#c10410a004951bd3cedac1da69053940fccb625d"
+  integrity sha512-o+Jm+ocb0asEngdM6FsZWtZsRzA8koFUudIDwYUfl94M3PejPHG7Vopw5hN9V8WsMkSFpm3tZP3Fesz89EyrfQ==
   dependencies:
     loader-utils "^1.1.0"
     schema-utils "^1.0.0"
@@ -6462,7 +6430,7 @@ minimist@~0.0.1:
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
   integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
 
-minipass@^2.2.1, minipass@^2.3.4:
+minipass@^2.2.1, minipass@^2.3.4, minipass@^2.3.5:
   version "2.3.5"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
   integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
@@ -6582,7 +6550,7 @@ ms@2.0.0:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
 
-ms@^2.1.1:
+ms@^2.0.0, ms@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
   integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
@@ -6735,6 +6703,15 @@ no-case@^2.2.0:
   dependencies:
     lower-case "^1.1.1"
 
+node-fetch-npm@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7"
+  integrity sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==
+  dependencies:
+    encoding "^0.1.11"
+    json-parse-better-errors "^1.0.0"
+    safe-buffer "^5.1.1"
+
 node-forge@0.7.5:
   version "0.7.5"
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df"
@@ -6823,39 +6800,14 @@ node-pre-gyp@^0.10.0:
     semver "^5.3.0"
     tar "^4"
 
-node-releases@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.3.tgz#3414ed84595096459c251699bfcb47d88324a9e4"
-  integrity sha512-ZaZWMsbuDcetpHmYeKWPO6e63pSXLb50M7lJgCbcM2nC/nQC3daNifmtp5a2kp7EWwYfhuvH6zLPWkrF8IiDdw==
+node-releases@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.0.5.tgz#a641adcc968b039a27345d92ef10b093e5cbd41d"
+  integrity sha512-Ky7q0BO1BBkG/rQz6PkEZ59rwo+aSfhczHP1wwq8IowoVdN/FpiP7qp0XW0P2+BVCWe5fQUBozdbVd54q1RbCQ==
   dependencies:
     semver "^5.3.0"
 
-node-sass@4.9.3:
-  version "4.9.3"
-  resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.3.tgz#f407cf3d66f78308bb1e346b24fa428703196224"
-  integrity sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww==
-  dependencies:
-    async-foreach "^0.1.3"
-    chalk "^1.1.1"
-    cross-spawn "^3.0.0"
-    gaze "^1.0.0"
-    get-stdin "^4.0.1"
-    glob "^7.0.3"
-    in-publish "^2.0.0"
-    lodash.assign "^4.2.0"
-    lodash.clonedeep "^4.3.2"
-    lodash.mergewith "^4.6.0"
-    meow "^3.7.0"
-    mkdirp "^0.5.1"
-    nan "^2.10.0"
-    node-gyp "^3.8.0"
-    npmlog "^4.0.0"
-    request "2.87.0"
-    sass-graph "^2.2.4"
-    stdout-stream "^1.4.0"
-    "true-case-path" "^1.0.2"
-
-node-sass@^4.9.3:
+node-sass@4.10.0, node-sass@^4.9.3:
   version "4.10.0"
   resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.10.0.tgz#dcc2b364c0913630945ccbf7a2bbf1f926effca4"
   integrity sha512-fDQJfXszw6vek63Fe/ldkYXmRYK/QS6NbvM3i5oEo9ntPDy4XX7BcKZyTKv+/kSSxRtXXc7l+MSwEmYc0CSy6Q==
@@ -6895,7 +6847,7 @@ nopt@^4.0.1:
     abbrev "1"
     osenv "^0.1.4"
 
-normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, "normalize-package-data@~1.0.1 || ^2.0.0":
+normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
   integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==
@@ -6927,7 +6879,7 @@ npm-font-source-sans-pro@^1.0.2:
   resolved "https://registry.yarnpkg.com/npm-font-source-sans-pro/-/npm-font-source-sans-pro-1.0.2.tgz#c55c8ae368eebdbcaca65425a0d7e1f9a192a03e"
   integrity sha1-xVyK42juvbysplQloNfh+aGSoD4=
 
-"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0":
+npm-package-arg@^6.0.0, npm-package-arg@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1"
   integrity sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==
@@ -6937,7 +6889,7 @@ npm-font-source-sans-pro@^1.0.2:
     semver "^5.5.0"
     validate-npm-package-name "^3.0.0"
 
-npm-packlist@^1.1.6:
+npm-packlist@^1.1.12, npm-packlist@^1.1.6:
   version "1.1.12"
   resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a"
   integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==
@@ -6945,24 +6897,26 @@ npm-packlist@^1.1.6:
     ignore-walk "^3.0.1"
     npm-bundled "^1.0.1"
 
-npm-registry-client@8.6.0:
-  version "8.6.0"
-  resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4"
-  integrity sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==
+npm-pick-manifest@^2.1.0:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.2.3.tgz#32111d2a9562638bb2c8f2bf27f7f3092c8fae40"
+  integrity sha512-+IluBC5K201+gRU85vFlUwX3PFShZAbAgDNp2ewJdWMVSppdo/Zih0ul2Ecky/X7b51J7LrrUAP+XOmOCvYZqA==
   dependencies:
-    concat-stream "^1.5.2"
-    graceful-fs "^4.1.6"
-    normalize-package-data "~1.0.1 || ^2.0.0"
-    npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
-    once "^1.3.3"
-    request "^2.74.0"
-    retry "^0.10.0"
-    safe-buffer "^5.1.1"
-    semver "2 >=2.2.1 || 3.x || 4 || 5"
-    slide "^1.1.3"
-    ssri "^5.2.4"
-  optionalDependencies:
-    npmlog "2 || ^3.1.0 || ^4.0.0"
+    figgy-pudding "^3.5.1"
+    npm-package-arg "^6.0.0"
+    semver "^5.4.1"
+
+npm-registry-fetch@^3.8.0:
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz#aa7d9a7c92aff94f48dba0984bdef4bd131c88cc"
+  integrity sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw==
+  dependencies:
+    JSONStream "^1.3.4"
+    bluebird "^3.5.1"
+    figgy-pudding "^3.4.1"
+    lru-cache "^4.1.3"
+    make-fetch-happen "^4.0.1"
+    npm-package-arg "^6.1.0"
 
 npm-run-path@^2.0.0:
   version "2.0.2"
@@ -6971,7 +6925,7 @@ npm-run-path@^2.0.0:
   dependencies:
     path-key "^2.0.0"
 
-"npmlog@0 || 1 || 2 || 3 || 4", "npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.0.0, npmlog@^4.0.2:
+"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
   integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
@@ -7008,11 +6962,6 @@ nwsapi@^2.0.7:
   resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.9.tgz#77ac0cdfdcad52b6a1151a84e73254edc33ed016"
   integrity sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==
 
-oauth-sign@~0.8.2:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-  integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
-
 oauth-sign@~0.9.0:
   version "0.9.0"
   resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@@ -7152,7 +7101,7 @@ optionator@^0.8.1:
     type-check "~0.3.2"
     wordwrap "~1.0.0"
 
-original@>=0.0.5:
+original@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
   integrity sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==
@@ -7272,10 +7221,43 @@ package-json-versionify@^1.0.2:
   dependencies:
     browserify-package-json "^1.0.0"
 
+pacote@9.1.1:
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/pacote/-/pacote-9.1.1.tgz#25091f75a25021de8be8d34cc6408728fca3579b"
+  integrity sha512-f28Rq5ozzKAA9YwIKw61/ipwAatUZseYmVssDbHHaexF0wRIVotapVEZPAjOT7Eu3LYVqEp0NVpNizoAnYBUaA==
+  dependencies:
+    bluebird "^3.5.2"
+    cacache "^11.2.0"
+    figgy-pudding "^3.5.1"
+    get-stream "^4.1.0"
+    glob "^7.1.3"
+    lru-cache "^4.1.3"
+    make-fetch-happen "^4.0.1"
+    minimatch "^3.0.4"
+    minipass "^2.3.5"
+    mississippi "^3.0.0"
+    mkdirp "^0.5.1"
+    normalize-package-data "^2.4.0"
+    npm-package-arg "^6.1.0"
+    npm-packlist "^1.1.12"
+    npm-pick-manifest "^2.1.0"
+    npm-registry-fetch "^3.8.0"
+    osenv "^0.1.5"
+    promise-inflight "^1.0.1"
+    promise-retry "^1.1.1"
+    protoduck "^5.0.1"
+    rimraf "^2.6.2"
+    safe-buffer "^5.1.2"
+    semver "^5.6.0"
+    ssri "^6.0.1"
+    tar "^4.4.6"
+    unique-filename "^1.1.1"
+    which "^1.3.1"
+
 pako@~1.0.2, pako@~1.0.5:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258"
-  integrity sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.7.tgz#2473439021b57f1516c82f58be7275ad8ef1bb27"
+  integrity sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ==
 
 parallel-transform@^1.1.0:
   version "1.1.0"
@@ -7543,9 +7525,9 @@ portfinder@1.0.17:
     mkdirp "0.5.x"
 
 portfinder@^1.0.9:
-  version "1.0.19"
-  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.19.tgz#07e87914a55242dcda5b833d42f018d6875b595f"
-  integrity sha512-23aeQKW9KgHe6citUrG3r9HjeX6vls0h713TAa+CwTKZwNIr/pD2ApaxYF4Um3ZZyq4ar+Siv3+fhoHaIwSOSw==
+  version "1.0.20"
+  resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.20.tgz#bea68632e54b2e13ab7b0c4775e9b41bf270e44a"
+  integrity sha512-Yxe4mTyDzTd59PZJY4ojZR8F+E5e97iq2ZOHPz3HDgSvYC5siNad2tLooQ5y5QHyQhc3xVqvyk/eNA3wuoa7Sw==
   dependencies:
     async "^1.5.2"
     debug "^2.2.0"
@@ -7615,12 +7597,12 @@ postcss-modules-values@^1.3.0:
     icss-replace-symbols "^1.1.0"
     postcss "^6.0.1"
 
-postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0:
+postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
   integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
 
-postcss@7.0.5, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2:
+postcss@7.0.5:
   version "7.0.5"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.5.tgz#70e6443e36a6d520b0fd4e7593fcca3635ee9f55"
   integrity sha512-HBNpviAUFCKvEh7NZhw1e8MBPivRszIiUnhrJ+sBFVSYSqubrzwX3KG51mYgcRHX8j/cAgZJedONZcm5jTBdgQ==
@@ -7629,7 +7611,7 @@ postcss@7.0.5, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2:
     source-map "^0.6.1"
     supports-color "^5.5.0"
 
-postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23:
+postcss@^6.0.1, postcss@^6.0.23:
   version "6.0.23"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
   integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==
@@ -7638,6 +7620,15 @@ postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23:
     source-map "^0.6.1"
     supports-color "^5.4.0"
 
+postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.5:
+  version "7.0.6"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.6.tgz#6dcaa1e999cdd4a255dcd7d4d9547f4ca010cdc2"
+  integrity sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==
+  dependencies:
+    chalk "^2.4.1"
+    source-map "^0.6.1"
+    supports-color "^5.5.0"
+
 prelude-ls@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@@ -7664,10 +7655,10 @@ pretty-format@^23.6.0:
     ansi-regex "^3.0.0"
     ansi-styles "^3.2.0"
 
-primeng@^6.1.2:
-  version "6.1.6"
-  resolved "https://registry.yarnpkg.com/primeng/-/primeng-6.1.6.tgz#3a699a02507fcd5befb2fb9fe18fcc5d385f810e"
-  integrity sha512-9QYkXfBuSwx5zZej5QiEWhdAFJPc+f9h6PXuFtw4PzUfOhPmnoUxR5K04xeFreCNP13WwP3Uh/U3o7mY0PZtQA==
+primeng@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/primeng/-/primeng-7.0.0.tgz#3a189568069a31544c9ed952328e221daad9b9b1"
+  integrity sha512-PrEEnp0VPbzsUQdpB/4KtUdRxaWwpprHy+IpUi09C42OAI2zqdTVIC0AaW81gDAGQyW7XraCP9EFI8KT4nC9GA==
 
 private@^0.1.8, private@~0.1.5:
   version "0.1.8"
@@ -7699,6 +7690,14 @@ promise-inflight@^1.0.1:
   resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
   integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
 
+promise-retry@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d"
+  integrity sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=
+  dependencies:
+    err-code "^1.0.0"
+    retry "^0.10.0"
+
 promise@^7.1.1:
   version "7.3.1"
   resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
@@ -7722,6 +7721,13 @@ prop-types@^15.6.2:
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
+protoduck@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.1.tgz#03c3659ca18007b69a50fd82a7ebcc516261151f"
+  integrity sha512-WxoCeDCoCBY55BMvj4cAEjdVUFGRWed9ZxPlqTKYyw1nDDTQ4pqmnIMAGfJlg7Dx35uB/M+PHJPTmGOvaCaPTg==
+  dependencies:
+    genfun "^5.0.0"
+
 protractor@^5.3.2:
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/protractor/-/protractor-5.4.1.tgz#011a99e38df7aa45d22455b889ffbb13a6ce0bd9"
@@ -7762,7 +7768,7 @@ pseudomap@^1.0.2:
   resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
   integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
 
-psl@^1.1.24:
+psl@^1.1.24, psl@^1.1.28:
   version "1.1.29"
   resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
   integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==
@@ -7814,7 +7820,7 @@ punycode@^1.2.4, punycode@^1.4.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
   integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
 
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@@ -7863,7 +7869,7 @@ qrcode@^0.8.2:
     isarray "^2.0.1"
     pngjs "^2.3.1"
 
-qs@6.5.2, qs@~6.5.1, qs@~6.5.2:
+qs@6.5.2, qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
@@ -8241,33 +8247,7 @@ request-promise-native@^1.0.5:
     stealthy-require "^1.1.0"
     tough-cookie ">=2.3.3"
 
-request@2.87.0:
-  version "2.87.0"
-  resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
-  integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==
-  dependencies:
-    aws-sign2 "~0.7.0"
-    aws4 "^1.6.0"
-    caseless "~0.12.0"
-    combined-stream "~1.0.5"
-    extend "~3.0.1"
-    forever-agent "~0.6.1"
-    form-data "~2.3.1"
-    har-validator "~5.0.3"
-    http-signature "~1.2.0"
-    is-typedarray "~1.0.0"
-    isstream "~0.1.2"
-    json-stringify-safe "~5.0.1"
-    mime-types "~2.1.17"
-    oauth-sign "~0.8.2"
-    performance-now "^2.1.0"
-    qs "~6.5.1"
-    safe-buffer "^5.1.1"
-    tough-cookie "~2.3.3"
-    tunnel-agent "^0.6.0"
-    uuid "^3.1.0"
-
-request@^2.74.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
+request@^2.83.0, request@^2.87.0, request@^2.88.0:
   version "2.88.0"
   resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
   integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -8335,7 +8315,7 @@ resolve@1.1.7, resolve@1.1.x:
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
   integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
 
-resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2:
+resolve@1.x, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
   integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==
@@ -8482,20 +8462,20 @@ sane@^2.0.0:
     fsevents "^1.2.3"
 
 sanitize-html@^1.18.4:
-  version "1.19.1"
-  resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.19.1.tgz#e8b33c69578054d6ee4f57ea152d6497f3f6fb7d"
-  integrity sha512-zNYr6FvBn4bZukr9x2uny6od/9YdjCLwF+FqxivqI0YOt/m9GIxfX+tWhm52tBAPUXiTTb4bJTGVagRz5b06bw==
+  version "1.19.2"
+  resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.19.2.tgz#c03fffe2bf96cd582968ece9792cbca32e64dde0"
+  integrity sha512-7fNb3/N0sZ/nkshMRBoxLz6K1dlMSVF/eQHX1Bof9sRT7cZJvmrDGfXEn544MXJnpY29vux1A599g9UrcHTBXA==
   dependencies:
-    chalk "^2.3.0"
-    htmlparser2 "^3.9.0"
+    chalk "^2.4.1"
+    css-tree "^1.0.0-alpha.29"
+    htmlparser2 "^3.10.0"
     lodash.clonedeep "^4.5.0"
     lodash.escaperegexp "^4.1.2"
     lodash.isplainobject "^4.0.6"
     lodash.isstring "^4.0.1"
-    lodash.mergewith "^4.6.0"
-    postcss "^6.0.14"
+    lodash.mergewith "^4.6.1"
     srcset "^1.0.0"
-    xtend "^4.0.0"
+    xtend "^4.0.1"
 
 sass-graph@^2.2.4:
   version "2.2.4"
@@ -8547,9 +8527,9 @@ sax@>=0.6.0, sax@^1.2.4:
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
 scheduler@^0.11.2:
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.2.tgz#a8db5399d06eba5abac51b705b7151d2319d33d3"
-  integrity sha512-+WCP3s3wOaW4S7C1tl3TEXp4l9lJn0ZK8G3W3WKRWmw77Z2cIFUW2MiNTMHn5sCjxN+t7N43HAOOgMjyAg5hlg==
+  version "0.11.3"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.3.tgz#b5769b90cf8b1464f3f3cfcafe8e3cd7555a2d6b"
+  integrity sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
@@ -8622,7 +8602,7 @@ semver-intersect@1.4.0:
   dependencies:
     semver "^5.0.0"
 
-"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.5, semver@^5.5.0:
+"semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.6.0:
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
   integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
@@ -8820,10 +8800,10 @@ slash@^1.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
   integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
 
-slide@^1.1.3:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
-  integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=
+smart-buffer@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3"
+  integrity sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==
 
 snapdragon-node@^2.0.1:
   version "2.1.1"
@@ -8880,6 +8860,26 @@ socket.io-client@2.1.1:
     socket.io-parser "~3.2.0"
     to-array "0.1.4"
 
+socket.io-client@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.2.0.tgz#84e73ee3c43d5020ccc1a258faeeb9aec2723af7"
+  integrity sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==
+  dependencies:
+    backo2 "1.0.2"
+    base64-arraybuffer "0.1.5"
+    component-bind "1.0.0"
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    engine.io-client "~3.3.1"
+    has-binary2 "~1.0.2"
+    has-cors "1.1.0"
+    indexof "0.0.1"
+    object-component "0.0.3"
+    parseqs "0.0.5"
+    parseuri "0.0.5"
+    socket.io-parser "~3.3.0"
+    to-array "0.1.4"
+
 socket.io-parser@~3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.2.0.tgz#e7c6228b6aa1f814e6148aea325b51aa9499e077"
@@ -8889,6 +8889,15 @@ socket.io-parser@~3.2.0:
     debug "~3.1.0"
     isarray "2.0.1"
 
+socket.io-parser@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
+  integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==
+  dependencies:
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    isarray "2.0.1"
+
 socket.io@2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.1.1.tgz#a069c5feabee3e6b214a75b40ce0652e1cfb9980"
@@ -8901,17 +8910,17 @@ socket.io@2.1.1:
     socket.io-client "2.1.1"
     socket.io-parser "~3.2.0"
 
-sockjs-client@1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.5.tgz#1bb7c0f7222c40f42adf14f4442cbd1269771a83"
-  integrity sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=
+sockjs-client@1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177"
+  integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==
   dependencies:
-    debug "^2.6.6"
-    eventsource "0.1.6"
-    faye-websocket "~0.11.0"
-    inherits "^2.0.1"
+    debug "^3.2.5"
+    eventsource "^1.0.7"
+    faye-websocket "~0.11.1"
+    inherits "^2.0.3"
     json3 "^3.3.2"
-    url-parse "^1.1.8"
+    url-parse "^1.4.3"
 
 sockjs@0.3.19:
   version "0.3.19"
@@ -8921,6 +8930,22 @@ sockjs@0.3.19:
     faye-websocket "^0.10.0"
     uuid "^3.0.1"
 
+socks-proxy-agent@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz#5936bf8b707a993079c6f37db2091821bffa6473"
+  integrity sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==
+  dependencies:
+    agent-base "~4.2.0"
+    socks "~2.2.0"
+
+socks@~2.2.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.2.tgz#f061219fc2d4d332afb4af93e865c84d3fa26e2b"
+  integrity sha512-g6wjBnnMOZpE0ym6e0uHSddz9p3a+WsBaaYQaBaSCJYvrC4IXykQR9MNGjLQf38e9iIIhp3b1/Zk8YZI3KGJ0Q==
+  dependencies:
+    ip "^1.1.5"
+    smart-buffer "^4.0.1"
+
 source-list-map@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -9012,9 +9037,9 @@ source-map@~0.2.0:
     amdefine ">=0.0.4"
 
 sourcemap-codec@^1.4.1:
-  version "1.4.3"
-  resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz#0ba615b73ec35112f63c2f2d9e7c3f87282b0e33"
-  integrity sha512-vFrY/x/NdsD7Yc8mpTJXuao9S8lq08Z/kOITHz6b7YbfI9xL8Spe5EvSQUHOI7SbpY8bRPr0U3kKSsPuqEGSfA==
+  version "1.4.4"
+  resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz#c63ea927c029dd6bd9a2b7fa03b3fec02ad56e9f"
+  integrity sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==
 
 spdx-correct@^3.0.0:
   version "3.0.2"
@@ -9067,7 +9092,7 @@ spdy@^3.4.1:
     select-hose "^2.0.0"
     spdy-transport "^2.0.18"
 
-speed-measure-webpack-plugin@^1.2.3:
+speed-measure-webpack-plugin@1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.3.tgz#de170b5cefbfa1c039d95e639edd3ad50cfc7c48"
   integrity sha512-p+taQ69VkRUXYMoZOx2nxV/Tz8tt79ahctoZJyJDHWP7fEYvwFNf5Pd73k5kZ6auu0pTsPNLEUwWpM8mCk85Zw==
@@ -9133,7 +9158,7 @@ ssri@^5.2.4:
   dependencies:
     safe-buffer "^5.1.1"
 
-ssri@^6.0.0:
+ssri@^6.0.0, ssri@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
   integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==
@@ -9293,10 +9318,10 @@ string2compact@^1.1.1, string2compact@^1.2.5:
     addr-to-ip-port "^1.0.1"
     ipaddr.js "^1.0.1"
 
-string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+string_decoder@^1.0.0, string_decoder@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
+  integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
   dependencies:
     safe-buffer "~5.1.0"
 
@@ -9305,6 +9330,13 @@ string_decoder@~0.10.x:
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
   integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
 
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
 strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@@ -9348,13 +9380,13 @@ strip-json-comments@~2.0.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
 
-style-loader@0.23.0:
-  version "0.23.0"
-  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.0.tgz#8377fefab68416a2e05f1cabd8c3a3acfcce74f1"
-  integrity sha512-uCcN7XWHkqwGVt7skpInW6IGO1tG6ReyFQ1Cseh0VcN6VdcFQi62aG/2F3Y9ueA8x4IVlfaSUxpmQXQD9QrEuQ==
+style-loader@0.23.1:
+  version "0.23.1"
+  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925"
+  integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==
   dependencies:
     loader-utils "^1.1.0"
-    schema-utils "^0.4.5"
+    schema-utils "^1.0.0"
 
 stylus-loader@3.0.2:
   version "3.0.2"
@@ -9407,9 +9439,9 @@ symbol-tree@^3.2.2:
   integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
 
 tapable@^1.0.0, tapable@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.0.tgz#0d076a172e3d9ba088fd2272b2668fb8d194b78c"
-  integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA==
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e"
+  integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA==
 
 tar@^2.0.0:
   version "2.2.1"
@@ -9420,7 +9452,7 @@ tar@^2.0.0:
     fstream "^1.0.2"
     inherits "2"
 
-tar@^4:
+tar@^4, tar@^4.4.6:
   version "4.4.8"
   resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d"
   integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==
@@ -9448,9 +9480,9 @@ terser-webpack-plugin@1.1.0, terser-webpack-plugin@^1.1.0:
     worker-farm "^1.5.2"
 
 terser@^3.8.1:
-  version "3.10.11"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-3.10.11.tgz#e063da74b194dde9faf0a561f3a438c549d2da3f"
-  integrity sha512-iruZ7j14oBbRYJC5cP0/vTU7YOWjN+J1ZskEGoF78tFzXdkK2hbCL/3TRZN8XB+MuvFhvOHMp7WkOCBO4VEL5g==
+  version "3.11.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-3.11.0.tgz#60782893e1f4d6788acc696351f40636d0e37af0"
+  integrity sha512-5iLMdhEPIq3zFWskpmbzmKwMQixKmTYwY3Ox9pjtSklBLnHiuQ0GKJLhL1HSYtyffHM3/lDIFBnb82m9D7ewwQ==
   dependencies:
     commander "~2.17.1"
     source-map "~0.6.1"
@@ -9485,7 +9517,7 @@ through2@^2.0.0:
     readable-stream "~2.3.6"
     xtend "~4.0.1"
 
-through@2, through@X.X.X, through@^2.3.6, through@~2.3.6:
+through@2, "through@>=2.2.7 <3", through@X.X.X, through@^2.3.6, through@~2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
   integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -9586,7 +9618,15 @@ torrent-piece@^2.0.0:
   resolved "https://registry.yarnpkg.com/torrent-piece/-/torrent-piece-2.0.0.tgz#6598ae67d93699e887f178db267ba16d89d7ec9b"
   integrity sha512-H/Z/yCuvZJj1vl1IQHI8dvF2QrUuXRJoptT5DW5967/dsLpXlCg+uyhFR5lfNj5mNaYePUbKtnL+qKWZGXv4Nw==
 
-tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
+tough-cookie@>=2.3.3, tough-cookie@^2.3.4:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+  integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+  dependencies:
+    psl "^1.1.28"
+    punycode "^2.1.1"
+
+tough-cookie@~2.4.3:
   version "2.4.3"
   resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
   integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
@@ -9594,13 +9634,6 @@ tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.4.3:
     psl "^1.1.24"
     punycode "^1.4.1"
 
-tough-cookie@~2.3.3:
-  version "2.3.4"
-  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"
-  integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==
-  dependencies:
-    punycode "^1.4.1"
-
 tr46@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@@ -9641,9 +9674,9 @@ tryer@^1.0.0:
   integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
 
 ts-jest@^23.1.4:
-  version "23.10.4"
-  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.10.4.tgz#a7a953f55c9165bcaa90ff91014a178e87fe0df8"
-  integrity sha512-oV/wBwGUS7olSk/9yWMiSIJWbz5xO4zhftnY3gwv6s4SMg6WHF1m8XZNBvQOKQRiTAexZ9754Z13dxBq3Zgssw==
+  version "23.10.5"
+  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-23.10.5.tgz#cdb550df4466a30489bf70ba867615799f388dd5"
+  integrity sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==
   dependencies:
     bs-logger "0.x"
     buffer-from "1.x"
@@ -9651,6 +9684,7 @@ ts-jest@^23.1.4:
     json5 "2.x"
     make-error "1.x"
     mkdirp "0.x"
+    resolve "1.x"
     semver "^5.5"
     yargs-parser "10.x"
 
@@ -9721,9 +9755,9 @@ tsutils@^2.27.2:
     tslib "^1.8.1"
 
 tsutils@^3.0.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.5.0.tgz#42602f7df241e753a2105cc3627a664abf11f745"
-  integrity sha512-/FZ+pEJQixWruFejFxNPRSwg+iF6aw7PYZVRqUscJ7EnzV3zieI8byfZziUR7QjCuJFulq8SEe9JcGflO4ze4Q==
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.5.2.tgz#6fd3c2d5a731e83bb21b070a173ec0faf3a8f6d3"
+  integrity sha512-qIlklNuI/1Dzfm+G+kJV5gg3gimZIX5haYtIVQe7qGyKd7eu8T1t1DY6pz4Sc2CGXAj9s1izycctm9Zfl9sRuQ==
   dependencies:
     tslib "^1.8.1"
 
@@ -9838,7 +9872,7 @@ uniq@^1.0.1:
   resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
   integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
 
-unique-filename@^1.1.0:
+unique-filename@^1.1.0, unique-filename@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
   integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==
@@ -9897,12 +9931,7 @@ urix@^0.1.0:
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
-url-join@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
-  integrity sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=
-
-url-parse@^1.1.8, url-parse@^1.4.3:
+url-parse@^1.4.3:
   version "1.4.4"
   resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.4.tgz#cac1556e95faa0303691fec5cf9d5a1bc34648f8"
   integrity sha512-/92DTTorg4JjktLNLe6GPS2/RvAd/RGr6LuktmWSMLEOa6rjnlrFXNgSbSmkNvCoL2T028A0a1JaJLzRMlFoHg==
@@ -9928,12 +9957,12 @@ use@^3.1.0:
   resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
   integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
 
-useragent@2.2.1:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e"
-  integrity sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=
+useragent@2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972"
+  integrity sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==
   dependencies:
-    lru-cache "2.2.x"
+    lru-cache "4.1.x"
     tmp "0.0.x"
 
 ut_metadata@^3.3.0:
@@ -10222,35 +10251,20 @@ webpack-core@^0.6.8:
     source-list-map "~0.1.7"
     source-map "~0.4.1"
 
-webpack-dev-middleware@3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz#a20ceef194873710052da678f3c6ee0aeed92552"
-  integrity sha512-YJLMF/96TpKXaEQwaLEo+Z4NDK8aV133ROF6xp9pe3gQoS7sxfpXh4Rv9eC+8vCvWfmDjRQaMSlRPbO+9G6jgA==
-  dependencies:
-    loud-rejection "^1.6.0"
-    memory-fs "~0.4.1"
-    mime "^2.3.1"
-    path-is-absolute "^1.0.0"
-    range-parser "^1.0.3"
-    url-join "^4.0.0"
-    webpack-log "^2.0.0"
-
-webpack-dev-middleware@3.3.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.3.0.tgz#8104daf4d4f65defe06ee2eaaeea612a7c541462"
-  integrity sha512-5C5gXtOo1I6+0AEg4UPglYEtu3Rai6l5IiO6aUu65scHXz29dc3oIWMiRwvcNLXgL0HwRkRxa9N02ZjFt4hY8w==
+webpack-dev-middleware@3.4.0:
+  version "3.4.0"
+  resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"
+  integrity sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==
   dependencies:
-    loud-rejection "^1.6.0"
     memory-fs "~0.4.1"
     mime "^2.3.1"
     range-parser "^1.0.3"
-    url-join "^4.0.0"
     webpack-log "^2.0.0"
 
-webpack-dev-server@3.1.8:
-  version "3.1.8"
-  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.8.tgz#eb7a95945d1108170f902604fb3b939533d9daeb"
-  integrity sha512-c+tcJtDqnPdxCAzEEZKdIPmg3i5i7cAHe+B+0xFNK0BlCc2HF/unYccbU7xTgfGc5xxhCztCQzFmsqim+KhI+A==
+webpack-dev-server@3.1.10:
+  version "3.1.10"
+  resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz#507411bee727ee8d2fdffdc621b66a64ab3dea2b"
+  integrity sha512-RqOAVjfqZJtQcB0LmrzJ5y4Jp78lv9CK0MZ1YJDTaTmedMZ9PU9FLMQNrMCfVu8hHzaVLVOJKBlGEHMN10z+ww==
   dependencies:
     ansi-html "0.0.7"
     bonjour "^3.5.0"
@@ -10273,11 +10287,11 @@ webpack-dev-server@3.1.8:
     selfsigned "^1.9.1"
     serve-index "^1.7.2"
     sockjs "0.3.19"
-    sockjs-client "1.1.5"
+    sockjs-client "1.3.0"
     spdy "^3.4.1"
     strip-ansi "^3.0.0"
     supports-color "^5.1.0"
-    webpack-dev-middleware "3.2.0"
+    webpack-dev-middleware "3.4.0"
     webpack-log "^2.0.0"
     yargs "12.0.2"
 
@@ -10314,6 +10328,14 @@ webpack-sources@1.2.0:
     source-list-map "^2.0.0"
     source-map "~0.6.1"
 
+webpack-sources@1.3.0, webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-sources@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
+  integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==
+  dependencies:
+    source-list-map "^2.0.0"
+    source-map "~0.6.1"
+
 webpack-sources@^0.1.4:
   version "0.1.5"
   resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750"
@@ -10322,14 +10344,6 @@ webpack-sources@^0.1.4:
     source-list-map "~0.1.7"
     source-map "~0.5.3"
 
-webpack-sources@^1.1.0, webpack-sources@^1.2.0, webpack-sources@^1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85"
-  integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==
-  dependencies:
-    source-list-map "^2.0.0"
-    source-map "~0.6.1"
-
 webpack-subresource-integrity@1.1.0-rc.6:
   version "1.1.0-rc.6"
   resolved "https://registry.yarnpkg.com/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.6.tgz#37f6f1264e1eb378e41465a98da80fad76ab8886"
@@ -10337,45 +10351,15 @@ webpack-subresource-integrity@1.1.0-rc.6:
   dependencies:
     webpack-core "^0.6.8"
 
-webpack@4.19.1:
-  version "4.19.1"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.19.1.tgz#096674bc3b573f8756c762754366e5b333d6576f"
-  integrity sha512-j7Q/5QqZRqIFXJvC0E59ipLV5Hf6lAnS3ezC3I4HMUybwEDikQBVad5d+IpPtmaQPQArvgUZLXIN6lWijHBn4g==
+webpack@4.23.1:
+  version "4.23.1"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.23.1.tgz#db7467b116771ae020c58bdfe2a0822785bb8239"
+  integrity sha512-iE5Cu4rGEDk7ONRjisTOjVHv3dDtcFfwitSxT7evtYj/rANJpt1OuC/Kozh1pBa99AUBr1L/LsaNB+D9Xz3CEg==
   dependencies:
-    "@webassemblyjs/ast" "1.7.6"
-    "@webassemblyjs/helper-module-context" "1.7.6"
-    "@webassemblyjs/wasm-edit" "1.7.6"
-    "@webassemblyjs/wasm-parser" "1.7.6"
-    acorn "^5.6.2"
-    acorn-dynamic-import "^3.0.0"
-    ajv "^6.1.0"
-    ajv-keywords "^3.1.0"
-    chrome-trace-event "^1.0.0"
-    enhanced-resolve "^4.1.0"
-    eslint-scope "^4.0.0"
-    json-parse-better-errors "^1.0.2"
-    loader-runner "^2.3.0"
-    loader-utils "^1.1.0"
-    memory-fs "~0.4.1"
-    micromatch "^3.1.8"
-    mkdirp "~0.5.0"
-    neo-async "^2.5.0"
-    node-libs-browser "^2.0.0"
-    schema-utils "^0.4.4"
-    tapable "^1.1.0"
-    uglifyjs-webpack-plugin "^1.2.4"
-    watchpack "^1.5.0"
-    webpack-sources "^1.2.0"
-
-webpack@^4.17.1:
-  version "4.25.1"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.25.1.tgz#4f459fbaea0f93440dc86c89f771bb3a837cfb6d"
-  integrity sha512-T0GU/3NRtO4tMfNzsvpdhUr8HnzA4LTdP2zd+e5zd6CdOH5vNKHnAlO+DvzccfhPdzqRrALOFcjYxx7K5DWmvA==
-  dependencies:
-    "@webassemblyjs/ast" "1.7.11"
-    "@webassemblyjs/helper-module-context" "1.7.11"
-    "@webassemblyjs/wasm-edit" "1.7.11"
-    "@webassemblyjs/wasm-parser" "1.7.11"
+    "@webassemblyjs/ast" "1.7.10"
+    "@webassemblyjs/helper-module-context" "1.7.10"
+    "@webassemblyjs/wasm-edit" "1.7.10"
+    "@webassemblyjs/wasm-parser" "1.7.10"
     acorn "^5.6.2"
     acorn-dynamic-import "^3.0.0"
     ajv "^6.1.0"
@@ -10468,9 +10452,9 @@ whatwg-fetch@^3.0.0:
   integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==
 
 whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"
-  integrity sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
+  integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
 
 whatwg-url@^6.4.1:
   version "6.5.0"
@@ -10505,7 +10489,7 @@ which-module@^2.0.0:
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
   integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
-which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9, which@^1.3.0:
+which@1, which@^1.1.1, which@^1.2.1, which@^1.2.12, which@^1.2.9, which@^1.3.0, which@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
@@ -10565,10 +10549,10 @@ ws@^5.2.0:
   dependencies:
     async-limiter "~1.0.0"
 
-ws@^6.0.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.0.tgz#119a9dbf92c54e190ec18d10e871d55c95cf9373"
-  integrity sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==
+ws@^6.0.0, ws@~6.1.0:
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.2.tgz#3cc7462e98792f0ac679424148903ded3b9c3ad8"
+  integrity sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==
   dependencies:
     async-limiter "~1.0.0"
 
@@ -10640,9 +10624,9 @@ yallist@^2.1.2:
   integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
 
 yallist@^3.0.0, yallist@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
-  integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
+  integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
 
 yargs-parser@10.x, yargs-parser@^10.1.0:
   version "10.1.0"
@@ -10651,6 +10635,14 @@ yargs-parser@10.x, yargs-parser@^10.1.0:
   dependencies:
     camelcase "^4.1.0"
 
+yargs-parser@^11.1.1:
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
+  integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
+  dependencies:
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
+
 yargs-parser@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a"
@@ -10697,7 +10689,7 @@ yargs@10.0.3:
     y18n "^3.2.1"
     yargs-parser "^8.0.0"
 
-yargs@12.0.2, yargs@^12.0.2:
+yargs@12.0.2:
   version "12.0.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
   integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==
@@ -10752,6 +10744,24 @@ yargs@^11.0.0:
     y18n "^3.2.1"
     yargs-parser "^9.0.2"
 
+yargs@^12.0.2:
+  version "12.0.5"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
+  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
+  dependencies:
+    cliui "^4.0.0"
+    decamelize "^1.2.0"
+    find-up "^3.0.0"
+    get-caller-file "^1.0.1"
+    os-locale "^3.0.0"
+    require-directory "^2.1.1"
+    require-main-filename "^1.0.1"
+    set-blocking "^2.0.0"
+    string-width "^2.0.0"
+    which-module "^2.0.0"
+    y18n "^3.2.1 || ^4.0.0"
+    yargs-parser "^11.1.1"
+
 yargs@^7.0.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8"
index 257ec7ed166b9eeb59978fbe8fea726a7aac4d49..e16b8c35298d76eb3c5a7d26d22bbddb03e4be95 100644 (file)
@@ -45,8 +45,10 @@ smtp:
 
 # From the project root directory
 storage:
+  tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
   avatars: 'storage/avatars/'
   videos: 'storage/videos/'
+  redundancy: 'storage/redundancy/'
   logs: 'storage/logs/'
   previews: 'storage/previews/'
   thumbnails: 'storage/thumbnails/'
@@ -75,7 +77,7 @@ trending:
 redundancy:
   videos:
     check_interval: '1 hour' # How often you want to check new videos to cache
-    strategies:
+    strategies: # Just uncomment strategies you want
 #      -
 #        size: '10GB'
 #        # Minimum time the video must remain in the cache. Only accept values > 10 hours (to not overload remote instances)
@@ -100,7 +102,12 @@ cache:
     size: 500 # Max number of video captions/subtitles you want to cache
 
 admin:
-  email: 'admin@example.com' # Your personal email as administrator
+  # Used to generate the root user at first startup
+  # And to receive emails from the contact form
+  email: 'admin@example.com'
+
+contact_form:
+  enabled: true
 
 signup:
   enabled: false
@@ -122,6 +129,8 @@ user:
 # Please, do not disable transcoding since many uploaded videos will not work
 transcoding:
   enabled: true
+  # Allow your users to upload .mkv, .mov, .avi, .flv videos
+  allow_additional_extensions: true
   threads: 1
   resolutions: # Only created if the original video has a higher resolution, uses more storage!
     240p: false
@@ -159,6 +168,8 @@ instance:
     "# If you would like to report a security issue\n# you may report it to:\nContact: https://github.com/Chocobozzz/PeerTube/blob/develop/SECURITY.md\nContact: mailto:"
 
 services:
+  # You can provide a reporting endpoint for Content Security Policy violations
+  csp-logger:
   # Cards configuration to format video in Twitter
   twitter:
     username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published
index ac15fc736f5062818b8815f382452a93ba8b8e3f..661eac0d5ea19f38daaa796ecf02617bfc92c5b6 100644 (file)
@@ -46,8 +46,10 @@ smtp:
 
 # From the project root directory
 storage:
+  tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing...
   avatars: '/var/www/peertube/storage/avatars/'
   videos: '/var/www/peertube/storage/videos/'
+  redundancy: '/var/www/peertube/storage/videos/'
   logs: '/var/www/peertube/storage/logs/'
   previews: '/var/www/peertube/storage/previews/'
   thumbnails: '/var/www/peertube/storage/thumbnails/'
@@ -76,7 +78,7 @@ trending:
 redundancy:
   videos:
     check_interval: '1 hour' # How often you want to check new videos to cache
-    strategies:
+    strategies: # Just uncomment strategies you want
 #      -
 #        size: '10GB'
 #        # Minimum time the video must remain in the cache. Only accept values > 10 hours (to not overload remote instances)
@@ -113,8 +115,13 @@ cache:
     size: 500 # Max number of video captions/subtitles you want to cache
 
 admin:
+  # Used to generate the root user at first startup
+  # And to receive emails from the contact form
   email: 'admin@example.com'
 
+contact_form:
+  enabled: true
+
 signup:
   enabled: false
   limit: 10 # When the limit is reached, registrations are disabled. -1 == unlimited
@@ -135,6 +142,8 @@ user:
 # Please, do not disable transcoding since many uploaded videos will not work
 transcoding:
   enabled: true
+  # Allow your users to upload .mkv, .mov, .avi, .flv videos
+  allow_additional_extensions: true
   threads: 1
   resolutions: # Only created if the original video has a higher resolution, uses more storage!
     240p: false
@@ -173,6 +182,8 @@ instance:
     "# If you would like to report a security issue\n# you may report it to:\nContact: https://github.com/Chocobozzz/PeerTube/blob/develop/SECURITY.md\nContact: mailto:"
 
 services:
+  # You can provide a reporting endpoint for Content Security Policy violations
+  csp-logger:
   # Cards configuration to format video in Twitter
   twitter:
     username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published
index 503bbc6610632fb813d7591c3f764ef8f0b08ad3..8f4f66d2a9c24e9ea79f41c6dd48baab4c6ff8a3 100644 (file)
@@ -10,8 +10,10 @@ database:
 
 # From the project root directory
 storage:
+  tmp: 'test1/tmp/'
   avatars: 'test1/avatars/'
   videos: 'test1/videos/'
+  redundancy: 'test1/redundancy/'
   logs: 'test1/logs/'
   previews: 'test1/previews/'
   thumbnails: 'test1/thumbnails/'
index 8c77bf58107bb36dd7c1588341612a3cb0ee7e10..b6d319394a8ed308d45629f60c99d44ee5749c4e 100644 (file)
@@ -10,8 +10,10 @@ database:
 
 # From the project root directory
 storage:
+  tmp: 'test2/tmp/'
   avatars: 'test2/avatars/'
   videos: 'test2/videos/'
+  redundancy: 'test2/redundancy/'
   logs: 'test2/logs/'
   previews: 'test2/previews/'
   thumbnails: 'test2/thumbnails/'
@@ -27,3 +29,4 @@ signup:
 
 transcoding:
   enabled: true
+  allow_additional_extensions: true
index 82d89567a7e09551fa30677029180ec7f937e8a6..934401eb07ed5e01ac7af6f9868e92bbba6a6af6 100644 (file)
@@ -10,8 +10,10 @@ database:
 
 # From the project root directory
 storage:
+  tmp: 'test3/tmp/'
   avatars: 'test3/avatars/'
   videos: 'test3/videos/'
+  redundancy: 'test3/redundancy/'
   logs: 'test3/logs/'
   previews: 'test3/previews/'
   thumbnails: 'test3/thumbnails/'
index 1aa56d041c665962e1e43488ee6d8378389709d9..ee99b250be4013f3061d0d1529017beca48e0a9d 100644 (file)
@@ -10,8 +10,10 @@ database:
 
 # From the project root directory
 storage:
+  tmp: 'test4/tmp/'
   avatars: 'test4/avatars/'
   videos: 'test4/videos/'
+  redundancy: 'test4/redundancy/'
   logs: 'test4/logs/'
   previews: 'test4/previews/'
   thumbnails: 'test4/thumbnails/'
index 5f1c2f583cce4635da722209352414b09dbfc40c..e2662bdd930a44ee5232ad122f6d59a658860f9f 100644 (file)
@@ -10,8 +10,10 @@ database:
 
 # From the project root directory
 storage:
+  tmp: 'test5/tmp/'
   avatars: 'test5/avatars/'
   videos: 'test5/videos/'
+  redundancy: 'test5/redundancy/'
   logs: 'test5/logs/'
   previews: 'test5/previews/'
   thumbnails: 'test5/thumbnails/'
index 719629844b162ad795ddb5df82e7e1c133ddefdf..ad39c6a9fd1584ea4e728bef635d29b0021e0196 100644 (file)
@@ -10,8 +10,10 @@ database:
 
 # From the project root directory
 storage:
+  tmp: 'test6/tmp/'
   avatars: 'test6/avatars/'
   videos: 'test6/videos/'
+  redundancy: 'test6/redundancy/'
   logs: 'test6/logs/'
   previews: 'test6/previews/'
   thumbnails: 'test6/thumbnails/'
index 9c051fabc45f3bfa09ea818ccd94832a85f46448..aba5dd73c58ec1e28d8dd21754dd5714355ccc8f 100644 (file)
@@ -21,6 +21,9 @@ smtp:
 log:
   level: 'debug'
 
+contact_form:
+  enabled: true
+
 redundancy:
   videos:
     check_interval: '10 minutes'
@@ -51,6 +54,7 @@ signup:
 
 transcoding:
   enabled: true
+  allow_additional_extensions: false
   threads: 2
   resolutions:
     240p: true
@@ -67,4 +71,4 @@ import:
       enabled: true
 
 instance:
-  default_nsfw_policy: 'display'
\ No newline at end of file
+  default_nsfw_policy: 'display'
index d4076612efcf22db7ec3d000ae093d235e83c273..0cf39c7ee4c5e4f51c2249317458398728ffd3f5 100644 (file)
@@ -1,7 +1,7 @@
 {
   "name": "peertube",
   "description": "Federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.",
-  "version": "1.1.0",
+  "version": "1.2.0",
   "private": true,
   "licence": "AGPLv3",
   "engines": {
     "cli-table": "^0.3.1",
     "commander": "^2.13.0",
     "concurrently": "^4.0.1",
-    "config": "^2.0.1",
+    "config": "^3.0.0",
     "cookie-parser": "^1.4.3",
     "cors": "^2.8.1",
     "create-torrent": "^3.24.5",
     "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017",
     "lodash": "^4.17.10",
     "magnet-uri": "^5.1.4",
-    "marked-man": "^0.2.1",
     "memoizee": "^0.4.14",
     "morgan": "^1.5.3",
     "multer": "^1.1.0",
     "pem": "^1.12.3",
     "pfeed": "^1.1.6",
     "pg": "^7.4.1",
-    "pg-hstore": "^2.3.2",
     "prompt": "^1.0.0",
     "redis": "^2.8.0",
-    "reflect-metadata": "^0.1.10",
+    "reflect-metadata": "^0.1.12",
     "request": "^2.81.0",
-    "safe-buffer": "^5.0.1",
     "scripty": "^1.5.0",
     "sequelize": "4.41.2",
     "sequelize-typescript": "0.6.6",
     "sharp": "^0.21.0",
+    "sitemap": "^2.1.0",
+    "socket.io": "^2.2.0",
     "srt-to-vtt": "^1.1.2",
     "summon-install": "^0.4.3",
     "useragent": "^2.3.0",
     "@types/bcrypt": "^3.0.0",
     "@types/bluebird": "3.5.21",
     "@types/body-parser": "^1.16.3",
-    "@types/bull": "^3.3.12",
+    "@types/bull": "3.4.0",
     "@types/bytes": "^3.0.0",
     "@types/chai": "^4.0.4",
     "@types/chai-json-schema": "^1.4.3",
     "@types/redis": "^2.8.5",
     "@types/request": "^2.0.3",
     "@types/sharp": "^0.21.0",
+    "@types/socket.io": "^2.1.2",
     "@types/supertest": "^2.0.3",
     "@types/validator": "^9.4.0",
     "@types/webtorrent": "^0.98.4",
     "libxmljs": "0.19.5",
     "lint-staged": "^8.0.4",
     "maildev": "^1.0.0-rc3",
+    "marked-man": "^0.2.1",
     "mocha": "^5.0.0",
     "nodemon": "^1.18.6",
     "sass-lint": "^1.12.1",
index 62daf98cf6f1944343617b5095a4788571bcf887..be3eef8026fa37b86a4d36c2316fbd89590cd87d 100755 (executable)
@@ -41,7 +41,7 @@ if [ -z ${1+x} ] || [ "$1" != "--light" ]; then
         languages=("fr_FR")
     else
         # Supported languages
-        languages=("fr_FR" "pt_BR" "sv_SE" "eu_ES" "ca_ES" "cs_CZ" "eo" "zh_Hant_TW" "de_DE" "es_ES" "oc" "zh_Hans_CN")
+        languages=("pl_PL" "it_IT" "ru_RU" "fr_FR" "pt_BR" "sv_SE" "eu_ES" "ca_ES" "cs_CZ" "eo" "zh_Hant_TW" "de_DE" "es_ES" "oc" "zh_Hans_CN")
     fi
 
     for lang in "${languages[@]}"; do
index 235ff52cc31f909694a2d7ee3fa7b983265b6d7c..b897c30baf8e34005477bd7fef835e399348dde9 100755 (executable)
@@ -18,6 +18,7 @@ removeFiles () {
 
 dropRedis () {
   redis-cli KEYS "bull-localhost:900$1*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL
+  redis-cli KEYS "redis-localhost:900$1*" | grep -v empty | xargs --no-run-if-empty redis-cli DEL
 }
 
 for i in $(seq 1 6); do
index 3d37372d1e4aa2692ce018ea9d5338dea6142135..9824bc2f5c2cdfe06d6fe1e34edebacd7347091a 100755 (executable)
@@ -70,11 +70,17 @@ async function fetchGithub (url: string) {
 
 async function fetchZanata (zanataUsername: string, zanataPassword: string) {
   const today = new Date().toISOString().split('T')[0]
-  const url = `https://trad.framasoft.org/zanata/rest/project/peertube/version/develop/contributors/2018-01-01..${today}`
+  const year2018 = `https://trad.framasoft.org/zanata/rest/project/peertube/version/develop/contributors/2018-01-01..2019-01-01`
+  const year2019 = `https://trad.framasoft.org/zanata/rest/project/peertube/version/develop/contributors/2019-01-01..${today}`
 
   const headers = {
     'X-Auth-User': zanataUsername,
     'X-Auth-Token': zanataPassword
   }
-  return get(url, headers)
+  const [ results2018, results2019 ] = await Promise.all([
+    get(year2018, headers),
+    get(year2019, headers)
+  ])
+
+  return results2018.concat(results2019)
 }
index eed3182a6ed37385c586f7dbc00fe0af3288e7bb..ab28f94c8859c965920dda186282857e3cd4f50a 100755 (executable)
@@ -42,6 +42,12 @@ values(VIDEO_CATEGORIES)
   .concat(values(VIDEO_PRIVACIES))
   .concat(values(VIDEO_STATES))
   .concat(values(VIDEO_IMPORT_STATES))
+  .concat([
+    'This video does not exist.',
+    'We cannot fetch the video. Please try again later.',
+    'Sorry',
+    'This video is not available because the remote instance is not responding.'
+  ])
   .forEach(v => serverKeys[v] = v)
 
 // More keys
@@ -103,4 +109,4 @@ function saveToXliffFile (jsonTranslations: TranslationType, cb: Function) {
 function handleError (err: any) {
   console.error(err)
   process.exit(-1)
-}
\ No newline at end of file
+}
index 4ab0b4863fd1ccff1aaaf4ff16652272be98787b..c9e4dbd4b8094bead529388148c9ab0dda73a0a1 100755 (executable)
@@ -19,7 +19,8 @@ async function run () {
 
   const storageOnlyOwnedToPrune = [
     CONFIG.STORAGE.VIDEOS_DIR,
-    CONFIG.STORAGE.TORRENTS_DIR
+    CONFIG.STORAGE.TORRENTS_DIR,
+    CONFIG.STORAGE.REDUNDANCY_DIR
   ]
 
   const storageForAllToPrune = [
@@ -36,6 +37,9 @@ async function run () {
     toDelete = toDelete.concat(await pruneDirectory(directory, false))
   }
 
+  const tmpFiles = await readdir(CONFIG.STORAGE.TMP_DIR)
+  toDelete = toDelete.concat(tmpFiles.map(t => join(CONFIG.STORAGE.TMP_DIR, t)))
+
   if (toDelete.length === 0) {
     console.log('No files to delete.')
     return
@@ -91,6 +95,7 @@ async function askConfirmation () {
         confirm: {
           type: 'string',
           description: 'These following unused files can be deleted, but please check your backups first (bugs happen).' +
+            ' Notice PeerTube must have been stopped when your ran this script.' +
             ' Can we delete these files?',
           default: 'n',
           required: true
index c70b3b42a8bdc608b1c3ea3961462de10135561a..4f7c58edda06cf3b285e4c230a8a2c35be788d79 100755 (executable)
@@ -20,6 +20,16 @@ if [ ! -e "$PEERTUBE_PATH/versions" -o ! -e "$PEERTUBE_PATH/config/production.ya
   exit 1
 fi
 
+if [ -x "$(command -v awk)" ] && [ -x "$(command -v sed)" ] ; then
+    REMAINING=$(df -k $PEERTUBE_PATH | awk '{ print $4}' | sed -n 2p)
+    ONE_GB=$((1024 * 1024))
+    if [ "$REMAINING" -lt "$ONE_GB" ]; then
+    echo "Error - not enough free space for upgrading"
+    echo ""
+    echo "Make sure you have at least 1 GB of free space in $PEERTUBE_PATH"
+    exit 1
+    fi
+fi
 
 # Backup database
 SQL_BACKUP_PATH="$PEERTUBE_PATH/backup/sql-peertube_prod-$(date +"%Y%m%d-%H%M").bak" 
index 3025a6fd7fbf0c5c28f4957bf026d39a7e735e8d..b501518595ac9d790297c488f28fcfe3d42a74cb 100644 (file)
--- a/server.ts
+++ b/server.ts
@@ -28,7 +28,7 @@ import { checkMissedConfig, checkFFmpeg } from './server/initializers/checker-be
 
 // Do not use barrels because we don't want to load all modules here (we need to initialize database first)
 import { logger } from './server/helpers/logger'
-import { API_VERSION, CONFIG, CACHE, HTTP_SIGNATURE } from './server/initializers/constants'
+import { API_VERSION, CONFIG, CACHE } from './server/initializers/constants'
 
 const missed = checkMissedConfig()
 if (missed.length !== 0) {
@@ -53,6 +53,9 @@ if (errorMessage !== null) {
 app.set('trust proxy', CONFIG.TRUST_PROXY)
 
 // Security middleware
+import { baseCSP } from './server/middlewares'
+
+app.use(baseCSP)
 app.use(helmet({
   frameguard: {
     action: 'deny' // we only allow it for /videos/embed, see server/controllers/client.ts
@@ -87,16 +90,17 @@ import {
   servicesRouter,
   webfingerRouter,
   trackerRouter,
-  createWebsocketServer
+  createWebsocketTrackerServer, botsRouter
 } from './server/controllers'
 import { advertiseDoNotTrack } from './server/middlewares/dnt'
 import { Redis } from './server/lib/redis'
-import { BadActorFollowScheduler } from './server/lib/schedulers/bad-actor-follow-scheduler'
+import { ActorFollowScheduler } from './server/lib/schedulers/actor-follow-scheduler'
 import { RemoveOldJobsScheduler } from './server/lib/schedulers/remove-old-jobs-scheduler'
 import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-scheduler'
 import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler'
 import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler'
 import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto'
+import { PeerTubeSocket } from './server/lib/peertube-socket'
 
 // ----------- Command line -----------
 
@@ -133,7 +137,7 @@ app.use(bodyParser.urlencoded({ extended: false }))
 app.use(bodyParser.json({
   type: [ 'application/json', 'application/*+json' ],
   limit: '500kb',
-  verify: (req: express.Request, _, buf: Buffer, encoding: string) => {
+  verify: (req: express.Request, _, buf: Buffer) => {
     const valid = isHTTPSignatureDigestValid(buf, req)
     if (valid !== true) throw new Error('Invalid digest')
   }
@@ -156,6 +160,7 @@ app.use('/', activityPubRouter)
 app.use('/', feedsRouter)
 app.use('/', webfingerRouter)
 app.use('/', trackerRouter)
+app.use('/', botsRouter)
 
 // Static files
 app.use('/', staticRouter)
@@ -185,7 +190,7 @@ app.use(function (err, req, res, next) {
   return res.status(err.status || 500).end()
 })
 
-const server = createWebsocketServer(app)
+const server = createWebsocketTrackerServer(app)
 
 // ----------- Run -----------
 
@@ -215,7 +220,7 @@ async function startApplication () {
   VideosCaptionCache.Instance.init(CONFIG.CACHE.VIDEO_CAPTIONS.SIZE, CACHE.VIDEO_CAPTIONS.MAX_AGE)
 
   // Enable Schedulers
-  BadActorFollowScheduler.Instance.enable()
+  ActorFollowScheduler.Instance.enable()
   RemoveOldJobsScheduler.Instance.enable()
   UpdateVideosScheduler.Instance.enable()
   YoutubeDlUpdateScheduler.Instance.enable()
@@ -224,6 +229,8 @@ async function startApplication () {
   // Redis initialization
   Redis.Instance.init()
 
+  PeerTubeSocket.Instance.init(server)
+
   // Make server listening
   server.listen(port, hostname, () => {
     logger.info('Server listening on %s:%d', hostname, port)
index 86ef2aed1f93cf925593a3c1ed12919c83e889ff..8c02372030c53961cedc96ee23c866542bd2926e 100644 (file)
@@ -14,6 +14,8 @@ import { AccountModel } from '../../models/account/account'
 import { VideoModel } from '../../models/video/video'
 import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
 import { VideoChannelModel } from '../../models/video/video-channel'
+import { JobQueue } from '../../lib/job-queue'
+import { logger } from '../../helpers/logger'
 
 const accountsRouter = express.Router()
 
@@ -57,6 +59,11 @@ export {
 function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) {
   const account: AccountModel = res.locals.account
 
+  if (account.isOutdated()) {
+    JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
+            .catch(err => logger.error('Cannot create AP refresher job for actor %s.', account.Actor.url, { err }))
+  }
+
   return res.json(account.toFormattedJSON())
 }
 
@@ -74,10 +81,10 @@ async function listVideoAccountChannels (req: express.Request, res: express.Resp
 
 async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
   const account: AccountModel = res.locals.account
-  const actorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
+  const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
 
   const resultList = await VideoModel.listForApi({
-    actorId,
+    followerActorId,
     start: req.query.start,
     count: req.query.count,
     sort: req.query.sort,
index 5233e9f68ee5015e7cf488f5732f9289514333b3..255026f46f371467c99f93ff5f8368f843978f5a 100644 (file)
@@ -1,5 +1,5 @@
 import * as express from 'express'
-import { omit } from 'lodash'
+import { omit, snakeCase } from 'lodash'
 import { ServerConfig, UserRight } from '../../../shared'
 import { About } from '../../../shared/models/server/about.model'
 import { CustomConfig } from '../../../shared/models/server/custom-config.model'
@@ -11,6 +11,9 @@ import { ClientHtml } from '../../lib/client-html'
 import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
 import { remove, writeJSON } from 'fs-extra'
 import { getServerCommit } from '../../helpers/utils'
+import { Emailer } from '../../lib/emailer'
+import { isNumeric } from 'validator'
+import { objectConverter } from '../../helpers/core-utils'
 
 const packageJSON = require('../../../../package.json')
 const configRouter = express.Router()
@@ -61,6 +64,12 @@ async function getConfig (req: express.Request, res: express.Response) {
         css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS
       }
     },
+    email: {
+      enabled: Emailer.isEnabled()
+    },
+    contactForm: {
+      enabled: CONFIG.CONTACT_FORM.ENABLED
+    },
     serverVersion: packageJSON.version,
     serverCommit,
     signup: {
@@ -111,6 +120,11 @@ async function getConfig (req: express.Request, res: express.Response) {
     user: {
       videoQuota: CONFIG.USER.VIDEO_QUOTA,
       videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
+    },
+    trending: {
+      videos: {
+        intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
+      }
     }
   }
 
@@ -150,32 +164,10 @@ async function deleteCustomConfig (req: express.Request, res: express.Response,
 }
 
 async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const toUpdate: CustomConfig = req.body
   const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
 
-  // Force number conversion
-  toUpdate.cache.previews.size = parseInt('' + toUpdate.cache.previews.size, 10)
-  toUpdate.cache.captions.size = parseInt('' + toUpdate.cache.captions.size, 10)
-  toUpdate.signup.limit = parseInt('' + toUpdate.signup.limit, 10)
-  toUpdate.user.videoQuota = parseInt('' + toUpdate.user.videoQuota, 10)
-  toUpdate.user.videoQuotaDaily = parseInt('' + toUpdate.user.videoQuotaDaily, 10)
-  toUpdate.transcoding.threads = parseInt('' + toUpdate.transcoding.threads, 10)
-
-  // camelCase to snake_case key
-  const toUpdateJSON = omit(
-    toUpdate,
-    'user.videoQuota',
-    'instance.defaultClientRoute',
-    'instance.shortDescription',
-    'cache.videoCaptions',
-    'signup.requiresEmailVerification'
-  )
-  toUpdateJSON.user['video_quota'] = toUpdate.user.videoQuota
-  toUpdateJSON.user['video_quota_daily'] = toUpdate.user.videoQuotaDaily
-  toUpdateJSON.instance['default_client_route'] = toUpdate.instance.defaultClientRoute
-  toUpdateJSON.instance['short_description'] = toUpdate.instance.shortDescription
-  toUpdateJSON.instance['default_nsfw_policy'] = toUpdate.instance.defaultNSFWPolicy
-  toUpdateJSON.signup['requires_email_verification'] = toUpdate.signup.requiresEmailVerification
+  // camelCase to snake_case key + Force number conversion
+  const toUpdateJSON = convertCustomConfigBody(req.body)
 
   await writeJSON(CONFIG.CUSTOM_FILE, toUpdateJSON, { spaces: 2 })
 
@@ -237,12 +229,16 @@ function customConfig (): CustomConfig {
     admin: {
       email: CONFIG.ADMIN.EMAIL
     },
+    contactForm: {
+      enabled: CONFIG.CONTACT_FORM.ENABLED
+    },
     user: {
       videoQuota: CONFIG.USER.VIDEO_QUOTA,
       videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY
     },
     transcoding: {
       enabled: CONFIG.TRANSCODING.ENABLED,
+      allowAdditionalExtensions: CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS,
       threads: CONFIG.TRANSCODING.THREADS,
       resolutions: {
         '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ],
@@ -264,3 +260,20 @@ function customConfig (): CustomConfig {
     }
   }
 }
+
+function convertCustomConfigBody (body: CustomConfig) {
+  function keyConverter (k: string) {
+    // Transcoding resolutions exception
+    if (/^\d{3,4}p$/.exec(k)) return k
+
+    return snakeCase(k)
+  }
+
+  function valueConverter (v: any) {
+    if (isNumeric(v + '')) return parseInt('' + v, 10)
+
+    return v
+  }
+
+  return objectConverter(body, keyConverter, valueConverter)
+}
diff --git a/server/controllers/api/server/contact.ts b/server/controllers/api/server/contact.ts
new file mode 100644 (file)
index 0000000..b1144c9
--- /dev/null
@@ -0,0 +1,28 @@
+import * as express from 'express'
+import { asyncMiddleware, contactAdministratorValidator } from '../../../middlewares'
+import { Redis } from '../../../lib/redis'
+import { Emailer } from '../../../lib/emailer'
+import { ContactForm } from '../../../../shared/models/server'
+
+const contactRouter = express.Router()
+
+contactRouter.post('/contact',
+  asyncMiddleware(contactAdministratorValidator),
+  asyncMiddleware(contactAdministrator)
+)
+
+async function contactAdministrator (req: express.Request, res: express.Response) {
+  const data = req.body as ContactForm
+
+  await Emailer.Instance.addContactFormJob(data.fromEmail, data.fromName, data.body)
+
+  await Redis.Instance.setContactFormIp(req.ip)
+
+  return res.status(204).end()
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  contactRouter
+}
index c08192a8c7eb925bb4229b64b294e4b5bf71d2db..814248e5f7080c77af5274c6d9e6796e6e39e669 100644 (file)
@@ -3,6 +3,7 @@ import { serverFollowsRouter } from './follows'
 import { statsRouter } from './stats'
 import { serverRedundancyRouter } from './redundancy'
 import { serverBlocklistRouter } from './server-blocklist'
+import { contactRouter } from './contact'
 
 const serverRouter = express.Router()
 
@@ -10,6 +11,7 @@ serverRouter.use('/', serverFollowsRouter)
 serverRouter.use('/', serverRedundancyRouter)
 serverRouter.use('/', statsRouter)
 serverRouter.use('/', serverBlocklistRouter)
+serverRouter.use('/', contactRouter)
 
 // ---------------------------------------------------------------------------
 
index 85803f69ee87e4559f4dbe56bf14f1688122e649..89ffd1717a8e609de92c68823e76f2864f97553f 100644 (file)
@@ -8,6 +8,7 @@ import { VideoCommentModel } from '../../../models/video/video-comment'
 import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
 import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../../initializers/constants'
 import { cacheRoute } from '../../../middlewares/cache'
+import { VideoFileModel } from '../../../models/video/video-file'
 
 const statsRouter = express.Router()
 
@@ -16,11 +17,12 @@ statsRouter.get('/stats',
   asyncMiddleware(getStats)
 )
 
-async function getStats (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function getStats (req: express.Request, res: express.Response) {
   const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats()
   const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats()
   const { totalUsers } = await UserModel.getStats()
   const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats()
+  const { totalLocalVideoFilesSize } = await VideoFileModel.getStats()
 
   const videosRedundancyStats = await Promise.all(
     CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.map(r => {
@@ -32,8 +34,9 @@ async function getStats (req: express.Request, res: express.Response, next: expr
   const data: ServerStats = {
     totalLocalVideos,
     totalLocalVideoViews,
-    totalVideos,
+    totalLocalVideoFilesSize,
     totalLocalVideoComments,
+    totalVideos,
     totalVideoComments,
     totalUsers,
     totalInstanceFollowers,
index 87fab4a407b966b1ec068e0f06667ddefbd18cd5..dbe0718d43efc6135974c662732c57db6eb36fd8 100644 (file)
@@ -38,6 +38,10 @@ import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../h
 import { meRouter } from './me'
 import { deleteUserToken } from '../../../lib/oauth-model'
 import { myBlocklistRouter } from './my-blocklist'
+import { myVideosHistoryRouter } from './my-history'
+import { myNotificationsRouter } from './my-notifications'
+import { Notifier } from '../../../lib/notifier'
+import { mySubscriptionsRouter } from './my-subscriptions'
 
 const auditLogger = auditLoggerFactory('users')
 
@@ -54,7 +58,10 @@ const askSendEmailLimiter = new RateLimit({
 })
 
 const usersRouter = express.Router()
+usersRouter.use('/', myNotificationsRouter)
+usersRouter.use('/', mySubscriptionsRouter)
 usersRouter.use('/', myBlocklistRouter)
+usersRouter.use('/', myVideosHistoryRouter)
 usersRouter.use('/', meRouter)
 
 usersRouter.get('/autocomplete',
@@ -209,6 +216,8 @@ async function registerUser (req: express.Request, res: express.Response) {
     await sendVerifyUserEmail(user)
   }
 
+  Notifier.Instance.notifyOnNewUserRegistration(user)
+
   return res.type('json').status(204).end()
 }
 
index 82299747dab159ca51b839eefdba2090d3249d22..94a2b8732d7ed9a38181c595e2bba6ccede4c729 100644 (file)
@@ -2,47 +2,34 @@ import * as express from 'express'
 import 'multer'
 import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared'
 import { getFormattedObjects } from '../../../helpers/utils'
-import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../../initializers'
+import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers'
 import { sendUpdateActor } from '../../../lib/activitypub/send'
 import {
   asyncMiddleware,
   asyncRetryTransactionMiddleware,
   authenticate,
-  commonVideosFiltersValidator,
   paginationValidator,
   setDefaultPagination,
   setDefaultSort,
-  userSubscriptionAddValidator,
-  userSubscriptionGetValidator,
   usersUpdateMeValidator,
   usersVideoRatingValidator
 } from '../../../middlewares'
-import {
-  areSubscriptionsExistValidator,
-  deleteMeValidator,
-  userSubscriptionsSortValidator,
-  videoImportsSortValidator,
-  videosSortValidator
-} from '../../../middlewares/validators'
+import { deleteMeValidator, videoImportsSortValidator, videosSortValidator } from '../../../middlewares/validators'
 import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
 import { UserModel } from '../../../models/account/user'
 import { VideoModel } from '../../../models/video/video'
 import { VideoSortField } from '../../../../client/src/app/shared/video/sort-field.type'
-import { buildNSFWFilter, createReqFiles } from '../../../helpers/express-utils'
+import { createReqFiles } from '../../../helpers/express-utils'
 import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model'
 import { updateAvatarValidator } from '../../../middlewares/validators/avatar'
 import { updateActorAvatarFile } from '../../../lib/avatar'
 import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
 import { VideoImportModel } from '../../../models/video/video-import'
-import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
-import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
-import { JobQueue } from '../../../lib/job-queue'
-import { logger } from '../../../helpers/logger'
 import { AccountModel } from '../../../models/account/account'
 
 const auditLogger = auditLoggerFactory('users-me')
 
-const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
+const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
 
 const meRouter = express.Router()
 
@@ -98,51 +85,6 @@ meRouter.post('/me/avatar/pick',
   asyncRetryTransactionMiddleware(updateMyAvatar)
 )
 
-// ##### Subscriptions part #####
-
-meRouter.get('/me/subscriptions/videos',
-  authenticate,
-  paginationValidator,
-  videosSortValidator,
-  setDefaultSort,
-  setDefaultPagination,
-  commonVideosFiltersValidator,
-  asyncMiddleware(getUserSubscriptionVideos)
-)
-
-meRouter.get('/me/subscriptions/exist',
-  authenticate,
-  areSubscriptionsExistValidator,
-  asyncMiddleware(areSubscriptionsExist)
-)
-
-meRouter.get('/me/subscriptions',
-  authenticate,
-  paginationValidator,
-  userSubscriptionsSortValidator,
-  setDefaultSort,
-  setDefaultPagination,
-  asyncMiddleware(getUserSubscriptions)
-)
-
-meRouter.post('/me/subscriptions',
-  authenticate,
-  userSubscriptionAddValidator,
-  asyncMiddleware(addUserSubscription)
-)
-
-meRouter.get('/me/subscriptions/:uri',
-  authenticate,
-  userSubscriptionGetValidator,
-  getUserSubscription
-)
-
-meRouter.delete('/me/subscriptions/:uri',
-  authenticate,
-  userSubscriptionGetValidator,
-  asyncRetryTransactionMiddleware(deleteUserSubscription)
-)
-
 // ---------------------------------------------------------------------------
 
 export {
@@ -151,100 +93,6 @@ export {
 
 // ---------------------------------------------------------------------------
 
-async function areSubscriptionsExist (req: express.Request, res: express.Response) {
-  const uris = req.query.uris as string[]
-  const user = res.locals.oauth.token.User as UserModel
-
-  const handles = uris.map(u => {
-    let [ name, host ] = u.split('@')
-    if (host === CONFIG.WEBSERVER.HOST) host = null
-
-    return { name, host, uri: u }
-  })
-
-  const results = await ActorFollowModel.listSubscribedIn(user.Account.Actor.id, handles)
-
-  const existObject: { [id: string ]: boolean } = {}
-  for (const handle of handles) {
-    const obj = results.find(r => {
-      const server = r.ActorFollowing.Server
-
-      return r.ActorFollowing.preferredUsername === handle.name &&
-        (
-          (!server && !handle.host) ||
-          (server.host === handle.host)
-        )
-    })
-
-    existObject[handle.uri] = obj !== undefined
-  }
-
-  return res.json(existObject)
-}
-
-async function addUserSubscription (req: express.Request, res: express.Response) {
-  const user = res.locals.oauth.token.User as UserModel
-  const [ name, host ] = req.body.uri.split('@')
-
-  const payload = {
-    name,
-    host,
-    followerActorId: user.Account.Actor.id
-  }
-
-  JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
-          .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err))
-
-  return res.status(204).end()
-}
-
-function getUserSubscription (req: express.Request, res: express.Response) {
-  const subscription: ActorFollowModel = res.locals.subscription
-
-  return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON())
-}
-
-async function deleteUserSubscription (req: express.Request, res: express.Response) {
-  const subscription: ActorFollowModel = res.locals.subscription
-
-  await sequelizeTypescript.transaction(async t => {
-    return subscription.destroy({ transaction: t })
-  })
-
-  return res.type('json').status(204).end()
-}
-
-async function getUserSubscriptions (req: express.Request, res: express.Response) {
-  const user = res.locals.oauth.token.User as UserModel
-  const actorId = user.Account.Actor.id
-
-  const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort)
-
-  return res.json(getFormattedObjects(resultList.data, resultList.total))
-}
-
-async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
-  const user = res.locals.oauth.token.User as UserModel
-  const resultList = await VideoModel.listForApi({
-    start: req.query.start,
-    count: req.query.count,
-    sort: req.query.sort,
-    includeLocalVideos: false,
-    categoryOneOf: req.query.categoryOneOf,
-    licenceOneOf: req.query.licenceOneOf,
-    languageOneOf: req.query.languageOneOf,
-    tagsOneOf: req.query.tagsOneOf,
-    tagsAllOf: req.query.tagsAllOf,
-    nsfw: buildNSFWFilter(res, req.query.nsfw),
-    filter: req.query.filter as VideoFilter,
-    withFiles: false,
-    actorId: user.Account.Actor.id,
-    user
-  })
-
-  return res.json(getFormattedObjects(resultList.data, resultList.total))
-}
-
 async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
   const user = res.locals.oauth.token.User as UserModel
   const resultList = await VideoModel.listUserVideosForApi(
@@ -330,6 +178,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
   if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy
   if (body.webTorrentEnabled !== undefined) user.webTorrentEnabled = body.webTorrentEnabled
   if (body.autoPlayVideo !== undefined) user.autoPlayVideo = body.autoPlayVideo
+  if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled
 
   await sequelizeTypescript.transaction(async t => {
     const userAccount = await AccountModel.load(user.Account.id)
@@ -348,7 +197,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
   return res.sendStatus(204)
 }
 
-async function updateMyAvatar (req: express.Request, res: express.Response, next: express.NextFunction) {
+async function updateMyAvatar (req: express.Request, res: express.Response) {
   const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
   const user: UserModel = res.locals.oauth.token.user
   const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
new file mode 100644 (file)
index 0000000..6cd782c
--- /dev/null
@@ -0,0 +1,57 @@
+import * as express from 'express'
+import {
+  asyncMiddleware,
+  asyncRetryTransactionMiddleware,
+  authenticate,
+  paginationValidator,
+  setDefaultPagination,
+  userHistoryRemoveValidator
+} from '../../../middlewares'
+import { UserModel } from '../../../models/account/user'
+import { getFormattedObjects } from '../../../helpers/utils'
+import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
+import { sequelizeTypescript } from '../../../initializers'
+
+const myVideosHistoryRouter = express.Router()
+
+myVideosHistoryRouter.get('/me/history/videos',
+  authenticate,
+  paginationValidator,
+  setDefaultPagination,
+  asyncMiddleware(listMyVideosHistory)
+)
+
+myVideosHistoryRouter.post('/me/history/videos/remove',
+  authenticate,
+  userHistoryRemoveValidator,
+  asyncRetryTransactionMiddleware(removeUserHistory)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  myVideosHistoryRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function listMyVideosHistory (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+
+  const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+async function removeUserHistory (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+  const beforeDate = req.body.beforeDate || null
+
+  await sequelizeTypescript.transaction(t => {
+    return UserVideoHistoryModel.removeHistoryBefore(user, beforeDate, t)
+  })
+
+  // Do not send the delete to other instances, we delete OUR copy of this video abuse
+
+  return res.type('json').status(204).end()
+}
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
new file mode 100644 (file)
index 0000000..76cf975
--- /dev/null
@@ -0,0 +1,108 @@
+import * as express from 'express'
+import 'multer'
+import {
+  asyncMiddleware,
+  asyncRetryTransactionMiddleware,
+  authenticate,
+  paginationValidator,
+  setDefaultPagination,
+  setDefaultSort,
+  userNotificationsSortValidator
+} from '../../../middlewares'
+import { UserModel } from '../../../models/account/user'
+import { getFormattedObjects } from '../../../helpers/utils'
+import { UserNotificationModel } from '../../../models/account/user-notification'
+import { meRouter } from './me'
+import {
+  listUserNotificationsValidator,
+  markAsReadUserNotificationsValidator,
+  updateNotificationSettingsValidator
+} from '../../../middlewares/validators/user-notifications'
+import { UserNotificationSetting } from '../../../../shared/models/users'
+import { UserNotificationSettingModel } from '../../../models/account/user-notification-setting'
+
+const myNotificationsRouter = express.Router()
+
+meRouter.put('/me/notification-settings',
+  authenticate,
+  updateNotificationSettingsValidator,
+  asyncRetryTransactionMiddleware(updateNotificationSettings)
+)
+
+myNotificationsRouter.get('/me/notifications',
+  authenticate,
+  paginationValidator,
+  userNotificationsSortValidator,
+  setDefaultSort,
+  setDefaultPagination,
+  listUserNotificationsValidator,
+  asyncMiddleware(listUserNotifications)
+)
+
+myNotificationsRouter.post('/me/notifications/read',
+  authenticate,
+  markAsReadUserNotificationsValidator,
+  asyncMiddleware(markAsReadUserNotifications)
+)
+
+myNotificationsRouter.post('/me/notifications/read-all',
+  authenticate,
+  asyncMiddleware(markAsReadAllUserNotifications)
+)
+
+export {
+  myNotificationsRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function updateNotificationSettings (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+  const body = req.body
+
+  const query = {
+    where: {
+      userId: user.id
+    }
+  }
+
+  const values: UserNotificationSetting = {
+    newVideoFromSubscription: body.newVideoFromSubscription,
+    newCommentOnMyVideo: body.newCommentOnMyVideo,
+    videoAbuseAsModerator: body.videoAbuseAsModerator,
+    blacklistOnMyVideo: body.blacklistOnMyVideo,
+    myVideoPublished: body.myVideoPublished,
+    myVideoImportFinished: body.myVideoImportFinished,
+    newFollow: body.newFollow,
+    newUserRegistration: body.newUserRegistration,
+    commentMention: body.commentMention
+  }
+
+  await UserNotificationSettingModel.update(values, query)
+
+  return res.status(204).end()
+}
+
+async function listUserNotifications (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+
+  const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+async function markAsReadUserNotifications (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+
+  await UserNotificationModel.markAsRead(user.id, req.body.ids)
+
+  return res.status(204).end()
+}
+
+async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) {
+  const user: UserModel = res.locals.oauth.token.User
+
+  await UserNotificationModel.markAllAsRead(user.id)
+
+  return res.status(204).end()
+}
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
new file mode 100644 (file)
index 0000000..accca6d
--- /dev/null
@@ -0,0 +1,170 @@
+import * as express from 'express'
+import 'multer'
+import { getFormattedObjects } from '../../../helpers/utils'
+import { CONFIG, sequelizeTypescript } from '../../../initializers'
+import {
+  asyncMiddleware,
+  asyncRetryTransactionMiddleware,
+  authenticate,
+  commonVideosFiltersValidator,
+  paginationValidator,
+  setDefaultPagination,
+  setDefaultSort,
+  userSubscriptionAddValidator,
+  userSubscriptionGetValidator
+} from '../../../middlewares'
+import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators'
+import { UserModel } from '../../../models/account/user'
+import { VideoModel } from '../../../models/video/video'
+import { buildNSFWFilter } from '../../../helpers/express-utils'
+import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
+import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
+import { JobQueue } from '../../../lib/job-queue'
+import { logger } from '../../../helpers/logger'
+
+const mySubscriptionsRouter = express.Router()
+
+mySubscriptionsRouter.get('/me/subscriptions/videos',
+  authenticate,
+  paginationValidator,
+  videosSortValidator,
+  setDefaultSort,
+  setDefaultPagination,
+  commonVideosFiltersValidator,
+  asyncMiddleware(getUserSubscriptionVideos)
+)
+
+mySubscriptionsRouter.get('/me/subscriptions/exist',
+  authenticate,
+  areSubscriptionsExistValidator,
+  asyncMiddleware(areSubscriptionsExist)
+)
+
+mySubscriptionsRouter.get('/me/subscriptions',
+  authenticate,
+  paginationValidator,
+  userSubscriptionsSortValidator,
+  setDefaultSort,
+  setDefaultPagination,
+  asyncMiddleware(getUserSubscriptions)
+)
+
+mySubscriptionsRouter.post('/me/subscriptions',
+  authenticate,
+  userSubscriptionAddValidator,
+  asyncMiddleware(addUserSubscription)
+)
+
+mySubscriptionsRouter.get('/me/subscriptions/:uri',
+  authenticate,
+  userSubscriptionGetValidator,
+  getUserSubscription
+)
+
+mySubscriptionsRouter.delete('/me/subscriptions/:uri',
+  authenticate,
+  userSubscriptionGetValidator,
+  asyncRetryTransactionMiddleware(deleteUserSubscription)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  mySubscriptionsRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function areSubscriptionsExist (req: express.Request, res: express.Response) {
+  const uris = req.query.uris as string[]
+  const user = res.locals.oauth.token.User as UserModel
+
+  const handles = uris.map(u => {
+    let [ name, host ] = u.split('@')
+    if (host === CONFIG.WEBSERVER.HOST) host = null
+
+    return { name, host, uri: u }
+  })
+
+  const results = await ActorFollowModel.listSubscribedIn(user.Account.Actor.id, handles)
+
+  const existObject: { [id: string ]: boolean } = {}
+  for (const handle of handles) {
+    const obj = results.find(r => {
+      const server = r.ActorFollowing.Server
+
+      return r.ActorFollowing.preferredUsername === handle.name &&
+        (
+          (!server && !handle.host) ||
+          (server.host === handle.host)
+        )
+    })
+
+    existObject[handle.uri] = obj !== undefined
+  }
+
+  return res.json(existObject)
+}
+
+async function addUserSubscription (req: express.Request, res: express.Response) {
+  const user = res.locals.oauth.token.User as UserModel
+  const [ name, host ] = req.body.uri.split('@')
+
+  const payload = {
+    name,
+    host,
+    followerActorId: user.Account.Actor.id
+  }
+
+  JobQueue.Instance.createJob({ type: 'activitypub-follow', payload })
+          .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err))
+
+  return res.status(204).end()
+}
+
+function getUserSubscription (req: express.Request, res: express.Response) {
+  const subscription: ActorFollowModel = res.locals.subscription
+
+  return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON())
+}
+
+async function deleteUserSubscription (req: express.Request, res: express.Response) {
+  const subscription: ActorFollowModel = res.locals.subscription
+
+  await sequelizeTypescript.transaction(async t => {
+    return subscription.destroy({ transaction: t })
+  })
+
+  return res.type('json').status(204).end()
+}
+
+async function getUserSubscriptions (req: express.Request, res: express.Response) {
+  const user = res.locals.oauth.token.User as UserModel
+  const actorId = user.Account.Actor.id
+
+  const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort)
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
+
+async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
+  const user = res.locals.oauth.token.User as UserModel
+  const resultList = await VideoModel.listForApi({
+    start: req.query.start,
+    count: req.query.count,
+    sort: req.query.sort,
+    includeLocalVideos: false,
+    categoryOneOf: req.query.categoryOneOf,
+    licenceOneOf: req.query.licenceOneOf,
+    languageOneOf: req.query.languageOneOf,
+    tagsOneOf: req.query.tagsOneOf,
+    tagsAllOf: req.query.tagsAllOf,
+    nsfw: buildNSFWFilter(res, req.query.nsfw),
+    filter: req.query.filter as VideoFilter,
+    withFiles: false,
+    followerActorId: user.Account.Actor.id,
+    user
+  })
+
+  return res.json(getFormattedObjects(resultList.data, resultList.total))
+}
index 9bf3c5fd808e1e2c9fa865442f9435a830b739a2..db7602139540d85a99963e91f9754c3f489cf167 100644 (file)
@@ -22,7 +22,7 @@ import { createVideoChannel } from '../../lib/video-channel'
 import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
 import { setAsyncActorKeys } from '../../lib/activitypub'
 import { AccountModel } from '../../models/account/account'
-import { CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
+import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers'
 import { logger } from '../../helpers/logger'
 import { VideoModel } from '../../models/video/video'
 import { updateAvatarValidator } from '../../middlewares/validators/avatar'
@@ -30,9 +30,10 @@ import { updateActorAvatarFile } from '../../lib/avatar'
 import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
 import { resetSequelizeInstance } from '../../helpers/database-utils'
 import { UserModel } from '../../models/account/user'
+import { JobQueue } from '../../lib/job-queue'
 
 const auditLogger = auditLoggerFactory('channels')
-const reqAvatarFile = createReqFiles([ 'avatarfile' ], IMAGE_MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.AVATARS_DIR })
+const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
 
 const videoChannelRouter = express.Router()
 
@@ -197,15 +198,20 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
 async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
 
+  if (videoChannelWithVideos.isOutdated()) {
+    JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } })
+            .catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err }))
+  }
+
   return res.json(videoChannelWithVideos.toFormattedJSON())
 }
 
 async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoChannelInstance: VideoChannelModel = res.locals.videoChannel
-  const actorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
+  const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
 
   const resultList = await VideoModel.listForApi({
-    actorId,
+    followerActorId,
     start: req.query.start,
     count: req.query.count,
     sort: req.query.sort,
index d0c81804bfe538036938e69bb4432880704554da..fe0a95cd51780eeacfe7c268896a7a3f77a8d636 100644 (file)
@@ -22,6 +22,7 @@ import { VideoModel } from '../../../models/video/video'
 import { VideoAbuseModel } from '../../../models/video/video-abuse'
 import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
 import { UserModel } from '../../../models/account/user'
+import { Notifier } from '../../../lib/notifier'
 
 const auditLogger = auditLoggerFactory('abuse')
 const abuseVideoRouter = express.Router()
@@ -117,6 +118,8 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) {
       await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance)
     }
 
+    Notifier.Instance.notifyOnNewVideoAbuse(videoAbuseInstance)
+
     auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON()))
 
     return videoAbuseInstance
index 7f803c8e939a3668050b8438f7082b284e717634..43b0516e79a8eed2af326e0294bb700509f710bb 100644 (file)
@@ -16,6 +16,10 @@ import {
 } from '../../../middlewares'
 import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
 import { sequelizeTypescript } from '../../../initializers'
+import { Notifier } from '../../../lib/notifier'
+import { VideoModel } from '../../../models/video/video'
+import { sendCreateVideo, sendDeleteVideo, sendUpdateVideo } from '../../../lib/activitypub/send'
+import { federateVideoIfNeeded } from '../../../lib/activitypub'
 
 const blacklistRouter = express.Router()
 
@@ -64,16 +68,26 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
 
   const toCreate = {
     videoId: videoInstance.id,
+    unfederated: body.unfederate === true,
     reason: body.reason
   }
 
-  await VideoBlacklistModel.create(toCreate)
+  const blacklist = await VideoBlacklistModel.create(toCreate)
+  blacklist.Video = videoInstance
+
+  if (body.unfederate === true) {
+    await sendDeleteVideo(videoInstance, undefined)
+  }
+
+  Notifier.Instance.notifyOnVideoBlacklist(blacklist)
+
+  logger.info('Video %s blacklisted.', res.locals.video.uuid)
+
   return res.type('json').status(204).end()
 }
 
 async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
   const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
-  logger.info(videoBlacklist)
 
   if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason
 
@@ -92,11 +106,20 @@ async function listBlacklist (req: express.Request, res: express.Response, next:
 
 async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
   const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel
+  const video: VideoModel = res.locals.video
 
-  await sequelizeTypescript.transaction(t => {
-    return videoBlacklist.destroy({ transaction: t })
+  await sequelizeTypescript.transaction(async t => {
+    const unfederated = videoBlacklist.unfederated
+    await videoBlacklist.destroy({ transaction: t })
+
+    // Re federate the video
+    if (unfederated === true) {
+      await federateVideoIfNeeded(video, true, t)
+    }
   })
 
+  Notifier.Instance.notifyOnVideoUnblacklist(video)
+
   logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
 
   return res.type('json').status(204).end()
index 3ba9181891e008013c5d2e2d7901f63fa2c2c077..9b36613688a58c46fdc715d0a90848162196d759 100644 (file)
@@ -2,7 +2,7 @@ import * as express from 'express'
 import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
 import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
 import { createReqFiles } from '../../../helpers/express-utils'
-import { CONFIG, sequelizeTypescript, VIDEO_CAPTIONS_MIMETYPE_EXT } from '../../../initializers'
+import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers'
 import { getFormattedObjects } from '../../../helpers/utils'
 import { VideoCaptionModel } from '../../../models/video/video-caption'
 import { VideoModel } from '../../../models/video/video'
@@ -12,7 +12,7 @@ import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
 
 const reqVideoCaptionAdd = createReqFiles(
   [ 'captionfile' ],
-  VIDEO_CAPTIONS_MIMETYPE_EXT,
+  MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT,
   {
     captionfile: CONFIG.STORAGE.CAPTIONS_DIR
   }
index 3875c8f79baf1c0e3f7041cd8bf54549e0ecca17..70c1148ba3839945a4a45a5d9556f66d5bb486b0 100644 (file)
@@ -26,6 +26,7 @@ import { VideoCommentModel } from '../../../models/video/video-comment'
 import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger'
 import { AccountModel } from '../../../models/account/account'
 import { UserModel } from '../../../models/account/user'
+import { Notifier } from '../../../lib/notifier'
 
 const auditLogger = auditLoggerFactory('comments')
 const videoCommentRouter = express.Router()
@@ -119,6 +120,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
     }, t)
   })
 
+  Notifier.Instance.notifyOnNewComment(comment)
   auditLogger.create(getAuditIdFromRes(res), new CommentAuditView(comment.toFormattedJSON()))
 
   return res.json({
@@ -140,6 +142,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
     }, t)
   })
 
+  Notifier.Instance.notifyOnNewComment(comment)
   auditLogger.create(getAuditIdFromRes(res), new CommentAuditView(comment.toFormattedJSON()))
 
   return res.json({ comment: comment.toFormattedJSON() }).end()
index 398fd5a7f68e9379a0cfb1f6091ed38d2e3dabda..98366cd82cff70235288cd37330cf243fb2a9471 100644 (file)
@@ -3,14 +3,7 @@ import * as magnetUtil from 'magnet-uri'
 import 'multer'
 import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
 import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
-import {
-  CONFIG,
-  IMAGE_MIMETYPE_EXT,
-  PREVIEWS_SIZE,
-  sequelizeTypescript,
-  THUMBNAILS_SIZE,
-  TORRENT_MIMETYPE_EXT
-} from '../../../initializers'
+import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers'
 import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
 import { createReqFiles } from '../../../helpers/express-utils'
 import { logger } from '../../../helpers/logger'
@@ -28,18 +21,18 @@ import { VideoChannelModel } from '../../../models/video/video-channel'
 import * as Bluebird from 'bluebird'
 import * as parseTorrent from 'parse-torrent'
 import { getSecureTorrentName } from '../../../helpers/utils'
-import { readFile, rename } from 'fs-extra'
+import { readFile, move } from 'fs-extra'
 
 const auditLogger = auditLoggerFactory('video-imports')
 const videoImportsRouter = express.Router()
 
 const reqVideoFileImport = createReqFiles(
   [ 'thumbnailfile', 'previewfile', 'torrentfile' ],
-  Object.assign({}, TORRENT_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
+  Object.assign({}, MIMETYPES.TORRENT.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
   {
-    thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
-    previewfile: CONFIG.STORAGE.PREVIEWS_DIR,
-    torrentfile: CONFIG.STORAGE.TORRENTS_DIR
+    thumbnailfile: CONFIG.STORAGE.TMP_DIR,
+    previewfile: CONFIG.STORAGE.TMP_DIR,
+    torrentfile: CONFIG.STORAGE.TMP_DIR
   }
 )
 
@@ -78,7 +71,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
 
     // Rename the torrent to a secured name
     const newTorrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, getSecureTorrentName(torrentName))
-    await rename(torrentfile.path, newTorrentPath)
+    await move(torrentfile.path, newTorrentPath)
     torrentfile.path = newTorrentPath
 
     const buf = await readFile(torrentfile.path)
index 3d1b2e1a225477931a55e4d8a63a27bd75fc5005..2b2dfa7ca40bbbcf999a5de07dfdc662217c81d5 100644 (file)
@@ -8,14 +8,13 @@ import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../
 import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
 import {
   CONFIG,
-  IMAGE_MIMETYPE_EXT,
+  MIMETYPES,
   PREVIEWS_SIZE,
   sequelizeTypescript,
   THUMBNAILS_SIZE,
   VIDEO_CATEGORIES,
   VIDEO_LANGUAGES,
   VIDEO_LICENCES,
-  VIDEO_MIMETYPE_EXT,
   VIDEO_PRIVACIES
 } from '../../../initializers'
 import {
@@ -57,27 +56,28 @@ import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-u
 import { videoCaptionsRouter } from './captions'
 import { videoImportsRouter } from './import'
 import { resetSequelizeInstance } from '../../../helpers/database-utils'
-import { rename } from 'fs-extra'
+import { move } from 'fs-extra'
 import { watchingRouter } from './watching'
+import { Notifier } from '../../../lib/notifier'
 
 const auditLogger = auditLoggerFactory('videos')
 const videosRouter = express.Router()
 
 const reqVideoFileAdd = createReqFiles(
   [ 'videofile', 'thumbnailfile', 'previewfile' ],
-  Object.assign({}, VIDEO_MIMETYPE_EXT, IMAGE_MIMETYPE_EXT),
+  Object.assign({}, MIMETYPES.VIDEO.MIMETYPE_EXT, MIMETYPES.IMAGE.MIMETYPE_EXT),
   {
-    videofile: CONFIG.STORAGE.VIDEOS_DIR,
-    thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
-    previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+    videofile: CONFIG.STORAGE.TMP_DIR,
+    thumbnailfile: CONFIG.STORAGE.TMP_DIR,
+    previewfile: CONFIG.STORAGE.TMP_DIR
   }
 )
 const reqVideoFileUpdate = createReqFiles(
   [ 'thumbnailfile', 'previewfile' ],
-  IMAGE_MIMETYPE_EXT,
+  MIMETYPES.IMAGE.MIMETYPE_EXT,
   {
-    thumbnailfile: CONFIG.STORAGE.THUMBNAILS_DIR,
-    previewfile: CONFIG.STORAGE.PREVIEWS_DIR
+    thumbnailfile: CONFIG.STORAGE.TMP_DIR,
+    previewfile: CONFIG.STORAGE.TMP_DIR
   }
 )
 
@@ -208,7 +208,7 @@ async function addVideo (req: express.Request, res: express.Response) {
   // Move physical file
   const videoDir = CONFIG.STORAGE.VIDEOS_DIR
   const destination = join(videoDir, video.getVideoFilename(videoFile))
-  await rename(videoPhysicalFile.path, destination)
+  await move(videoPhysicalFile.path, destination)
   // This is important in case if there is another attempt in the retry process
   videoPhysicalFile.filename = video.getVideoFilename(videoFile)
   videoPhysicalFile.path = destination
@@ -271,6 +271,8 @@ async function addVideo (req: express.Request, res: express.Response) {
     return videoCreated
   })
 
+  Notifier.Instance.notifyOnNewVideo(videoCreated)
+
   if (video.state === VideoState.TO_TRANSCODE) {
     // Put uuid because we don't have id auto incremented for now
     const dataInput = {
@@ -295,6 +297,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
   const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
   const videoInfoToUpdate: VideoUpdate = req.body
   const wasPrivateVideo = videoInstance.privacy === VideoPrivacy.PRIVATE
+  const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED
 
   // Process thumbnail or create it from the video
   if (req.files && req.files['thumbnailfile']) {
@@ -309,10 +312,8 @@ async function updateVideo (req: express.Request, res: express.Response) {
   }
 
   try {
-    await sequelizeTypescript.transaction(async t => {
-      const sequelizeOptions = {
-        transaction: t
-      }
+    const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => {
+      const sequelizeOptions = { transaction: t }
       const oldVideoChannel = videoInstance.VideoChannel
 
       if (videoInfoToUpdate.name !== undefined) videoInstance.set('name', videoInfoToUpdate.name)
@@ -363,7 +364,11 @@ async function updateVideo (req: express.Request, res: express.Response) {
       }
 
       const isNewVideo = wasPrivateVideo && videoInstanceUpdated.privacy !== VideoPrivacy.PRIVATE
-      await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
+
+      // Don't send update if the video was unfederated
+      if (!videoInstanceUpdated.VideoBlacklist || videoInstanceUpdated.VideoBlacklist.unfederated === false) {
+        await federateVideoIfNeeded(videoInstanceUpdated, isNewVideo, t)
+      }
 
       auditLogger.update(
         getAuditIdFromRes(res),
@@ -371,7 +376,13 @@ async function updateVideo (req: express.Request, res: express.Response) {
         oldVideoAuditView
       )
       logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid)
+
+      return videoInstanceUpdated
     })
+
+    if (wasUnlistedVideo || wasPrivateVideo) {
+      Notifier.Instance.notifyOnNewVideo(videoInstanceUpdated)
+    }
   } catch (err) {
     // Force fields we want to update
     // If the transaction is retried, sequelize will think the object has not changed
@@ -388,7 +399,7 @@ function getVideo (req: express.Request, res: express.Response) {
   const videoInstance = res.locals.video
 
   if (videoInstance.isOutdated()) {
-    JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoInstance.url } })
+    JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoInstance.url } })
       .catch(err => logger.error('Cannot create AP refresher job for video %s.', videoInstance.url, { err }))
   }
 
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts
new file mode 100644 (file)
index 0000000..2db86a2
--- /dev/null
@@ -0,0 +1,101 @@
+import * as express from 'express'
+import { asyncMiddleware } from '../middlewares'
+import { CONFIG, ROUTE_CACHE_LIFETIME } from '../initializers'
+import * as sitemapModule from 'sitemap'
+import { logger } from '../helpers/logger'
+import { VideoModel } from '../models/video/video'
+import { VideoChannelModel } from '../models/video/video-channel'
+import { AccountModel } from '../models/account/account'
+import { cacheRoute } from '../middlewares/cache'
+import { buildNSFWFilter } from '../helpers/express-utils'
+import { truncate } from 'lodash'
+
+const botsRouter = express.Router()
+
+// Special route that add OpenGraph and oEmbed tags
+// Do not use a template engine for a so little thing
+botsRouter.use('/sitemap.xml',
+  asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP)),
+  asyncMiddleware(getSitemap)
+)
+
+// ---------------------------------------------------------------------------
+
+export {
+  botsRouter
+}
+
+// ---------------------------------------------------------------------------
+
+async function getSitemap (req: express.Request, res: express.Response) {
+  let urls = getSitemapBasicUrls()
+
+  urls = urls.concat(await getSitemapLocalVideoUrls())
+  urls = urls.concat(await getSitemapVideoChannelUrls())
+  urls = urls.concat(await getSitemapAccountUrls())
+
+  const sitemap = sitemapModule.createSitemap({
+    hostname: CONFIG.WEBSERVER.URL,
+    urls: urls
+  })
+
+  sitemap.toXML((err, xml) => {
+    if (err) {
+      logger.error('Cannot generate sitemap.', { err })
+      return res.sendStatus(500)
+    }
+
+    res.header('Content-Type', 'application/xml')
+    res.send(xml)
+  })
+}
+
+async function getSitemapVideoChannelUrls () {
+  const rows = await VideoChannelModel.listLocalsForSitemap('createdAt')
+
+  return rows.map(channel => ({
+    url: CONFIG.WEBSERVER.URL + '/video-channels/' + channel.Actor.preferredUsername
+  }))
+}
+
+async function getSitemapAccountUrls () {
+  const rows = await AccountModel.listLocalsForSitemap('createdAt')
+
+  return rows.map(channel => ({
+    url: CONFIG.WEBSERVER.URL + '/accounts/' + channel.Actor.preferredUsername
+  }))
+}
+
+async function getSitemapLocalVideoUrls () {
+  const resultList = await VideoModel.listForApi({
+    start: 0,
+    count: undefined,
+    sort: 'createdAt',
+    includeLocalVideos: true,
+    nsfw: buildNSFWFilter(),
+    filter: 'local',
+    withFiles: false
+  })
+
+  return resultList.data.map(v => ({
+    url: CONFIG.WEBSERVER.URL + '/videos/watch/' + v.uuid,
+    video: [
+      {
+        title: v.name,
+        // Sitemap description should be < 2000 characters
+        description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
+        player_loc: CONFIG.WEBSERVER.URL + '/videos/embed/' + v.uuid,
+        thumbnail_loc: CONFIG.WEBSERVER.URL + v.getThumbnailStaticPath()
+      }
+    ]
+  }))
+}
+
+function getSitemapBasicUrls () {
+  const paths = [
+    '/about/instance',
+    '/videos/local'
+  ]
+
+  return paths.map(p => ({ url: CONFIG.WEBSERVER.URL + p }))
+}
index 73b40cf6513c3941fe5fe9ef1b76c9691db0a456..f17f2a5d29cb0d1bc4e56aeb5a74706742ed4a04 100644 (file)
@@ -2,7 +2,7 @@ import * as express from 'express'
 import { join } from 'path'
 import { root } from '../helpers/core-utils'
 import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers'
-import { asyncMiddleware } from '../middlewares'
+import { asyncMiddleware, embedCSP } from '../middlewares'
 import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n'
 import { ClientHtml } from '../lib/client-html'
 import { logger } from '../helpers/logger'
@@ -16,21 +16,20 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
 
 // Special route that add OpenGraph and oEmbed tags
 // Do not use a template engine for a so little thing
-clientsRouter.use('/videos/watch/:id',
-  asyncMiddleware(generateWatchHtmlPage)
-)
+clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage))
 
-clientsRouter.use('' +
+clientsRouter.use(
   '/videos/embed',
-  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+  embedCSP,
+  (req: express.Request, res: express.Response) => {
     res.removeHeader('X-Frame-Options')
     res.sendFile(embedPath)
   }
 )
-clientsRouter.use('' +
-  '/videos/test-embed', (req: express.Request, res: express.Response, next: express.NextFunction) => {
-  res.sendFile(testEmbedPath)
-})
+clientsRouter.use(
+  '/videos/test-embed',
+  (req: express.Request, res: express.Response) => res.sendFile(testEmbedPath)
+)
 
 // Static HTML/CSS/JS client files
 
@@ -89,7 +88,7 @@ export {
 // ---------------------------------------------------------------------------
 
 async function generateHTMLPage (req: express.Request, res: express.Response, paramLang?: string) {
-  const html = await ClientHtml.getIndexHTML(req, res, paramLang)
+  const html = await ClientHtml.getDefaultHTMLPage(req, res, paramLang)
 
   return sendHTML(html, res)
 }
index ccb9b60292e7a26818f602290cbb95ef24d79860..960085af1af685d017c79e71fd324959105453a6 100644 (file)
@@ -56,7 +56,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
 
   // Adding video items to the feed, one at a time
   comments.forEach(comment => {
-    const link = CONFIG.WEBSERVER.URL + '/videos/watch/' + comment.Video.uuid + ';threadId=' + comment.getThreadId()
+    const link = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
 
     feed.addItem({
       title: `${comment.Video.name} - ${comment.Account.getDisplayName()}`,
index 197fa897af8718197d853fc3299d1cf013551316..a88a03c79e87501f29bb59e6a54f1226b3ee6f89 100644 (file)
@@ -6,3 +6,4 @@ export * from './services'
 export * from './static'
 export * from './webfinger'
 export * from './tracker'
+export * from './bots'
index 75e30353c37a868df3398b482ae4abcf9ddb54d1..4fd58f70c008b11704042f558fce4d42eaf3354a 100644 (file)
@@ -34,12 +34,17 @@ staticRouter.use(
 )
 
 // Videos path for webseeding
-const videosPhysicalPath = CONFIG.STORAGE.VIDEOS_DIR
 staticRouter.use(
   STATIC_PATHS.WEBSEED,
   cors(),
-  express.static(videosPhysicalPath)
+  express.static(CONFIG.STORAGE.VIDEOS_DIR, { fallthrough: false }) // 404 because we don't have this video
 )
+staticRouter.use(
+  STATIC_PATHS.REDUNDANCY,
+  cors(),
+  express.static(CONFIG.STORAGE.REDUNDANCY_DIR, { fallthrough: false }) // 404 because we don't have this video
+)
+
 staticRouter.use(
   STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension',
   asyncMiddleware(videosGetValidator),
@@ -131,6 +136,12 @@ staticRouter.use('/.well-known/dnt/',
   }
 )
 
+staticRouter.use('/.well-known/change-password',
+  (_, res: express.Response) => {
+    res.redirect('/my-account/settings')
+  }
+)
+
 // ---------------------------------------------------------------------------
 
 export {
index 9bc7586d19a23412e4652cf28228e60f0971fd08..1deb8c40292f6ae997eb77004b858c1165481e56 100644 (file)
@@ -6,6 +6,7 @@ import * as proxyAddr from 'proxy-addr'
 import { Server as WebSocketServer } from 'ws'
 import { CONFIG, TRACKER_RATE_LIMITS } from '../initializers/constants'
 import { VideoFileModel } from '../models/video/video-file'
+import { parse } from 'url'
 
 const TrackerServer = bitTorrentTracker.Server
 
@@ -59,16 +60,26 @@ const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer)
 trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' }))
 trackerRouter.get('/tracker/scrape', (req, res) => onHttpRequest(req, res, { action: 'scrape' }))
 
-function createWebsocketServer (app: express.Application) {
+function createWebsocketTrackerServer (app: express.Application) {
   const server = http.createServer(app)
-  const wss = new WebSocketServer({ server: server, path: '/tracker/socket' })
+  const wss = new WebSocketServer({ noServer: true })
+
   wss.on('connection', function (ws, req) {
-    const ip = proxyAddr(req, CONFIG.TRUST_PROXY)
-    ws['ip'] = ip
+    ws['ip'] = proxyAddr(req, CONFIG.TRUST_PROXY)
 
     trackerServer.onWebSocketConnection(ws)
   })
 
+  server.on('upgrade', (request, socket, head) => {
+    const pathname = parse(request.url).pathname
+
+    if (pathname === '/tracker/socket') {
+      wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request))
+    }
+
+    // Don't destroy socket, we have Socket.IO too
+  })
+
   return server
 }
 
@@ -76,7 +87,7 @@ function createWebsocketServer (app: express.Application) {
 
 export {
   trackerRouter,
-  createWebsocketServer
+  createWebsocketTrackerServer
 }
 
 // ---------------------------------------------------------------------------
index 79b76fa0b89e2636bf6dc177363f2f8c9ffbc1c3..f1430055fe93c34a456efc1f220e141a2b3e8df6 100644 (file)
@@ -106,7 +106,7 @@ function buildSignedActivity (byActor: ActorModel, data: Object) {
   return signJsonLDObject(byActor, activity) as Promise<Activity>
 }
 
-function getAPUrl (activity: string | { id: string }) {
+function getAPId (activity: string | { id: string }) {
   if (typeof activity === 'string') return activity
 
   return activity.id
@@ -123,7 +123,7 @@ function checkUrlsSameHost (url1: string, url2: string) {
 
 export {
   checkUrlsSameHost,
-  getAPUrl,
+  getAPId,
   activityPubContextify,
   activityPubCollectionPagination,
   buildSignedActivity
index 660dce65c3c5c0292dd3e680775c3356542300a7..0fb11a1251a694116c6feceff681399f7f33bf17 100644 (file)
@@ -2,7 +2,7 @@ import { join } from 'path'
 import { CONFIG } from '../initializers'
 import { VideoCaptionModel } from '../models/video/video-caption'
 import * as srt2vtt from 'srt-to-vtt'
-import { createReadStream, createWriteStream, remove, rename } from 'fs-extra'
+import { createReadStream, createWriteStream, remove, move } from 'fs-extra'
 
 async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) {
   const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
@@ -13,7 +13,7 @@ async function moveAndProcessCaptionFile (physicalFile: { filename: string, path
     await convertSrtToVtt(physicalFile.path, destination)
     await remove(physicalFile.path)
   } else { // Just move the vtt file
-    await rename(physicalFile.path, destination)
+    await move(physicalFile.path, destination, { overwrite: true })
   }
 
   // This is important in case if there is another attempt in the retry process
index 84e33c0e987a253dded722800e7d9b0f460c96ac..3fb824e36220832da1b44c42ed383f5a05daea26 100644 (file)
@@ -11,6 +11,25 @@ import * as pem from 'pem'
 import { URL } from 'url'
 import { truncate } from 'lodash'
 import { exec } from 'child_process'
+import { isArray } from './custom-validators/misc'
+
+const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => {
+  if (!oldObject || typeof oldObject !== 'object') {
+    return valueConverter(oldObject)
+  }
+
+  if (isArray(oldObject)) {
+    return oldObject.map(e => objectConverter(e, keyConverter, valueConverter))
+  }
+
+  const newObject = {}
+  Object.keys(oldObject).forEach(oldKey => {
+    const newKey = keyConverter(oldKey)
+    newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter)
+  })
+
+  return newObject
+}
 
 const timeTable = {
   ms:           1,
@@ -235,6 +254,7 @@ export {
   isTestInstance,
   isProdInstance,
 
+  objectConverter,
   root,
   escapeHTML,
   pageToStartAndCount,
index 2562ead9b874549317b3ebc0b758a84ebc0f918e..b24590d9d752f7a0e20666c680ad2e7babd1a92b 100644 (file)
@@ -1,26 +1,14 @@
 import * as validator from 'validator'
 import { Activity, ActivityType } from '../../../../shared/models/activitypub'
-import {
-  isActorAcceptActivityValid,
-  isActorDeleteActivityValid,
-  isActorFollowActivityValid,
-  isActorRejectActivityValid,
-  isActorUpdateActivityValid
-} from './actor'
-import { isAnnounceActivityValid } from './announce'
-import { isActivityPubUrlValid } from './misc'
-import { isDislikeActivityValid, isLikeActivityValid } from './rate'
-import { isUndoActivityValid } from './undo'
-import { isVideoCommentCreateActivityValid, isVideoCommentDeleteActivityValid } from './video-comments'
-import {
-  isVideoFlagValid,
-  isVideoTorrentDeleteActivityValid,
-  sanitizeAndCheckVideoTorrentCreateActivity,
-  sanitizeAndCheckVideoTorrentUpdateActivity
-} from './videos'
+import { sanitizeAndCheckActorObject } from './actor'
+import { isActivityPubUrlValid, isBaseActivityValid, isObjectValid } from './misc'
+import { isDislikeActivityValid } from './rate'
+import { sanitizeAndCheckVideoCommentObject } from './video-comments'
+import { sanitizeAndCheckVideoTorrentObject } from './videos'
 import { isViewActivityValid } from './view'
 import { exists } from '../misc'
-import { isCacheFileCreateActivityValid, isCacheFileUpdateActivityValid } from './cache-file'
+import { isCacheFileObjectValid } from './cache-file'
+import { isFlagActivityValid } from './flag'
 
 function isRootActivityValid (activity: any) {
   return Array.isArray(activity['@context']) && (
@@ -46,7 +34,10 @@ const activityCheckers: { [ P in ActivityType ]: (activity: Activity) => boolean
   Reject: checkRejectActivity,
   Announce: checkAnnounceActivity,
   Undo: checkUndoActivity,
-  Like: checkLikeActivity
+  Like: checkLikeActivity,
+  View: checkViewActivity,
+  Flag: checkFlagActivity,
+  Dislike: checkDislikeActivity
 }
 
 function isActivityValid (activity: any) {
@@ -66,47 +57,79 @@ export {
 
 // ---------------------------------------------------------------------------
 
+function checkViewActivity (activity: any) {
+  return isBaseActivityValid(activity, 'View') &&
+    isViewActivityValid(activity)
+}
+
+function checkFlagActivity (activity: any) {
+  return isBaseActivityValid(activity, 'Flag') &&
+    isFlagActivityValid(activity)
+}
+
+function checkDislikeActivity (activity: any) {
+  return isBaseActivityValid(activity, 'Dislike') &&
+    isDislikeActivityValid(activity)
+}
+
 function checkCreateActivity (activity: any) {
-  return isViewActivityValid(activity) ||
-    isDislikeActivityValid(activity) ||
-    sanitizeAndCheckVideoTorrentCreateActivity(activity) ||
-    isVideoFlagValid(activity) ||
-    isVideoCommentCreateActivityValid(activity) ||
-    isCacheFileCreateActivityValid(activity)
+  return isBaseActivityValid(activity, 'Create') &&
+    (
+      isViewActivityValid(activity.object) ||
+      isDislikeActivityValid(activity.object) ||
+      isFlagActivityValid(activity.object) ||
+
+      isCacheFileObjectValid(activity.object) ||
+      sanitizeAndCheckVideoCommentObject(activity.object) ||
+      sanitizeAndCheckVideoTorrentObject(activity.object)
+    )
 }
 
 function checkUpdateActivity (activity: any) {
-  return isCacheFileUpdateActivityValid(activity) ||
-    sanitizeAndCheckVideoTorrentUpdateActivity(activity) ||
-    isActorUpdateActivityValid(activity)
+  return isBaseActivityValid(activity, 'Update') &&
+    (
+      isCacheFileObjectValid(activity.object) ||
+      sanitizeAndCheckVideoTorrentObject(activity.object) ||
+      sanitizeAndCheckActorObject(activity.object)
+    )
 }
 
 function checkDeleteActivity (activity: any) {
-  return isVideoTorrentDeleteActivityValid(activity) ||
-    isActorDeleteActivityValid(activity) ||
-    isVideoCommentDeleteActivityValid(activity)
+  // We don't really check objects
+  return isBaseActivityValid(activity, 'Delete') &&
+    isObjectValid(activity.object)
 }
 
 function checkFollowActivity (activity: any) {
-  return isActorFollowActivityValid(activity)
+  return isBaseActivityValid(activity, 'Follow') &&
+    isObjectValid(activity.object)
 }
 
 function checkAcceptActivity (activity: any) {
-  return isActorAcceptActivityValid(activity)
+  return isBaseActivityValid(activity, 'Accept')
 }
 
 function checkRejectActivity (activity: any) {
-  return isActorRejectActivityValid(activity)
+  return isBaseActivityValid(activity, 'Reject')
 }
 
 function checkAnnounceActivity (activity: any) {
-  return isAnnounceActivityValid(activity)
+  return isBaseActivityValid(activity, 'Announce') &&
+    isObjectValid(activity.object)
 }
 
 function checkUndoActivity (activity: any) {
-  return isUndoActivityValid(activity)
+  return isBaseActivityValid(activity, 'Undo') &&
+    (
+      checkFollowActivity(activity.object) ||
+      checkLikeActivity(activity.object) ||
+      checkDislikeActivity(activity.object) ||
+      checkAnnounceActivity(activity.object) ||
+      checkCreateActivity(activity.object)
+    )
 }
 
 function checkLikeActivity (activity: any) {
-  return isLikeActivityValid(activity)
+  return isBaseActivityValid(activity, 'Like') &&
+    isObjectValid(activity.object)
 }
index 77c003cdf36dfa74df151c5e1104e9d6a09704f6..c05f60f140b82984df19593da14dcbb8856d0c9f 100644 (file)
@@ -27,7 +27,8 @@ function isActorPublicKeyValid (publicKey: string) {
     validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY)
 }
 
-const actorNameRegExp = new RegExp('^[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_\.]+$')
+const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]'
+const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`)
 function isActorPreferredUsernameValid (preferredUsername: string) {
   return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp)
 }
@@ -72,24 +73,10 @@ function isActorDeleteActivityValid (activity: any) {
   return isBaseActivityValid(activity, 'Delete')
 }
 
-function isActorFollowActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Follow') &&
-    isActivityPubUrlValid(activity.object)
-}
-
-function isActorAcceptActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Accept')
-}
-
-function isActorRejectActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Reject')
-}
-
-function isActorUpdateActivityValid (activity: any) {
-  normalizeActor(activity.object)
+function sanitizeAndCheckActorObject (object: any) {
+  normalizeActor(object)
 
-  return isBaseActivityValid(activity, 'Update') &&
-    isActorObjectValid(activity.object)
+  return isActorObjectValid(object)
 }
 
 function normalizeActor (actor: any) {
@@ -127,6 +114,7 @@ function areValidActorHandles (handles: string[]) {
 
 export {
   normalizeActor,
+  actorNameAlphabet,
   areValidActorHandles,
   isActorEndpointsObjectValid,
   isActorPublicKeyObjectValid,
@@ -137,10 +125,7 @@ export {
   isActorObjectValid,
   isActorFollowingCountValid,
   isActorFollowersCountValid,
-  isActorFollowActivityValid,
-  isActorAcceptActivityValid,
-  isActorRejectActivityValid,
   isActorDeleteActivityValid,
-  isActorUpdateActivityValid,
+  sanitizeAndCheckActorObject,
   isValidActorHandle
 }
diff --git a/server/helpers/custom-validators/activitypub/announce.ts b/server/helpers/custom-validators/activitypub/announce.ts
deleted file mode 100644 (file)
index 0519c60..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
-
-function isAnnounceActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Announce') &&
-    (
-      isActivityPubUrlValid(activity.object) ||
-      (activity.object && isActivityPubUrlValid(activity.object.id))
-    )
-}
-
-export {
-  isAnnounceActivityValid
-}
index bd70934c812f325482e95880e11fae9d980c6ae4..e2bd0c55e13013a892905917399f23e19b7e233c 100644 (file)
@@ -1,18 +1,8 @@
-import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
+import { isActivityPubUrlValid } from './misc'
 import { isRemoteVideoUrlValid } from './videos'
-import { isDateValid, exists } from '../misc'
+import { exists, isDateValid } from '../misc'
 import { CacheFileObject } from '../../../../shared/models/activitypub/objects'
 
-function isCacheFileCreateActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Create') &&
-    isCacheFileObjectValid(activity.object)
-}
-
-function isCacheFileUpdateActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Update') &&
-    isCacheFileObjectValid(activity.object)
-}
-
 function isCacheFileObjectValid (object: CacheFileObject) {
   return exists(object) &&
     object.type === 'CacheFile' &&
@@ -22,7 +12,5 @@ function isCacheFileObjectValid (object: CacheFileObject) {
 }
 
 export {
-  isCacheFileUpdateActivityValid,
-  isCacheFileCreateActivityValid,
   isCacheFileObjectValid
 }
diff --git a/server/helpers/custom-validators/activitypub/flag.ts b/server/helpers/custom-validators/activitypub/flag.ts
new file mode 100644 (file)
index 0000000..6452e29
--- /dev/null
@@ -0,0 +1,14 @@
+import { isActivityPubUrlValid } from './misc'
+import { isVideoAbuseReasonValid } from '../video-abuses'
+
+function isFlagActivityValid (activity: any) {
+  return activity.type === 'Flag' &&
+    isVideoAbuseReasonValid(activity.content) &&
+    isActivityPubUrlValid(activity.object)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  isFlagActivityValid
+}
index 4e2c57f04814b02c3776d93b2a3fccaf1107258e..f1762d11ca0dd074caa7d2e0b3bb2a8d4d88928e 100644 (file)
@@ -28,15 +28,20 @@ function isBaseActivityValid (activity: any, type: string) {
   return (activity['@context'] === undefined || Array.isArray(activity['@context'])) &&
     activity.type === type &&
     isActivityPubUrlValid(activity.id) &&
-    exists(activity.actor) &&
-    (isActivityPubUrlValid(activity.actor) || isActivityPubUrlValid(activity.actor.id)) &&
-    (
-      activity.to === undefined ||
-      (Array.isArray(activity.to) && activity.to.every(t => isActivityPubUrlValid(t)))
-    ) &&
+    isObjectValid(activity.actor) &&
+    isUrlCollectionValid(activity.to) &&
+    isUrlCollectionValid(activity.cc)
+}
+
+function isUrlCollectionValid (collection: any) {
+  return collection === undefined ||
+    (Array.isArray(collection) && collection.every(t => isActivityPubUrlValid(t)))
+}
+
+function isObjectValid (object: any) {
+  return exists(object) &&
     (
-      activity.cc === undefined ||
-      (Array.isArray(activity.cc) && activity.cc.every(t => isActivityPubUrlValid(t)))
+      isActivityPubUrlValid(object) || isActivityPubUrlValid(object.id)
     )
 }
 
@@ -57,5 +62,6 @@ export {
   isUrlValid,
   isActivityPubUrlValid,
   isBaseActivityValid,
-  setValidAttributedTo
+  setValidAttributedTo,
+  isObjectValid
 }
index e70bd94b880dc5e66ae3145b1e53e5d5c809d05c..ba68e8074f2e5ffd3987ff0397f5293f8315cca3 100644 (file)
@@ -1,20 +1,13 @@
-import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
-
-function isLikeActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Like') &&
-    isActivityPubUrlValid(activity.object)
-}
+import { isActivityPubUrlValid, isObjectValid } from './misc'
 
 function isDislikeActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Create') &&
-    activity.object.type === 'Dislike' &&
-    isActivityPubUrlValid(activity.object.actor) &&
-    isActivityPubUrlValid(activity.object.object)
+  return activity.type === 'Dislike' &&
+    isActivityPubUrlValid(activity.actor) &&
+    isObjectValid(activity.object)
 }
 
 // ---------------------------------------------------------------------------
 
 export {
-  isLikeActivityValid,
   isDislikeActivityValid
 }
diff --git a/server/helpers/custom-validators/activitypub/undo.ts b/server/helpers/custom-validators/activitypub/undo.ts
deleted file mode 100644 (file)
index 5780358..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-import { isActorFollowActivityValid } from './actor'
-import { isBaseActivityValid } from './misc'
-import { isDislikeActivityValid, isLikeActivityValid } from './rate'
-import { isAnnounceActivityValid } from './announce'
-import { isCacheFileCreateActivityValid } from './cache-file'
-
-function isUndoActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Undo') &&
-    (
-      isActorFollowActivityValid(activity.object) ||
-      isLikeActivityValid(activity.object) ||
-      isDislikeActivityValid(activity.object) ||
-      isAnnounceActivityValid(activity.object) ||
-      isCacheFileCreateActivityValid(activity.object)
-    )
-}
-
-export {
-  isUndoActivityValid
-}
index 051c4565abb0fbbe09107fb01eee837535fe9b21..0415db21c27e7d5454ddfac568d59e87d7cc4736 100644 (file)
@@ -3,11 +3,6 @@ import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers'
 import { exists, isArray, isDateValid } from '../misc'
 import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
 
-function isVideoCommentCreateActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Create') &&
-    sanitizeAndCheckVideoCommentObject(activity.object)
-}
-
 function sanitizeAndCheckVideoCommentObject (comment: any) {
   if (!comment || comment.type !== 'Note') return false
 
@@ -25,15 +20,9 @@ function sanitizeAndCheckVideoCommentObject (comment: any) {
     ) // Only accept public comments
 }
 
-function isVideoCommentDeleteActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Delete')
-}
-
 // ---------------------------------------------------------------------------
 
 export {
-  isVideoCommentCreateActivityValid,
-  isVideoCommentDeleteActivityValid,
   sanitizeAndCheckVideoCommentObject
 }
 
index 95fe824b9111086b7245bb7394d68e96af557183..0f34aab213eb5be8f6057c549310a75d8509895d 100644 (file)
@@ -14,27 +14,11 @@ import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from
 import { VideoState } from '../../../../shared/models/videos'
 import { isVideoAbuseReasonValid } from '../video-abuses'
 
-function sanitizeAndCheckVideoTorrentCreateActivity (activity: any) {
-  return isBaseActivityValid(activity, 'Create') &&
-    sanitizeAndCheckVideoTorrentObject(activity.object)
-}
-
 function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
   return isBaseActivityValid(activity, 'Update') &&
     sanitizeAndCheckVideoTorrentObject(activity.object)
 }
 
-function isVideoTorrentDeleteActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Delete')
-}
-
-function isVideoFlagValid (activity: any) {
-  return isBaseActivityValid(activity, 'Create') &&
-    activity.object.type === 'Flag' &&
-    isVideoAbuseReasonValid(activity.object.content) &&
-    isActivityPubUrlValid(activity.object.object)
-}
-
 function isActivityPubVideoDurationValid (value: string) {
   // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-duration
   return exists(value) &&
@@ -103,11 +87,8 @@ function isRemoteVideoUrlValid (url: any) {
 // ---------------------------------------------------------------------------
 
 export {
-  sanitizeAndCheckVideoTorrentCreateActivity,
   sanitizeAndCheckVideoTorrentUpdateActivity,
-  isVideoTorrentDeleteActivityValid,
   isRemoteStringIdentifierValid,
-  isVideoFlagValid,
   sanitizeAndCheckVideoTorrentObject,
   isRemoteVideoUrlValid
 }
index 7a3aca6f5a6e57f39b720a501d1ecfff04245fe8..41d16469ffa7ebbd2f937fe5792f9f8c223c8c4d 100644 (file)
@@ -1,11 +1,11 @@
-import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
+import { isActivityPubUrlValid } from './misc'
 
 function isViewActivityValid (activity: any) {
-  return isBaseActivityValid(activity, 'Create') &&
-    activity.object.type === 'View' &&
-    isActivityPubUrlValid(activity.object.actor) &&
-    isActivityPubUrlValid(activity.object.object)
+  return activity.type === 'View' &&
+    isActivityPubUrlValid(activity.actor) &&
+    isActivityPubUrlValid(activity.object)
 }
+
 // ---------------------------------------------------------------------------
 
 export {
index 6d10a65a881ee75b9f656ebe76aed2d363f1db2e..b6f0ebe6f2478fd06138978efefa9f95995cd4ef 100644 (file)
@@ -9,6 +9,10 @@ function isArray (value: any) {
   return Array.isArray(value)
 }
 
+function isNotEmptyIntArray (value: any) {
+  return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0
+}
+
 function isDateValid (value: string) {
   return exists(value) && validator.isISO8601(value)
 }
@@ -78,6 +82,7 @@ function isFileValid (
 
 export {
   exists,
+  isNotEmptyIntArray,
   isArray,
   isIdValid,
   isUUIDValid,
index d5021bf38dbf5b8f553af001b87e5fe06a59128e..18c80ec8fcd587f7ee6083c2cf9a53d66962a00c 100644 (file)
@@ -3,6 +3,7 @@ import 'express-validator'
 
 import { isArray, exists } from './misc'
 import { isTestInstance } from '../core-utils'
+import { CONSTRAINTS_FIELDS } from '../../initializers'
 
 function isHostValid (host: string) {
   const isURLOptions = {
@@ -26,9 +27,19 @@ function isEachUniqueHostValid (hosts: string[]) {
     })
 }
 
+function isValidContactBody (value: any) {
+  return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.BODY)
+}
+
+function isValidContactFromName (value: any) {
+  return exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.CONTACT_FORM.FROM_NAME)
+}
+
 // ---------------------------------------------------------------------------
 
 export {
+  isValidContactBody,
+  isValidContactFromName,
   isEachUniqueHostValid,
   isHostValid
 }
diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts
new file mode 100644 (file)
index 0000000..02ea3bb
--- /dev/null
@@ -0,0 +1,23 @@
+import { exists } from './misc'
+import * as validator from 'validator'
+import { UserNotificationType } from '../../../shared/models/users'
+import { UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
+
+function isUserNotificationTypeValid (value: any) {
+  return exists(value) && validator.isInt('' + value) && UserNotificationType[value] !== undefined
+}
+
+function isUserNotificationSettingValid (value: any) {
+  return exists(value) &&
+    validator.isInt('' + value) && (
+      value === UserNotificationSettingValue.NONE ||
+      value === UserNotificationSettingValue.WEB ||
+      value === UserNotificationSettingValue.EMAIL ||
+      value === (UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL)
+    )
+}
+
+export {
+  isUserNotificationSettingValid,
+  isUserNotificationTypeValid
+}
index 1cb5e5b0f514a3d93efe669113ddbe1aadcabeb9..80652b4798eb566f2606da590e681f74c581abc7 100644 (file)
@@ -46,6 +46,10 @@ function isUserWebTorrentEnabledValid (value: any) {
   return isBooleanValid(value)
 }
 
+function isUserVideosHistoryEnabledValid (value: any) {
+  return isBooleanValid(value)
+}
+
 function isUserAutoPlayVideoValid (value: any) {
   return isBooleanValid(value)
 }
@@ -73,6 +77,7 @@ function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } |
 // ---------------------------------------------------------------------------
 
 export {
+  isUserVideosHistoryEnabledValid,
   isUserBlockedValid,
   isUserPasswordValid,
   isUserBlockedReasonValid,
index 177e9e86ed45b475e1e9f007bb3ad2c091a00023..b33d90e1856f5bc3a77683f2c8d321ec4b763dfa 100644 (file)
@@ -1,4 +1,4 @@
-import { CONSTRAINTS_FIELDS, VIDEO_CAPTIONS_MIMETYPE_EXT, VIDEO_LANGUAGES } from '../../initializers'
+import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers'
 import { exists, isFileValid } from './misc'
 import { Response } from 'express'
 import { VideoModel } from '../../models/video/video'
@@ -8,7 +8,7 @@ function isVideoCaptionLanguageValid (value: any) {
   return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined
 }
 
-const videoCaptionTypes = Object.keys(VIDEO_CAPTIONS_MIMETYPE_EXT)
+const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT)
                                 .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream ><
                                 .map(m => `(${m})`)
 const videoCaptionTypesRegex = videoCaptionTypes.join('|')
index 4d6ab1fa41cf6578300bcb45292808f56ce281cf..ce9e9193cebf5b0d9fc977067a2ca2ed327a3919 100644 (file)
@@ -1,7 +1,7 @@
 import 'express-validator'
 import 'multer'
 import * as validator from 'validator'
-import { CONSTRAINTS_FIELDS, TORRENT_MIMETYPE_EXT, VIDEO_IMPORT_STATES } from '../../initializers'
+import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers'
 import { exists, isFileValid } from './misc'
 import * as express from 'express'
 import { VideoImportModel } from '../../models/video/video-import'
@@ -24,7 +24,7 @@ function isVideoImportStateValid (value: any) {
   return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined
 }
 
-const videoTorrentImportTypes = Object.keys(TORRENT_MIMETYPE_EXT).map(m => `(${m})`)
+const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`)
 const videoTorrentImportRegex = videoTorrentImportTypes.join('|')
 function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
   return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true)
index a13b09ac80c31ebb23f58b007c4723134f753750..95e256b8fb9e8fdbe72959651dade5e7fdac58bd 100644 (file)
@@ -5,10 +5,9 @@ import 'multer'
 import * as validator from 'validator'
 import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
 import {
-  CONSTRAINTS_FIELDS,
+  CONSTRAINTS_FIELDS, MIMETYPES,
   VIDEO_CATEGORIES,
   VIDEO_LICENCES,
-  VIDEO_MIMETYPE_EXT,
   VIDEO_PRIVACIES,
   VIDEO_RATE_TYPES,
   VIDEO_STATES
@@ -83,10 +82,15 @@ function isVideoRatingTypeValid (value: string) {
   return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1
 }
 
-const videoFileTypes = Object.keys(VIDEO_MIMETYPE_EXT).map(m => `(${m})`)
-const videoFileTypesRegex = videoFileTypes.join('|')
+function isVideoFileExtnameValid (value: string) {
+  return exists(value) && MIMETYPES.VIDEO.EXT_MIMETYPE[value] !== undefined
+}
 
 function isVideoFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) {
+  const videoFileTypesRegex = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
+                                    .map(m => `(${m})`)
+                                    .join('|')
+
   return isFileValid(files, videoFileTypesRegex, 'videofile', null)
 }
 
@@ -221,6 +225,7 @@ export {
   isVideoStateValid,
   isVideoViewsValid,
   isVideoRatingTypeValid,
+  isVideoFileExtnameValid,
   isVideoDurationValid,
   isVideoTagValid,
   isVideoPrivacyValid,
index 162fe2244118a514ce5c38e2461419855881b380..9a72ee96da77d0e4c7d46bea815a16f0c98f12dc 100644 (file)
@@ -7,12 +7,12 @@ import { extname } from 'path'
 import { isArray } from './custom-validators/misc'
 import { UserModel } from '../models/account/user'
 
-function buildNSFWFilter (res: express.Response, paramNSFW?: string) {
+function buildNSFWFilter (res?: express.Response, paramNSFW?: string) {
   if (paramNSFW === 'true') return true
   if (paramNSFW === 'false') return false
   if (paramNSFW === 'both') return undefined
 
-  if (res.locals.oauth) {
+  if (res && res.locals.oauth) {
     const user: UserModel = res.locals.oauth.token.User
 
     // User does not want NSFW videos
index b59e7e40e692e99ef19bb5e49b6d4745ae412a35..132f4690ec715063fa1abcdd56e0450a553cbe2c 100644 (file)
@@ -41,7 +41,7 @@ async function getVideoFileResolution (path: string) {
 async function getVideoFileFPS (path: string) {
   const videoStream = await getVideoFileStream(path)
 
-  for (const key of [ 'r_frame_rate' , 'avg_frame_rate' ]) {
+  for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) {
     const valuesText: string = videoStream[key]
     if (!valuesText) continue
 
@@ -184,7 +184,7 @@ function getVideoFileStream (path: string) {
       if (err) return rej(err)
 
       const videoStream = metadata.streams.find(s => s.codec_type === 'video')
-      if (!videoStream) throw new Error('Cannot find video stream of ' + path)
+      if (!videoStream) return rej(new Error('Cannot find video stream of ' + path))
 
       return res(videoStream)
     })
@@ -328,10 +328,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol
     const audioCodecName = parsedAudio.audioStream[ 'codec_name' ]
     let bitrate: number
     if (audio.bitrate[ audioCodecName ]) {
-      bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ])
+      localCommand = localCommand.audioCodec('aac')
 
-      if (bitrate === -1) localCommand = localCommand.audioCodec('copy')
-      else if (bitrate !== undefined) localCommand = localCommand.audioBitrate(bitrate)
+      bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ])
+      if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate)
     }
   }
 
index da3285b13da7c3cfc902abfcfeab2f46564ab913..e43ea3f1dad03c62cf65457e40fe461b22c98227 100644 (file)
@@ -1,6 +1,7 @@
 import 'multer'
 import * as sharp from 'sharp'
-import { move, remove } from 'fs-extra'
+import { readFile, remove } from 'fs-extra'
+import { logger } from './logger'
 
 async function processImage (
   physicalFile: { path: string },
@@ -11,14 +12,11 @@ async function processImage (
     throw new Error('Sharp needs an input path different that the output path.')
   }
 
-  const sharpInstance = sharp(physicalFile.path)
-  const metadata = await sharpInstance.metadata()
+  logger.debug('Processing image %s to %s.', physicalFile.path, destination)
 
-  // No need to resize
-  if (metadata.width === newSize.width && metadata.height === newSize.height) {
-    await move(physicalFile.path, destination, { overwrite: true })
-    return
-  }
+  // Avoid sharp cache
+  const buf = await readFile(physicalFile.path)
+  const sharpInstance = sharp(buf)
 
   await remove(destination)
 
diff --git a/server/helpers/regexp.ts b/server/helpers/regexp.ts
new file mode 100644 (file)
index 0000000..2336654
--- /dev/null
@@ -0,0 +1,23 @@
+// Thanks to https://regex101.com
+function regexpCapture (str: string, regex: RegExp, maxIterations = 100) {
+  let m: RegExpExecArray
+  let i = 0
+  let result: RegExpExecArray[] = []
+
+  // tslint:disable:no-conditional-assignment
+  while ((m = regex.exec(str)) !== null && i < maxIterations) {
+    // This is necessary to avoid infinite loops with zero-width matches
+    if (m.index === regex.lastIndex) {
+      regex.lastIndex++
+    }
+
+    result.push(m)
+    i++
+  }
+
+  return result
+}
+
+export {
+  regexpCapture
+}
index 805930a9ff38f9ee415d03c616fe3d412efc6a90..3fc776f1a53299ceb2630313938d8c4757b24b33 100644 (file)
@@ -1,8 +1,9 @@
 import * as Bluebird from 'bluebird'
 import { createWriteStream } from 'fs-extra'
 import * as request from 'request'
-import { ACTIVITY_PUB } from '../initializers'
+import { ACTIVITY_PUB, CONFIG } from '../initializers'
 import { processImage } from './image-utils'
+import { join } from 'path'
 
 function doRequest <T> (
   requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean }
@@ -28,11 +29,11 @@ function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.U
   })
 }
 
-async function downloadImage (url: string, destPath: string, size: { width: number, height: number }) {
-  const tmpPath = destPath + '.tmp'
-
+async function downloadImage (url: string, destDir: string, destName: string, size: { width: number, height: number }) {
+  const tmpPath = join(CONFIG.STORAGE.TMP_DIR, 'pending-' + destName)
   await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath)
 
+  const destPath = join(destDir, destName)
   await processImage({ path: tmpPath }, destPath, size)
 }
 
index 5c9d6fe2f2b4b5e96eee3a3184c0e913cc1a8b22..3c3406e381724fc71ec5660aeb430470c553c68a 100644 (file)
@@ -7,6 +7,7 @@ import { join } from 'path'
 import { Instance as ParseTorrent } from 'parse-torrent'
 import { remove } from 'fs-extra'
 import * as memoizee from 'memoizee'
+import { isArray } from './custom-validators/misc'
 
 function deleteFileAsync (path: string) {
   remove(path)
@@ -19,10 +20,7 @@ async function generateRandomString (size: number) {
   return raw.toString('hex')
 }
 
-interface FormattableToJSON {
-  toFormattedJSON (args?: any)
-}
-
+interface FormattableToJSON { toFormattedJSON (args?: any) }
 function getFormattedObjects<U, T extends FormattableToJSON> (objects: T[], objectsTotal: number, formattedArg?: any) {
   const formattedObjects: U[] = []
 
@@ -46,11 +44,11 @@ const getServerActor = memoizee(async function () {
   return actor
 })
 
-function generateVideoTmpPath (target: string | ParseTorrent) {
+function generateVideoImportTmpPath (target: string | ParseTorrent) {
   const id = typeof target === 'string' ? target : target.infoHash
 
   const hash = sha256(id)
-  return join(CONFIG.STORAGE.VIDEOS_DIR, hash + '-import.mp4')
+  return join(CONFIG.STORAGE.TMP_DIR, hash + '-import.mp4')
 }
 
 function getSecureTorrentName (originalName: string) {
@@ -103,6 +101,6 @@ export {
   getSecureTorrentName,
   getServerActor,
   getServerCommit,
-  generateVideoTmpPath,
+  generateVideoImportTmpPath,
   getUUIDFromFilename
 }
index ce35b87dac395135d731a8875012d55f4f0c73b0..3c9a0b96ae87b21d942da729634c1e4250b4766a 100644 (file)
@@ -1,5 +1,5 @@
 import { logger } from './logger'
-import { generateVideoTmpPath } from './utils'
+import { generateVideoImportTmpPath } from './utils'
 import * as WebTorrent from 'webtorrent'
 import { createWriteStream, ensureDir, remove } from 'fs-extra'
 import { CONFIG } from '../initializers'
@@ -9,10 +9,10 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName
   const id = target.magnetUri || target.torrentName
   let timer
 
-  const path = generateVideoTmpPath(id)
+  const path = generateVideoImportTmpPath(id)
   logger.info('Importing torrent video %s', id)
 
-  const directoryPath = join(CONFIG.STORAGE.VIDEOS_DIR, 'import')
+  const directoryPath = join(CONFIG.STORAGE.TMP_DIR, 'webtorrent')
   await ensureDir(directoryPath)
 
   return new Promise<string>((res, rej) => {
index 2a56630427855fee83ccb79e370cc6aa11c1009d..b74351b4219a72963c04cfe092c8081a5a7ad63f 100644 (file)
@@ -1,7 +1,7 @@
 import { truncate } from 'lodash'
 import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers'
 import { logger } from './logger'
-import { generateVideoTmpPath } from './utils'
+import { generateVideoImportTmpPath } from './utils'
 import { join } from 'path'
 import { root } from './core-utils'
 import { ensureDir, writeFile, remove } from 'fs-extra'
@@ -40,7 +40,7 @@ function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo>
 }
 
 function downloadYoutubeDLVideo (url: string, timeout: number) {
-  const path = generateVideoTmpPath(url)
+  const path = generateVideoImportTmpPath(url)
   let timer
 
   logger.info('Importing youtubeDL video %s', url)
index 72d84695773eb4f0c01f025c7c9c6ff74a6068c3..955d55206cc40e73dade1619f18fdce2ebbb2e63 100644 (file)
@@ -10,6 +10,7 @@ import { getServerActor } from '../helpers/utils'
 import { RecentlyAddedStrategy } from '../../shared/models/redundancy'
 import { isArray } from '../helpers/custom-validators/misc'
 import { uniq } from 'lodash'
+import { Emailer } from '../lib/emailer'
 
 async function checkActivityPubUrls () {
   const actor = await getServerActor()
@@ -32,9 +33,19 @@ async function checkActivityPubUrls () {
 // Some checks on configuration files
 // Return an error message, or null if everything is okay
 function checkConfig () {
-  const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
+
+  if (!Emailer.isEnabled()) {
+    if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
+      return 'Emailer is disabled but you require signup email verification.'
+    }
+
+    if (CONFIG.CONTACT_FORM.ENABLED) {
+      logger.warn('Emailer is disabled so the contact form will not work.')
+    }
+  }
 
   // NSFW policy
+  const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
   {
     const available = [ 'do_not_list', 'blur', 'display' ]
     if (available.indexOf(defaultNSFWPolicy) === -1) {
@@ -68,6 +79,7 @@ function checkConfig () {
     }
   }
 
+  // Check storage directory locations
   if (isProdInstance()) {
     const configStorage = config.get('storage')
     for (const key of Object.keys(configStorage)) {
index 9dfb5d68c375947a009d122e04bcf8ba7637b390..7905d9ffaee5c087fb876077609b7f6d54928b0d 100644 (file)
@@ -12,13 +12,14 @@ function checkMissedConfig () {
     'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max',
     'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address',
     'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache',
+    'storage.redundancy', 'storage.tmp',
     'log.level',
     'user.video_quota', 'user.video_quota_daily',
-    'cache.previews.size', 'admin.email',
+    'cache.previews.size', 'admin.email', 'contact_form.enabled',
     'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
     'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
     'redundancy.videos.strategies', 'redundancy.videos.check_interval',
-    'transcoding.enabled', 'transcoding.threads',
+    'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions',
     'import.videos.http.enabled', 'import.videos.torrent.enabled',
     'trending.videos.interval_days',
     'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route',
index aa243859cc6a2bab9662d9de29f9bbc28a62cc57..6f3ebb9aa8af82ce81f2d0964110efd06e259af1 100644 (file)
@@ -16,7 +16,7 @@ let config: IConfig = require('config')
 
 // ---------------------------------------------------------------------------
 
-const LAST_MIGRATION_VERSION = 290
+const LAST_MIGRATION_VERSION = 325
 
 // ---------------------------------------------------------------------------
 
@@ -50,7 +50,9 @@ const SORTABLE_COLUMNS = {
   VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ],
 
   ACCOUNTS_BLOCKLIST: [ 'createdAt' ],
-  SERVERS_BLOCKLIST: [ 'createdAt' ]
+  SERVERS_BLOCKLIST: [ 'createdAt' ],
+
+  USER_NOTIFICATIONS: [ 'createdAt' ]
 }
 
 const OAUTH_LIFETIME = {
@@ -61,6 +63,7 @@ const OAUTH_LIFETIME = {
 const ROUTE_CACHE_LIFETIME = {
   FEEDS: '15 minutes',
   ROBOTS: '2 hours',
+  SITEMAP: '1 day',
   SECURITYTXT: '2 hours',
   NODEINFO: '10 minutes',
   DNT_POLICY: '1 week',
@@ -143,7 +146,7 @@ const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour
 
 // 1 hour
 let SCHEDULER_INTERVALS_MS = {
-  badActorFollow: 60000 * 60, // 1 hour
+  actorFollowScores: 60000 * 60, // 1 hour
   removeOldJobs: 60000 * 60, // 1 hour
   updateVideos: 60000, // 1 minute
   youtubeDLUpdate: 60000 * 60 * 24 // 1 day
@@ -185,9 +188,11 @@ const CONFIG = {
     FROM_ADDRESS: config.get<string>('smtp.from_address')
   },
   STORAGE: {
+    TMP_DIR: buildPath(config.get<string>('storage.tmp')),
     AVATARS_DIR: buildPath(config.get<string>('storage.avatars')),
     LOG_DIR: buildPath(config.get<string>('storage.logs')),
     VIDEOS_DIR: buildPath(config.get<string>('storage.videos')),
+    REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')),
     THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')),
     PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')),
     CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')),
@@ -226,6 +231,9 @@ const CONFIG = {
   ADMIN: {
     get EMAIL () { return config.get<string>('admin.email') }
   },
+  CONTACT_FORM: {
+    get ENABLED () { return config.get<boolean>('contact_form.enabled') }
+  },
   SIGNUP: {
     get ENABLED () { return config.get<boolean>('signup.enabled') },
     get LIMIT () { return config.get<number>('signup.limit') },
@@ -243,6 +251,7 @@ const CONFIG = {
   },
   TRANSCODING: {
     get ENABLED () { return config.get<boolean>('transcoding.enabled') },
+    get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') },
     get THREADS () { return config.get<number>('transcoding.threads') },
     RESOLUTIONS: {
       get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') },
@@ -286,6 +295,7 @@ const CONFIG = {
     get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') }
   },
   SERVICES: {
+    get 'CSP-LOGGER' () { return config.get<string>('services.csp-logger') },
     TWITTER: {
       get USERNAME () { return config.get<string>('services.twitter.username') },
       get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') }
@@ -295,25 +305,25 @@ const CONFIG = {
 
 // ---------------------------------------------------------------------------
 
-const CONSTRAINTS_FIELDS = {
+let CONSTRAINTS_FIELDS = {
   USERS: {
-    NAME: { min: 3, max: 120 }, // Length
+    NAME: { min: 1, max: 120 }, // Length
     DESCRIPTION: { min: 3, max: 1000 }, // Length
-    USERNAME: { min: 3, max: 20 }, // Length
+    USERNAME: { min: 1, max: 50 }, // Length
     PASSWORD: { min: 6, max: 255 }, // Length
     VIDEO_QUOTA: { min: -1 },
     VIDEO_QUOTA_DAILY: { min: -1 },
     BLOCKED_REASON: { min: 3, max: 250 } // Length
   },
   VIDEO_ABUSES: {
-    REASON: { min: 2, max: 300 }, // Length
-    MODERATION_COMMENT: { min: 2, max: 300 } // Length
+    REASON: { min: 2, max: 3000 }, // Length
+    MODERATION_COMMENT: { min: 2, max: 3000 } // Length
   },
   VIDEO_BLACKLIST: {
     REASON: { min: 2, max: 300 } // Length
   },
   VIDEO_CHANNELS: {
-    NAME: { min: 3, max: 120 }, // Length
+    NAME: { min: 1, max: 120 }, // Length
     DESCRIPTION: { min: 3, max: 1000 }, // Length
     SUPPORT: { min: 3, max: 1000 }, // Length
     URL: { min: 3, max: 2000 } // Length
@@ -354,7 +364,7 @@ const CONSTRAINTS_FIELDS = {
         max: 2 * 1024 * 1024 // 2MB
       }
     },
-    EXTNAME: [ '.mp4', '.ogv', '.webm' ],
+    EXTNAME: buildVideosExtname(),
     INFO_HASH: { min: 40, max: 40 }, // Length, info hash is 20 bytes length but we represent it in hexadecimal so 20 * 2
     DURATION: { min: 0 }, // Number
     TAGS: { min: 0, max: 5 }, // Number of total tags
@@ -387,6 +397,10 @@ const CONSTRAINTS_FIELDS = {
   },
   VIDEO_SHARE: {
     URL: { min: 3, max: 2000 } // Length
+  },
+  CONTACT_FORM: {
+    FROM_NAME: { min: 1, max: 120 }, // Length
+    BODY: { min: 3, max: 5000 } // Length
   }
 }
 
@@ -402,6 +416,8 @@ const RATES_LIMIT = {
 }
 
 let VIDEO_VIEW_LIFETIME = 60000 * 60 // 1 hour
+let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour
+
 const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = {
   MIN: 10,
   AVERAGE: 30,
@@ -477,27 +493,31 @@ const VIDEO_ABUSE_STATES = {
   [VideoAbuseState.ACCEPTED]: 'Accepted'
 }
 
-const VIDEO_MIMETYPE_EXT = {
-  'video/webm': '.webm',
-  'video/ogg': '.ogv',
-  'video/mp4': '.mp4'
-}
-const VIDEO_EXT_MIMETYPE = invert(VIDEO_MIMETYPE_EXT)
-
-const IMAGE_MIMETYPE_EXT = {
-  'image/png': '.png',
-  'image/jpg': '.jpg',
-  'image/jpeg': '.jpg'
-}
-
-const VIDEO_CAPTIONS_MIMETYPE_EXT = {
-  'text/vtt': '.vtt',
-  'application/x-subrip': '.srt'
-}
-
-const TORRENT_MIMETYPE_EXT = {
-  'application/x-bittorrent': '.torrent'
+const MIMETYPES = {
+  VIDEO: {
+    MIMETYPE_EXT: buildVideoMimetypeExt(),
+    EXT_MIMETYPE: null as { [ id: string ]: string }
+  },
+  IMAGE: {
+    MIMETYPE_EXT: {
+      'image/png': '.png',
+      'image/jpg': '.jpg',
+      'image/jpeg': '.jpg'
+    }
+  },
+  VIDEO_CAPTIONS: {
+    MIMETYPE_EXT: {
+      'text/vtt': '.vtt',
+      'application/x-subrip': '.srt'
+    }
+  },
+  TORRENT: {
+    MIMETYPE_EXT: {
+      'application/x-bittorrent': '.torrent'
+    }
+  }
 }
+MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
 
 // ---------------------------------------------------------------------------
 
@@ -523,7 +543,7 @@ const ACTIVITY_PUB = {
   COLLECTION_ITEMS_PER_PAGE: 10,
   FETCH_PAGE_LIMIT: 100,
   URL_MIME_TYPES: {
-    VIDEO: Object.keys(VIDEO_MIMETYPE_EXT),
+    VIDEO: Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT),
     TORRENT: [ 'application/x-bittorrent' ],
     MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ]
   },
@@ -569,6 +589,7 @@ const STATIC_PATHS = {
   THUMBNAILS: '/static/thumbnails/',
   TORRENTS: '/static/torrents/',
   WEBSEED: '/static/webseed/',
+  REDUNDANCY: '/static/redundancy/',
   AVATARS: '/static/avatars/',
   VIDEO_CAPTIONS: '/static/video-captions/'
 }
@@ -665,7 +686,7 @@ if (isTestInstance() === true) {
 
   CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB
 
-  SCHEDULER_INTERVALS_MS.badActorFollow = 10000
+  SCHEDULER_INTERVALS_MS.actorFollowScores = 1000
   SCHEDULER_INTERVALS_MS.removeOldJobs = 10000
   SCHEDULER_INTERVALS_MS.updateVideos = 5000
   REPEAT_JOBS['videos-views'] = { every: 5000 }
@@ -673,6 +694,7 @@ if (isTestInstance() === true) {
   REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1
 
   VIDEO_VIEW_LIFETIME = 1000 // 1 second
+  CONTACT_FORM_LIFETIME = 1000 // 1 second
 
   JOB_ATTEMPTS['email'] = 1
 
@@ -681,13 +703,12 @@ if (isTestInstance() === true) {
   ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms'
 }
 
-updateWebserverConfig()
+updateWebserverUrls()
 
 // ---------------------------------------------------------------------------
 
 export {
   API_VERSION,
-  VIDEO_CAPTIONS_MIMETYPE_EXT,
   AVATARS_SIZE,
   ACCEPT_HEADERS,
   BCRYPT_SALT_SIZE,
@@ -715,7 +736,6 @@ export {
   FEEDS,
   JOB_TTL,
   NSFW_POLICY_TYPES,
-  TORRENT_MIMETYPE_EXT,
   STATIC_MAX_AGE,
   STATIC_PATHS,
   VIDEO_IMPORT_TIMEOUT,
@@ -728,7 +748,6 @@ export {
   VIDEO_LICENCES,
   VIDEO_STATES,
   VIDEO_RATE_TYPES,
-  VIDEO_MIMETYPE_EXT,
   VIDEO_TRANSCODING_FPS,
   FFMPEG_NICE,
   VIDEO_ABUSE_STATES,
@@ -736,18 +755,18 @@ export {
   USER_PASSWORD_RESET_LIFETIME,
   MEMOIZE_TTL,
   USER_EMAIL_VERIFY_LIFETIME,
-  IMAGE_MIMETYPE_EXT,
   OVERVIEWS,
   SCHEDULER_INTERVALS_MS,
   REPEAT_JOBS,
   STATIC_DOWNLOAD_PATHS,
   RATES_LIMIT,
-  VIDEO_EXT_MIMETYPE,
+  MIMETYPES,
   CRAWL_REQUEST_CONCURRENCY,
   JOB_COMPLETED_LIFETIME,
   HTTP_SIGNATURE,
   VIDEO_IMPORT_STATES,
   VIDEO_VIEW_LIFETIME,
+  CONTACT_FORM_LIFETIME,
   buildLanguages
 }
 
@@ -764,16 +783,50 @@ function getLocalConfigFilePath () {
   return join(dirname(configSources[ 0 ].name), filename + '.json')
 }
 
-function updateWebserverConfig () {
+function buildVideoMimetypeExt () {
+  const data = {
+    'video/webm': '.webm',
+    'video/ogg': '.ogv',
+    'video/mp4': '.mp4'
+  }
+
+  if (CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS) {
+    Object.assign(data, {
+      'video/quicktime': '.mov',
+      'video/x-msvideo': '.avi',
+      'video/x-flv': '.flv',
+      'video/x-matroska': '.mkv',
+      'application/octet-stream': '.mkv',
+      'video/avi': '.avi'
+    })
+  }
+
+  return data
+}
+
+function updateWebserverUrls () {
   CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT)
   CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP)
 }
 
+function updateWebserverConfig () {
+  CONSTRAINTS_FIELDS.VIDEOS.EXTNAME = buildVideosExtname()
+
+  MIMETYPES.VIDEO.MIMETYPE_EXT = buildVideoMimetypeExt()
+  MIMETYPES.VIDEO.EXT_MIMETYPE = invert(MIMETYPES.VIDEO.MIMETYPE_EXT)
+}
+
+function buildVideosExtname () {
+  return CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.ALLOW_ADDITIONAL_EXTENSIONS
+    ? [ '.mp4', '.ogv', '.webm', '.mkv', '.mov', '.avi', '.flv' ]
+    : [ '.mp4', '.ogv', '.webm' ]
+}
+
 function buildVideosRedundancy (objs: any[]): VideosRedundancy[] {
   if (!objs) return []
 
   return objs.map(obj => {
-    return Object.assign(obj, {
+    return Object.assign({}, obj, {
       minLifetime: parseDuration(obj.min_lifetime),
       size: bytes.parse(obj.size),
       minViews: obj.min_views
@@ -850,4 +903,5 @@ export function reloadConfig () {
   config = require('config')
 
   updateWebserverConfig()
+  updateWebserverUrls()
 }
index 40cd659ab8bdf7d0708c8bb9904413297c1631aa..84ad2079b94767ec790a54937ca4b03acba86159 100644 (file)
@@ -31,6 +31,8 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy'
 import { UserVideoHistoryModel } from '../models/account/user-video-history'
 import { AccountBlocklistModel } from '../models/account/account-blocklist'
 import { ServerBlocklistModel } from '../models/server/server-blocklist'
+import { UserNotificationModel } from '../models/account/user-notification'
+import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
 
 require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string
 
@@ -95,7 +97,9 @@ async function initDatabaseModels (silent: boolean) {
     VideoRedundancyModel,
     UserVideoHistoryModel,
     AccountBlocklistModel,
-    ServerBlocklistModel
+    ServerBlocklistModel,
+    UserNotificationModel,
+    UserNotificationSettingModel
   ])
 
   // Check extensions exist in the database
diff --git a/server/initializers/migrations/0295-video-file-extname.ts b/server/initializers/migrations/0295-video-file-extname.ts
new file mode 100644 (file)
index 0000000..dbf249f
--- /dev/null
@@ -0,0 +1,49 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  {
+    await utils.queryInterface.renameColumn('videoFile', 'extname', 'extname_old')
+  }
+
+  {
+    const data = {
+      type: Sequelize.STRING,
+      defaultValue: null,
+      allowNull: true
+    }
+
+    await utils.queryInterface.addColumn('videoFile', 'extname', data)
+  }
+
+  {
+    const query = 'UPDATE "videoFile" SET "extname" = "extname_old"::text'
+    await utils.sequelize.query(query)
+  }
+
+  {
+    const data = {
+      type: Sequelize.STRING,
+      defaultValue: null,
+      allowNull: false
+    }
+    await utils.queryInterface.changeColumn('videoFile', 'extname', data)
+  }
+
+  {
+    await utils.queryInterface.removeColumn('videoFile', 'extname_old')
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
diff --git a/server/initializers/migrations/0300-user-videos-history-enabled.ts b/server/initializers/migrations/0300-user-videos-history-enabled.ts
new file mode 100644 (file)
index 0000000..aa5fc21
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  {
+    const data = {
+      type: Sequelize.BOOLEAN,
+      allowNull: false,
+      defaultValue: true
+    }
+
+    await utils.queryInterface.addColumn('user', 'videosHistoryEnabled', data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
diff --git a/server/initializers/migrations/0305-fix-unfederated-videos.ts b/server/initializers/migrations/0305-fix-unfederated-videos.ts
new file mode 100644 (file)
index 0000000..be20660
--- /dev/null
@@ -0,0 +1,52 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  {
+    const query = `INSERT INTO "videoShare" (url, "actorId", "videoId", "createdAt", "updatedAt") ` +
+      `(` +
+        `SELECT ` +
+        `video.url || '/announces/' || "videoChannel"."actorId" as url, ` +
+        `"videoChannel"."actorId" AS "actorId", ` +
+        `"video"."id" AS "videoId", ` +
+        `NOW() AS "createdAt", ` +
+        `NOW() AS "updatedAt" ` +
+        `FROM video ` +
+        `INNER JOIN "videoChannel" ON "video"."channelId" = "videoChannel"."id" ` +
+        `WHERE "video"."remote" = false AND "video"."privacy" != 3 AND "video"."state" = 1` +
+      `) ` +
+      `ON CONFLICT DO NOTHING`
+
+    await utils.sequelize.query(query)
+  }
+
+  {
+    const query = `INSERT INTO "videoShare" (url, "actorId", "videoId", "createdAt", "updatedAt") ` +
+      `(` +
+        `SELECT ` +
+        `video.url || '/announces/' || (SELECT id FROM actor WHERE "preferredUsername" = 'peertube' ORDER BY id ASC LIMIT 1) as url, ` +
+        `(SELECT id FROM actor WHERE "preferredUsername" = 'peertube' ORDER BY id ASC LIMIT 1) AS "actorId", ` +
+        `"video"."id" AS "videoId", ` +
+        `NOW() AS "createdAt", ` +
+        `NOW() AS "updatedAt" ` +
+        `FROM video ` +
+        `WHERE "video"."remote" = false AND "video"."privacy" != 3 AND "video"."state" = 1` +
+      `) ` +
+      `ON CONFLICT DO NOTHING`
+
+    await utils.sequelize.query(query)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
diff --git a/server/initializers/migrations/0310-drop-unused-video-indexes.ts b/server/initializers/migrations/0310-drop-unused-video-indexes.ts
new file mode 100644 (file)
index 0000000..d51f430
--- /dev/null
@@ -0,0 +1,32 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize,
+  db: any
+}): Promise<void> {
+  const indexNames = [
+    'video_category',
+    'video_licence',
+    'video_nsfw',
+    'video_language',
+    'video_wait_transcoding',
+    'video_state',
+    'video_remote',
+    'video_likes'
+  ]
+
+  for (const indexName of indexNames) {
+    await utils.sequelize.query('DROP INDEX IF EXISTS "' + indexName + '";')
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts
new file mode 100644 (file)
index 0000000..8284c58
--- /dev/null
@@ -0,0 +1,47 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+  {
+    const query = `
+CREATE TABLE IF NOT EXISTS "userNotificationSetting" ("id" SERIAL,
+"newVideoFromSubscription" INTEGER NOT NULL DEFAULT NULL,
+"newCommentOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
+"videoAbuseAsModerator" INTEGER NOT NULL DEFAULT NULL,
+"blacklistOnMyVideo" INTEGER NOT NULL DEFAULT NULL,
+"myVideoPublished" INTEGER NOT NULL DEFAULT NULL,
+"myVideoImportFinished" INTEGER NOT NULL DEFAULT NULL,
+"newUserRegistration" INTEGER NOT NULL DEFAULT NULL,
+"newFollow" INTEGER NOT NULL DEFAULT NULL,
+"commentMention" INTEGER NOT NULL DEFAULT NULL,
+"userId" INTEGER REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL,
+"updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL,
+PRIMARY KEY ("id"))
+`
+    await utils.sequelize.query(query)
+  }
+
+  {
+    const query = 'INSERT INTO "userNotificationSetting" ' +
+      '("newVideoFromSubscription", "newCommentOnMyVideo", "videoAbuseAsModerator", "blacklistOnMyVideo", ' +
+      '"myVideoPublished", "myVideoImportFinished", "newUserRegistration", "newFollow", "commentMention", ' +
+      '"userId", "createdAt", "updatedAt") ' +
+      '(SELECT 1, 1, 3, 3, 1, 1, 1, 1, 1, id, NOW(), NOW() FROM "user")'
+
+    await utils.sequelize.query(query)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
diff --git a/server/initializers/migrations/0320-blacklist-unfederate.ts b/server/initializers/migrations/0320-blacklist-unfederate.ts
new file mode 100644 (file)
index 0000000..6fb7bbb
--- /dev/null
@@ -0,0 +1,27 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+  {
+    const data = {
+      type: Sequelize.BOOLEAN,
+      allowNull: false,
+      defaultValue: false
+    }
+
+    await utils.queryInterface.addColumn('videoBlacklist', 'unfederated', data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
diff --git a/server/initializers/migrations/0325-video-abuse-fields.ts b/server/initializers/migrations/0325-video-abuse-fields.ts
new file mode 100644 (file)
index 0000000..fca6d66
--- /dev/null
@@ -0,0 +1,37 @@
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+  transaction: Sequelize.Transaction,
+  queryInterface: Sequelize.QueryInterface,
+  sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+  {
+    const data = {
+      type: Sequelize.STRING(3000),
+      allowNull: false,
+      defaultValue: null
+    }
+
+    await utils.queryInterface.changeColumn('videoAbuse', 'reason', data)
+  }
+
+  {
+    const data = {
+      type: Sequelize.STRING(3000),
+      allowNull: true,
+      defaultValue: null
+    }
+
+    await utils.queryInterface.changeColumn('videoAbuse', 'moderationComment', data)
+  }
+}
+
+function down (options) {
+  throw new Error('Not implemented.')
+}
+
+export {
+  up,
+  down
+}
index 504263c9998c8b4e3cd9314ce97cd562bfc6b3cb..8215840da3eb0fdd525feb2943b6518cad30be47 100644 (file)
@@ -1,11 +1,10 @@
 import * as Bluebird from 'bluebird'
-import { join } from 'path'
 import { Transaction } from 'sequelize'
 import * as url from 'url'
 import * as uuidv4 from 'uuid/v4'
 import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub'
 import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects'
-import { checkUrlsSameHost, getAPUrl } from '../../helpers/activitypub'
+import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
 import { isActorObjectValid, normalizeActor } from '../../helpers/custom-validators/activitypub/actor'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
 import { retryTransactionWrapper, updateInstanceWithAnother } from '../../helpers/database-utils'
@@ -13,7 +12,7 @@ import { logger } from '../../helpers/logger'
 import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto'
 import { doRequest, downloadImage } from '../../helpers/requests'
 import { getUrlFromWebfinger } from '../../helpers/webfinger'
-import { AVATARS_SIZE, CONFIG, IMAGE_MIMETYPE_EXT, sequelizeTypescript } from '../../initializers'
+import { AVATARS_SIZE, CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers'
 import { AccountModel } from '../../models/account/account'
 import { ActorModel } from '../../models/activitypub/actor'
 import { AvatarModel } from '../../models/avatar/avatar'
@@ -43,7 +42,7 @@ async function getOrCreateActorAndServerAndModel (
   recurseIfNeeded = true,
   updateCollections = false
 ) {
-  const actorUrl = getAPUrl(activityActor)
+  const actorUrl = getAPId(activityActor)
   let created = false
 
   let actor = await fetchActorByUrl(actorUrl, fetchType)
@@ -172,15 +171,13 @@ async function fetchActorTotalItems (url: string) {
 
 async function fetchAvatarIfExists (actorJSON: ActivityPubActor) {
   if (
-    actorJSON.icon && actorJSON.icon.type === 'Image' && IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
+    actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined &&
     isActivityPubUrlValid(actorJSON.icon.url)
   ) {
-    const extension = IMAGE_MIMETYPE_EXT[actorJSON.icon.mediaType]
+    const extension = MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType]
 
     const avatarName = uuidv4() + extension
-    const destPath = join(CONFIG.STORAGE.AVATARS_DIR, avatarName)
-
-    await downloadImage(actorJSON.icon.url, destPath, AVATARS_SIZE)
+    await downloadImage(actorJSON.icon.url, CONFIG.STORAGE.AVATARS_DIR, avatarName, AVATARS_SIZE)
 
     return avatarName
   }
@@ -204,6 +201,69 @@ async function addFetchOutboxJob (actor: ActorModel) {
   return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })
 }
 
+async function refreshActorIfNeeded (
+  actorArg: ActorModel,
+  fetchedType: ActorFetchByUrlType
+): Promise<{ actor: ActorModel, refreshed: boolean }> {
+  if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
+
+  // We need more attributes
+  const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
+
+  try {
+    let actorUrl: string
+    try {
+      actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost())
+    } catch (err) {
+      logger.warn('Cannot get actor URL from webfinger, keeping the old one.', err)
+      actorUrl = actor.url
+    }
+
+    const { result, statusCode } = await fetchRemoteActor(actorUrl)
+
+    if (statusCode === 404) {
+      logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
+      actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy()
+      return { actor: undefined, refreshed: false }
+    }
+
+    if (result === undefined) {
+      logger.warn('Cannot fetch remote actor in refresh actor.')
+      return { actor, refreshed: false }
+    }
+
+    return sequelizeTypescript.transaction(async t => {
+      updateInstanceWithAnother(actor, result.actor)
+
+      if (result.avatarName !== undefined) {
+        await updateActorAvatarInstance(actor, result.avatarName, t)
+      }
+
+      // Force update
+      actor.setDataValue('updatedAt', new Date())
+      await actor.save({ transaction: t })
+
+      if (actor.Account) {
+        actor.Account.set('name', result.name)
+        actor.Account.set('description', result.summary)
+
+        await actor.Account.save({ transaction: t })
+      } else if (actor.VideoChannel) {
+        actor.VideoChannel.set('name', result.name)
+        actor.VideoChannel.set('description', result.summary)
+        actor.VideoChannel.set('support', result.support)
+
+        await actor.VideoChannel.save({ transaction: t })
+      }
+
+      return { refreshed: true, actor }
+    })
+  } catch (err) {
+    logger.warn('Cannot refresh actor.', { err })
+    return { actor, refreshed: false }
+  }
+}
+
 export {
   getOrCreateActorAndServerAndModel,
   buildActorInstance,
@@ -211,6 +271,7 @@ export {
   fetchActorTotalItems,
   fetchAvatarIfExists,
   updateActorInstance,
+  refreshActorIfNeeded,
   updateActorAvatarInstance,
   addFetchOutboxJob
 }
@@ -299,7 +360,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe
 
   const actorJSON: ActivityPubActor = requestResult.body
   if (isActorObjectValid(actorJSON) === false) {
-    logger.debug('Remote actor JSON is not valid.', { actorJSON: actorJSON })
+    logger.debug('Remote actor JSON is not valid.', { actorJSON })
     return { result: undefined, statusCode: requestResult.response.statusCode }
   }
 
@@ -375,59 +436,3 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu
 
   return videoChannelCreated
 }
-
-async function refreshActorIfNeeded (
-  actorArg: ActorModel,
-  fetchedType: ActorFetchByUrlType
-): Promise<{ actor: ActorModel, refreshed: boolean }> {
-  if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false }
-
-  // We need more attributes
-  const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url)
-
-  try {
-    const actorUrl = await getUrlFromWebfinger(actor.preferredUsername + '@' + actor.getHost())
-    const { result, statusCode } = await fetchRemoteActor(actorUrl)
-
-    if (statusCode === 404) {
-      logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url)
-      actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy()
-      return { actor: undefined, refreshed: false }
-    }
-
-    if (result === undefined) {
-      logger.warn('Cannot fetch remote actor in refresh actor.')
-      return { actor, refreshed: false }
-    }
-
-    return sequelizeTypescript.transaction(async t => {
-      updateInstanceWithAnother(actor, result.actor)
-
-      if (result.avatarName !== undefined) {
-        await updateActorAvatarInstance(actor, result.avatarName, t)
-      }
-
-      // Force update
-      actor.setDataValue('updatedAt', new Date())
-      await actor.save({ transaction: t })
-
-      if (actor.Account) {
-        actor.Account.set('name', result.name)
-        actor.Account.set('description', result.summary)
-
-        await actor.Account.save({ transaction: t })
-      } else if (actor.VideoChannel) {
-        actor.VideoChannel.set('name', result.name)
-        actor.VideoChannel.set('description', result.summary)
-        actor.VideoChannel.set('support', result.support)
-
-        await actor.VideoChannel.save({ transaction: t })
-      }
-
-      return { refreshed: true, actor }
-    })
-  } catch (err) {
-    logger.warn('Cannot refresh actor.', { err })
-    return { actor, refreshed: false }
-  }
-}
index 89bda9c32a531c4d49472702e59801ccae403582..ebb275e348fe840cff7782dd976a8edc314ab4c0 100644 (file)
@@ -24,6 +24,7 @@ async function processAccept (actor: ActorModel, targetActor: ActorModel) {
   if (follow.state !== 'accepted') {
     follow.set('state', 'accepted')
     await follow.save()
+
     await addFetchOutboxJob(targetActor)
   }
 }
index cc88b5423702f0be03dfd8d563b054e3b07c95e2..23310b41e8de4b4923d1a0d0b282ad4b86e1541b 100644 (file)
@@ -5,6 +5,8 @@ import { ActorModel } from '../../../models/activitypub/actor'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { VideoPrivacy } from '../../../../shared/models/videos'
+import { Notifier } from '../../notifier'
 
 async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) {
   return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity)
@@ -21,9 +23,9 @@ export {
 async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
   const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
 
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri })
+  const { video, created: videoCreated } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri })
 
-  return sequelizeTypescript.transaction(async t => {
+  await sequelizeTypescript.transaction(async t => {
     // Add share entry
 
     const share = {
@@ -49,4 +51,6 @@ async function processVideoShare (actorAnnouncer: ActorModel, activity: Activity
 
     return undefined
   })
+
+  if (videoCreated) Notifier.Instance.notifyOnNewVideo(video)
 }
index cd7ea01aa099464fb5a5e827862215caf49ac8b9..5f4d793a5746db35d9ae3312616bc76afb15b20f 100644 (file)
@@ -1,36 +1,44 @@
-import { ActivityCreate, CacheFileObject, VideoAbuseState, VideoTorrentObject } from '../../../../shared'
-import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
+import { ActivityCreate, CacheFileObject, VideoTorrentObject } from '../../../../shared'
 import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
 import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { logger } from '../../../helpers/logger'
 import { sequelizeTypescript } from '../../../initializers'
-import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
 import { ActorModel } from '../../../models/activitypub/actor'
-import { VideoAbuseModel } from '../../../models/video/video-abuse'
 import { addVideoComment, resolveThread } from '../video-comments'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { forwardVideoRelatedActivity } from '../send/utils'
-import { Redis } from '../../redis'
 import { createOrUpdateCacheFile } from '../cache-file'
-import { getVideoDislikeActivityPubUrl } from '../url'
-import { VideoModel } from '../../../models/video/video'
+import { Notifier } from '../../notifier'
+import { processViewActivity } from './process-view'
+import { processDislikeActivity } from './process-dislike'
+import { processFlagActivity } from './process-flag'
 
 async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) {
   const activityObject = activity.object
   const activityType = activityObject.type
 
   if (activityType === 'View') {
-    return processCreateView(byActor, activity)
-  } else if (activityType === 'Dislike') {
-    return retryTransactionWrapper(processCreateDislike, byActor, activity)
-  } else if (activityType === 'Video') {
+    return processViewActivity(activity, byActor)
+  }
+
+  if (activityType === 'Dislike') {
+    return retryTransactionWrapper(processDislikeActivity, activity, byActor)
+  }
+
+  if (activityType === 'Flag') {
+    return retryTransactionWrapper(processFlagActivity, activity, byActor)
+  }
+
+  if (activityType === 'Video') {
     return processCreateVideo(activity)
-  } else if (activityType === 'Flag') {
-    return retryTransactionWrapper(processCreateVideoAbuse, byActor, activityObject as VideoAbuseObject)
-  } else if (activityType === 'Note') {
-    return retryTransactionWrapper(processCreateVideoComment, byActor, activity)
-  } else if (activityType === 'CacheFile') {
-    return retryTransactionWrapper(processCacheFile, byActor, activity)
+  }
+
+  if (activityType === 'Note') {
+    return retryTransactionWrapper(processCreateVideoComment, activity, byActor)
+  }
+
+  if (activityType === 'CacheFile') {
+    return retryTransactionWrapper(processCacheFile, activity, byActor)
   }
 
   logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -48,61 +56,14 @@ export {
 async function processCreateVideo (activity: ActivityCreate) {
   const videoToCreateData = activity.object as VideoTorrentObject
 
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
+  const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
 
-  return video
-}
-
-async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
-  const dislike = activity.object as DislikeObject
-  const byAccount = byActor.Account
-
-  if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
-
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
+  if (created) Notifier.Instance.notifyOnNewVideo(video)
 
-  return sequelizeTypescript.transaction(async t => {
-    const rate = {
-      type: 'dislike' as 'dislike',
-      videoId: video.id,
-      accountId: byAccount.id
-    }
-
-    const [ , created ] = await AccountVideoRateModel.findOrCreate({
-      where: rate,
-      defaults: Object.assign({}, rate, { url: getVideoDislikeActivityPubUrl(byActor, video) }),
-      transaction: t
-    })
-    if (created === true) await video.increment('dislikes', { transaction: t })
-
-    if (video.isOwned() && created === true) {
-      // Don't resend the activity to the sender
-      const exceptions = [ byActor ]
-
-      await forwardVideoRelatedActivity(activity, t, exceptions, video)
-    }
-  })
-}
-
-async function processCreateView (byActor: ActorModel, activity: ActivityCreate) {
-  const view = activity.object as ViewObject
-
-  const options = {
-    videoObject: view.object,
-    fetchType: 'only-video' as 'only-video'
-  }
-  const { video } = await getOrCreateVideoAndAccountAndChannel(options)
-
-  await Redis.Instance.addVideoView(video.id)
-
-  if (video.isOwned()) {
-    // Don't resend the activity to the sender
-    const exceptions = [ byActor ]
-    await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
-  }
+  return video
 }
 
-async function processCacheFile (byActor: ActorModel, activity: ActivityCreate) {
+async function processCacheFile (activity: ActivityCreate, byActor: ActorModel) {
   const cacheFile = activity.object as CacheFileObject
 
   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
@@ -118,29 +79,7 @@ async function processCacheFile (byActor: ActorModel, activity: ActivityCreate)
   }
 }
 
-async function processCreateVideoAbuse (byActor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
-  logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
-
-  const account = byActor.Account
-  if (!account) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
-
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoAbuseToCreateData.object })
-
-  return sequelizeTypescript.transaction(async t => {
-    const videoAbuseData = {
-      reporterAccountId: account.id,
-      reason: videoAbuseToCreateData.content,
-      videoId: video.id,
-      state: VideoAbuseState.PENDING
-    }
-
-    await VideoAbuseModel.create(videoAbuseData, { transaction: t })
-
-    logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
-  })
-}
-
-async function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) {
+async function processCreateVideoComment (activity: ActivityCreate, byActor: ActorModel) {
   const commentObject = activity.object as VideoCommentObject
   const byAccount = byActor.Account
 
@@ -148,7 +87,7 @@ async function processCreateVideoComment (byActor: ActorModel, activity: Activit
 
   const { video } = await resolveThread(commentObject.inReplyTo)
 
-  const { created } = await addVideoComment(video, commentObject.id)
+  const { comment, created } = await addVideoComment(video, commentObject.id)
 
   if (video.isOwned() && created === true) {
     // Don't resend the activity to the sender
@@ -156,4 +95,6 @@ async function processCreateVideoComment (byActor: ActorModel, activity: Activit
 
     await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
   }
+
+  if (created === true) Notifier.Instance.notifyOnNewComment(comment)
 }
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts
new file mode 100644 (file)
index 0000000..bfd69e0
--- /dev/null
@@ -0,0 +1,52 @@
+import { ActivityCreate, ActivityDislike } from '../../../../shared'
+import { DislikeObject } from '../../../../shared/models/activitypub/objects'
+import { retryTransactionWrapper } from '../../../helpers/database-utils'
+import { sequelizeTypescript } from '../../../initializers'
+import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
+import { ActorModel } from '../../../models/activitypub/actor'
+import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { forwardVideoRelatedActivity } from '../send/utils'
+import { getVideoDislikeActivityPubUrl } from '../url'
+
+async function processDislikeActivity (activity: ActivityCreate | ActivityDislike, byActor: ActorModel) {
+  return retryTransactionWrapper(processDislike, activity, byActor)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  processDislikeActivity
+}
+
+// ---------------------------------------------------------------------------
+
+async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: ActorModel) {
+  const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object
+  const byAccount = byActor.Account
+
+  if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
+
+  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislikeObject })
+
+  return sequelizeTypescript.transaction(async t => {
+    const rate = {
+      type: 'dislike' as 'dislike',
+      videoId: video.id,
+      accountId: byAccount.id
+    }
+
+    const [ , created ] = await AccountVideoRateModel.findOrCreate({
+      where: rate,
+      defaults: Object.assign({}, rate, { url: getVideoDislikeActivityPubUrl(byActor, video) }),
+      transaction: t
+    })
+    if (created === true) await video.increment('dislikes', { transaction: t })
+
+    if (video.isOwned() && created === true) {
+      // Don't resend the activity to the sender
+      const exceptions = [ byActor ]
+
+      await forwardVideoRelatedActivity(activity, t, exceptions, video)
+    }
+  })
+}
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
new file mode 100644 (file)
index 0000000..79ce6fb
--- /dev/null
@@ -0,0 +1,49 @@
+import { ActivityCreate, ActivityFlag, VideoAbuseState } from '../../../../shared'
+import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects'
+import { retryTransactionWrapper } from '../../../helpers/database-utils'
+import { logger } from '../../../helpers/logger'
+import { sequelizeTypescript } from '../../../initializers'
+import { ActorModel } from '../../../models/activitypub/actor'
+import { VideoAbuseModel } from '../../../models/video/video-abuse'
+import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { Notifier } from '../../notifier'
+import { getAPId } from '../../../helpers/activitypub'
+
+async function processFlagActivity (activity: ActivityCreate | ActivityFlag, byActor: ActorModel) {
+  return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  processFlagActivity
+}
+
+// ---------------------------------------------------------------------------
+
+async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: ActorModel) {
+  const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
+
+  logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
+
+  const account = byActor.Account
+  if (!account) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
+
+  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object })
+
+  return sequelizeTypescript.transaction(async t => {
+    const videoAbuseData = {
+      reporterAccountId: account.id,
+      reason: flag.content,
+      videoId: video.id,
+      state: VideoAbuseState.PENDING
+    }
+
+    const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t })
+    videoAbuseInstance.Video = video
+
+    Notifier.Instance.notifyOnNewVideoAbuse(videoAbuseInstance)
+
+    logger.info('Remote abuse for video uuid %s created', flag.object)
+  })
+}
index 24c9085f7ac77520a537e713e8c09a336518341c..0cd537187a93ccacc939d85f0258ef473f68a28b 100644 (file)
@@ -5,9 +5,11 @@ import { sequelizeTypescript } from '../../../initializers'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { sendAccept } from '../send'
+import { Notifier } from '../../notifier'
+import { getAPId } from '../../../helpers/activitypub'
 
 async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) {
-  const activityObject = activity.object
+  const activityObject = getAPId(activity.object)
 
   return retryTransactionWrapper(processFollow, byActor, activityObject)
 }
@@ -21,13 +23,13 @@ export {
 // ---------------------------------------------------------------------------
 
 async function processFollow (actor: ActorModel, targetActorURL: string) {
-  await sequelizeTypescript.transaction(async t => {
+  const { actorFollow, created } = await sequelizeTypescript.transaction(async t => {
     const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
 
     if (!targetActor) throw new Error('Unknown actor')
     if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
 
-    const [ actorFollow ] = await ActorFollowModel.findOrCreate({
+    const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({
       where: {
         actorId: actor.id,
         targetActorId: targetActor.id
@@ -52,8 +54,12 @@ async function processFollow (actor: ActorModel, targetActorURL: string) {
     actorFollow.ActorFollowing = targetActor
 
     // Target sends to actor he accepted the follow request
-    return sendAccept(actorFollow)
+    await sendAccept(actorFollow)
+
+    return { actorFollow, created }
   })
 
+  if (created) Notifier.Instance.notifyOfNewFollow(actorFollow)
+
   logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url)
 }
index e8e97eecef078df5d57e9d2b1682059b5d8c5573..2a04167d78a5c72364981a75c8f6fc5d64493096 100644 (file)
@@ -6,6 +6,7 @@ import { ActorModel } from '../../../models/activitypub/actor'
 import { forwardVideoRelatedActivity } from '../send/utils'
 import { getOrCreateVideoAndAccountAndChannel } from '../videos'
 import { getVideoLikeActivityPubUrl } from '../url'
+import { getAPId } from '../../../helpers/activitypub'
 
 async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) {
   return retryTransactionWrapper(processLikeVideo, byActor, activity)
@@ -20,7 +21,7 @@ export {
 // ---------------------------------------------------------------------------
 
 async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) {
-  const videoUrl = activity.object
+  const videoUrl = getAPId(activity.object)
 
   const byAccount = byActor.Account
   if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
index 438a013b630d069e96b6b0dda147d9734cc03bd2..ed0177a67f785a0c5a3c260ac79cd1211b5d8764 100644 (file)
@@ -26,6 +26,10 @@ async function processUndoActivity (activity: ActivityUndo, byActor: ActorModel)
     }
   }
 
+  if (activityToUndo.type === 'Dislike') {
+    return retryTransactionWrapper(processUndoDislike, byActor, activity)
+  }
+
   if (activityToUndo.type === 'Follow') {
     return retryTransactionWrapper(processUndoFollow, byActor, activityToUndo)
   }
@@ -72,7 +76,9 @@ async function processUndoLike (byActor: ActorModel, activity: ActivityUndo) {
 }
 
 async function processUndoDislike (byActor: ActorModel, activity: ActivityUndo) {
-  const dislike = activity.object.object as DislikeObject
+  const dislike = activity.object.type === 'Dislike'
+    ? activity.object
+    : activity.object.object as DislikeObject
 
   const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
 
index 03831a00e108947e060dabd80ad24c6a72e5ffdc..c6b42d8465eae5de78c861675e33e183017e9fd5 100644 (file)
@@ -51,7 +51,7 @@ async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate)
     return undefined
   }
 
-  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id })
+  const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false })
   const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
 
   const updateOptions = {
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts
new file mode 100644 (file)
index 0000000..8f66d36
--- /dev/null
@@ -0,0 +1,35 @@
+import { ActorModel } from '../../../models/activitypub/actor'
+import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { forwardVideoRelatedActivity } from '../send/utils'
+import { Redis } from '../../redis'
+import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
+
+async function processViewActivity (activity: ActivityView | ActivityCreate, byActor: ActorModel) {
+  return processCreateView(activity, byActor)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  processViewActivity
+}
+
+// ---------------------------------------------------------------------------
+
+async function processCreateView (activity: ActivityView | ActivityCreate, byActor: ActorModel) {
+  const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object
+
+  const options = {
+    videoObject: videoObject,
+    fetchType: 'only-video' as 'only-video'
+  }
+  const { video } = await getOrCreateVideoAndAccountAndChannel(options)
+
+  await Redis.Instance.addVideoView(video.id)
+
+  if (video.isOwned()) {
+    // Don't resend the activity to the sender
+    const exceptions = [ byActor ]
+    await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
+  }
+}
index bcc5cac7ac23e5db390998c11b8fd5cc1e81a6f6..9dd241402dfd1a0bc227792d7e61cf1044c0534d 100644 (file)
@@ -1,5 +1,5 @@
 import { Activity, ActivityType } from '../../../../shared/models/activitypub'
-import { checkUrlsSameHost, getAPUrl } from '../../../helpers/activitypub'
+import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub'
 import { logger } from '../../../helpers/logger'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { processAcceptActivity } from './process-accept'
@@ -12,6 +12,9 @@ import { processRejectActivity } from './process-reject'
 import { processUndoActivity } from './process-undo'
 import { processUpdateActivity } from './process-update'
 import { getOrCreateActorAndServerAndModel } from '../actor'
+import { processDislikeActivity } from './process-dislike'
+import { processFlagActivity } from './process-flag'
+import { processViewActivity } from './process-view'
 
 const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: ActorModel, inboxActor?: ActorModel) => Promise<any> } = {
   Create: processCreateActivity,
@@ -22,7 +25,10 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: Ac
   Reject: processRejectActivity,
   Announce: processAnnounceActivity,
   Undo: processUndoActivity,
-  Like: processLikeActivity
+  Like: processLikeActivity,
+  Dislike: processDislikeActivity,
+  Flag: processFlagActivity,
+  View: processViewActivity
 }
 
 async function processActivities (
@@ -35,12 +41,12 @@ async function processActivities (
   const actorsCache: { [ url: string ]: ActorModel } = {}
 
   for (const activity of activities) {
-    if (!options.signatureActor && [ 'Create', 'Announce', 'Like' ].indexOf(activity.type) === -1) {
+    if (!options.signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
       logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
       continue
     }
 
-    const actorUrl = getAPUrl(activity.actor)
+    const actorUrl = getAPId(activity.actor)
 
     // When we fetch remote data, we don't have signature
     if (options.signatureActor && actorUrl !== options.signatureActor.url) {
index 5dcba778cf73a226dbb0e2200e4523e01e603d26..1767df0aeb5b895761a01c93079f1579a54eefdd 100644 (file)
@@ -11,7 +11,7 @@ import { doRequest } from '../../helpers/requests'
 import { getOrCreateActorAndServerAndModel } from './actor'
 import { logger } from '../../helpers/logger'
 import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
-import { checkUrlsSameHost, getAPUrl } from '../../helpers/activitypub'
+import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
 
 async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) {
   if (video.privacy === VideoPrivacy.PRIVATE) return undefined
@@ -41,7 +41,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) {
       })
       if (!body || !body.actor) throw new Error('Body or body actor is invalid')
 
-      const actorUrl = getAPUrl(body.actor)
+      const actorUrl = getAPId(body.actor)
       if (checkUrlsSameHost(shareUrl, actorUrl) !== true) {
         throw new Error(`Actor url ${actorUrl} has not the same host than the share url ${shareUrl}`)
       }
@@ -78,7 +78,7 @@ async function shareByServer (video: VideoModel, t: Transaction) {
   const serverActor = await getServerActor()
 
   const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video)
-  return VideoShareModel.findOrCreate({
+  const [ serverShare ] = await VideoShareModel.findOrCreate({
     defaults: {
       actorId: serverActor.id,
       videoId: video.id,
@@ -88,16 +88,14 @@ async function shareByServer (video: VideoModel, t: Transaction) {
       url: serverShareUrl
     },
     transaction: t
-  }).then(([ serverShare, created ]) => {
-    if (created) return sendVideoAnnounce(serverActor, serverShare, video, t)
-
-    return undefined
   })
+
+  return sendVideoAnnounce(serverActor, serverShare, video, t)
 }
 
 async function shareByVideoChannel (video: VideoModel, t: Transaction) {
   const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
-  return VideoShareModel.findOrCreate({
+  const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
     defaults: {
       actorId: video.VideoChannel.actorId,
       videoId: video.id,
@@ -107,11 +105,9 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) {
       url: videoChannelShareUrl
     },
     transaction: t
-  }).then(([ videoChannelShare, created ]) => {
-    if (created) return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
-
-    return undefined
   })
+
+  return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
 }
 
 async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) {
index 5868e7297e5fd1b31b2aec448fa24209b79545f8..e87301fe7d0b25250d67579de96ca5680828bd82 100644 (file)
@@ -70,7 +70,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
     throw new Error(`Comment url ${commentUrl} host is different from the AP object id ${body.id}`)
   }
 
-  const actor = await getOrCreateActorAndServerAndModel(actorUrl)
+  const actor = await getOrCreateActorAndServerAndModel(actorUrl, 'all')
   const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body)
   if (!entry) return { created: false }
 
@@ -80,6 +80,8 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) {
     },
     defaults: entry
   })
+  comment.Account = actor.Account
+  comment.Video = videoInstance
 
   return { comment, created }
 }
index 2cce67f0c81194e1d018b0f10e298992ad3563a5..45a2b22eaea405a1db2b3a98a8fcdad91dd8d4db 100644 (file)
@@ -9,7 +9,7 @@ import { AccountVideoRateModel } from '../../models/account/account-video-rate'
 import { logger } from '../../helpers/logger'
 import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers'
 import { doRequest } from '../../helpers/requests'
-import { checkUrlsSameHost, getAPUrl } from '../../helpers/activitypub'
+import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
 import { ActorModel } from '../../models/activitypub/actor'
 import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url'
 
@@ -26,7 +26,7 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa
       })
       if (!body || !body.actor) throw new Error('Body or body actor is invalid')
 
-      const actorUrl = getAPUrl(body.actor)
+      const actorUrl = getAPId(body.actor)
       if (checkUrlsSameHost(actorUrl, rateUrl) !== true) {
         throw new Error(`Rate url ${rateUrl} has not the same host than actor url ${actorUrl}`)
       }
index 998f903303137aa17040d6bb6c5cfc860a5a819f..e1e52349939dcdd2d253c24b32eb240a8863ddbb 100644 (file)
@@ -1,7 +1,6 @@
 import * as Bluebird from 'bluebird'
 import * as sequelize from 'sequelize'
 import * as magnetUtil from 'magnet-uri'
-import { join } from 'path'
 import * as request from 'request'
 import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index'
 import { VideoTorrentObject } from '../../../shared/models/activitypub/objects'
@@ -11,7 +10,7 @@ import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos
 import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils'
 import { logger } from '../../helpers/logger'
 import { doRequest, downloadImage } from '../../helpers/requests'
-import { ACTIVITY_PUB, CONFIG, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_MIMETYPE_EXT } from '../../initializers'
+import { ACTIVITY_PUB, CONFIG, MIMETYPES, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE } from '../../initializers'
 import { ActorModel } from '../../models/activitypub/actor'
 import { TagModel } from '../../models/video/tag'
 import { VideoModel } from '../../models/video/video'
@@ -29,7 +28,8 @@ import { createRates } from './video-rates'
 import { addVideoShares, shareVideoByServerAndChannel } from './share'
 import { AccountModel } from '../../models/account/account'
 import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video'
-import { checkUrlsSameHost, getAPUrl } from '../../helpers/activitypub'
+import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub'
+import { Notifier } from '../notifier'
 
 async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) {
   // If the video is not private and published, we federate it
@@ -95,9 +95,8 @@ function fetchRemoteVideoStaticFile (video: VideoModel, path: string, reject: Fu
 
 function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) {
   const thumbnailName = video.getThumbnailName()
-  const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName)
 
-  return downloadImage(icon.url, thumbnailPath, THUMBNAILS_SIZE)
+  return downloadImage(icon.url, CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName, THUMBNAILS_SIZE)
 }
 
 function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) {
@@ -156,29 +155,34 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid
 }
 
 async function getOrCreateVideoAndAccountAndChannel (options: {
-  videoObject: VideoTorrentObject | string,
+  videoObject: { id: string } | string,
   syncParam?: SyncParam,
-  fetchType?: VideoFetchByUrlType
+  fetchType?: VideoFetchByUrlType,
+  allowRefresh?: boolean // true by default
 }) {
   // Default params
   const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
   const fetchType = options.fetchType || 'all'
+  const allowRefresh = options.allowRefresh !== false
 
   // Get video url
-  const videoUrl = getAPUrl(options.videoObject)
+  const videoUrl = getAPId(options.videoObject)
 
   let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType)
   if (videoFromDatabase) {
-    const refreshOptions = {
-      video: videoFromDatabase,
-      fetchedType: fetchType,
-      syncParam
-    }
 
-    if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
-    else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', videoUrl: videoFromDatabase.url } })
+    if (allowRefresh === true) {
+      const refreshOptions = {
+        video: videoFromDatabase,
+        fetchedType: fetchType,
+        syncParam
+      }
+
+      if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
+      else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoFromDatabase.url } })
+    }
 
-    return { video: videoFromDatabase }
+    return { video: videoFromDatabase, created: false }
   }
 
   const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl)
@@ -189,7 +193,7 @@ async function getOrCreateVideoAndAccountAndChannel (options: {
 
   await syncVideoExternalAttributes(video, fetchedVideo, syncParam)
 
-  return { video }
+  return { video, created: true }
 }
 
 async function updateVideoFromAP (options: {
@@ -200,13 +204,14 @@ async function updateVideoFromAP (options: {
   overrideTo?: string[]
 }) {
   logger.debug('Updating remote video "%s".', options.videoObject.uuid)
+
   let videoFieldsSave: any
+  const wasPrivateVideo = options.video.privacy === VideoPrivacy.PRIVATE
+  const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED
 
   try {
     await sequelizeTypescript.transaction(async t => {
-      const sequelizeOptions = {
-        transaction: t
-      }
+      const sequelizeOptions = { transaction: t }
 
       videoFieldsSave = options.video.toJSON()
 
@@ -276,6 +281,11 @@ async function updateVideoFromAP (options: {
       }
     })
 
+    // Notify our users?
+    if (wasPrivateVideo || wasUnlistedVideo) {
+      Notifier.Instance.notifyOnNewVideo(options.video)
+    }
+
     logger.info('Remote video with uuid %s updated', options.videoObject.uuid)
   } catch (err) {
     if (options.video !== undefined && videoFieldsSave !== undefined) {
@@ -358,7 +368,7 @@ export {
 // ---------------------------------------------------------------------------
 
 function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject {
-  const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
+  const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT)
 
   const urlMediaType = url.mediaType || url.mimeType
   return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/')
@@ -486,7 +496,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid
 
     const mediaType = fileUrl.mediaType || fileUrl.mimeType
     const attribute = {
-      extname: VIDEO_MIMETYPE_EXT[ mediaType ],
+      extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ],
       infoHash: parsed.infoHash,
       resolution: fileUrl.height,
       size: fileUrl.size,
diff --git a/server/lib/cache/actor-follow-score-cache.ts b/server/lib/cache/actor-follow-score-cache.ts
new file mode 100644 (file)
index 0000000..d070bde
--- /dev/null
@@ -0,0 +1,46 @@
+import { ACTOR_FOLLOW_SCORE } from '../../initializers'
+import { logger } from '../../helpers/logger'
+
+// Cache follows scores, instead of writing them too often in database
+// Keep data in memory, we don't really need Redis here as we don't really care to loose some scores
+class ActorFollowScoreCache {
+
+  private static instance: ActorFollowScoreCache
+  private pendingFollowsScore: { [ url: string ]: number } = {}
+
+  private constructor () {}
+
+  static get Instance () {
+    return this.instance || (this.instance = new this())
+  }
+
+  updateActorFollowsScore (goodInboxes: string[], badInboxes: string[]) {
+    if (goodInboxes.length === 0 && badInboxes.length === 0) return
+
+    logger.info('Updating %d good actor follows and %d bad actor follows scores in cache.', goodInboxes.length, badInboxes.length)
+
+    for (const goodInbox of goodInboxes) {
+      if (this.pendingFollowsScore[goodInbox] === undefined) this.pendingFollowsScore[goodInbox] = 0
+
+      this.pendingFollowsScore[goodInbox] += ACTOR_FOLLOW_SCORE.BONUS
+    }
+
+    for (const badInbox of badInboxes) {
+      if (this.pendingFollowsScore[badInbox] === undefined) this.pendingFollowsScore[badInbox] = 0
+
+      this.pendingFollowsScore[badInbox] += ACTOR_FOLLOW_SCORE.PENALTY
+    }
+  }
+
+  getPendingFollowsScoreCopy () {
+    return this.pendingFollowsScore
+  }
+
+  clearPendingFollowsScore () {
+    this.pendingFollowsScore = {}
+  }
+}
+
+export {
+  ActorFollowScoreCache
+}
index 54eb983fa16ec1c970982399562c08c5d4891f57..e921d04a719f168c6d2028c1ef663f368b92355f 100644 (file)
@@ -1,2 +1,3 @@
+export * from './actor-follow-score-cache'
 export * from './videos-preview-cache'
 export * from './videos-caption-cache'
index fc013e0c3bc2601af26d7cf7e171886c62c9ba44..b2c376e209529de65740dde6003ae768e256a259 100644 (file)
@@ -1,7 +1,7 @@
 import * as express from 'express'
 import * as Bluebird from 'bluebird'
 import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n'
-import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, STATIC_PATHS } from '../initializers'
+import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE } from '../initializers'
 import { join } from 'path'
 import { escapeHTML } from '../helpers/core-utils'
 import { VideoModel } from '../models/video/video'
@@ -18,21 +18,13 @@ export class ClientHtml {
     ClientHtml.htmlCache = {}
   }
 
-  static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) {
-    const path = ClientHtml.getIndexPath(req, res, paramLang)
-    if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path]
-
-    const buffer = await readFile(path)
+  static async getDefaultHTMLPage (req: express.Request, res: express.Response, paramLang?: string) {
+    const html = await ClientHtml.getIndexHTML(req, res, paramLang)
 
-    let html = buffer.toString()
-
-    html = ClientHtml.addTitleTag(html)
-    html = ClientHtml.addDescriptionTag(html)
-    html = ClientHtml.addCustomCSS(html)
+    let customHtml = ClientHtml.addTitleTag(html)
+    customHtml = ClientHtml.addDescriptionTag(customHtml)
 
-    ClientHtml.htmlCache[path] = html
-
-    return html
+    return customHtml
   }
 
   static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) {
@@ -55,7 +47,26 @@ export class ClientHtml {
       return ClientHtml.getIndexHTML(req, res)
     }
 
-    return ClientHtml.addOpenGraphAndOEmbedTags(html, video)
+    let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name))
+    customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description))
+    customHtml = ClientHtml.addOpenGraphAndOEmbedTags(customHtml, video)
+
+    return customHtml
+  }
+
+  private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) {
+    const path = ClientHtml.getIndexPath(req, res, paramLang)
+    if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path]
+
+    const buffer = await readFile(path)
+
+    let html = buffer.toString()
+
+    html = ClientHtml.addCustomCSS(html)
+
+    ClientHtml.htmlCache[path] = html
+
+    return html
   }
 
   private static getIndexPath (req: express.Request, res: express.Response, paramLang?: string) {
@@ -81,14 +92,18 @@ export class ClientHtml {
     return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html')
   }
 
-  private static addTitleTag (htmlStringPage: string) {
-    const titleTag = '<title>' + CONFIG.INSTANCE.NAME + '</title>'
+  private static addTitleTag (htmlStringPage: string, title?: string) {
+    let text = title || CONFIG.INSTANCE.NAME
+    if (title) text += ` - ${CONFIG.INSTANCE.NAME}`
+
+    const titleTag = `<title>${text}</title>`
 
     return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.TITLE, titleTag)
   }
 
-  private static addDescriptionTag (htmlStringPage: string) {
-    const descriptionTag = `<meta name="description" content="${CONFIG.INSTANCE.SHORT_DESCRIPTION}" />`
+  private static addDescriptionTag (htmlStringPage: string, description?: string) {
+    const content = description || CONFIG.INSTANCE.SHORT_DESCRIPTION
+    const descriptionTag = `<meta name="description" content="${content}" />`
 
     return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.DESCRIPTION, descriptionTag)
   }
@@ -100,8 +115,8 @@ export class ClientHtml {
   }
 
   private static addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
-    const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
-    const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
+    const previewUrl = CONFIG.WEBSERVER.URL + video.getPreviewStaticPath()
+    const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
 
     const videoNameEscaped = escapeHTML(video.name)
     const videoDescriptionEscaped = escapeHTML(video.description)
@@ -172,8 +187,8 @@ export class ClientHtml {
     // Schema.org
     tagsString += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>`
 
-    // SEO
-    tagsString += `<link rel="canonical" href="${videoUrl}" />`
+    // SEO, use origin video url so Google does not index remote videos
+    tagsString += `<link rel="canonical" href="${video.url}" />`
 
     return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.OPENGRAPH_AND_OEMBED, tagsString)
   }
index 9327792fb6f8a731e10e252f2325218eff24dfdd..f384a254e017d6912a71e8063ce2c25c19aa8512 100644 (file)
@@ -1,5 +1,4 @@
 import { createTransport, Transporter } from 'nodemailer'
-import { UserRight } from '../../shared/models/users'
 import { isTestInstance } from '../helpers/core-utils'
 import { bunyanLogger, logger } from '../helpers/logger'
 import { CONFIG } from '../initializers'
@@ -8,6 +7,11 @@ import { VideoModel } from '../models/video/video'
 import { JobQueue } from './job-queue'
 import { EmailPayload } from './job-queue/handlers/email'
 import { readFileSync } from 'fs-extra'
+import { VideoCommentModel } from '../models/video/video-comment'
+import { VideoAbuseModel } from '../models/video/video-abuse'
+import { VideoBlacklistModel } from '../models/video/video-blacklist'
+import { VideoImportModel } from '../models/video/video-import'
+import { ActorFollowModel } from '../models/activitypub/actor-follow'
 
 class Emailer {
 
@@ -22,7 +26,7 @@ class Emailer {
     if (this.initialized === true) return
     this.initialized = true
 
-    if (CONFIG.SMTP.HOSTNAME && CONFIG.SMTP.PORT) {
+    if (Emailer.isEnabled()) {
       logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT)
 
       let tls
@@ -57,6 +61,10 @@ class Emailer {
     }
   }
 
+  static isEnabled () {
+    return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT
+  }
+
   async checkConnectionOrDie () {
     if (!this.transporter) return
 
@@ -72,50 +80,158 @@ class Emailer {
     }
   }
 
-  addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) {
+  addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) {
+    const channelName = video.VideoChannel.getDisplayName()
+    const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
+
     const text = `Hi dear user,\n\n` +
-      `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` +
-      `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
-      `If you are not the person who initiated this request, please ignore this email.\n\n` +
+      `Your subscription ${channelName} just published a new video: ${video.name}` +
+      `\n\n` +
+      `You can view it on ${videoUrl} ` +
+      `\n\n` +
       `Cheers,\n` +
       `PeerTube.`
 
     const emailPayload: EmailPayload = {
-      to: [ to ],
-      subject: 'Reset your PeerTube password',
+      to,
+      subject: channelName + ' just published a new video',
       text
     }
 
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  addVerifyEmailJob (to: string, verifyEmailUrl: string) {
-    const text = `Welcome to PeerTube,\n\n` +
-      `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must  verify your email! ` +
-      `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
-      `If you are not the person who initiated this request, please ignore this email.\n\n` +
+  addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') {
+    const followerName = actorFollow.ActorFollower.Account.getDisplayName()
+    const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName()
+
+    const text = `Hi dear user,\n\n` +
+      `Your ${followType} ${followingName} has a new subscriber: ${followerName}` +
+      `\n\n` +
       `Cheers,\n` +
       `PeerTube.`
 
     const emailPayload: EmailPayload = {
-      to: [ to ],
-      subject: 'Verify your PeerTube email',
+      to,
+      subject: 'New follower on your channel ' + followingName,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  myVideoPublishedNotification (to: string[], video: VideoModel) {
+    const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
+
+    const text = `Hi dear user,\n\n` +
+      `Your video ${video.name} has been published.` +
+      `\n\n` +
+      `You can view it on ${videoUrl} ` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: `Your video ${video.name} is published`,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) {
+    const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath()
+
+    const text = `Hi dear user,\n\n` +
+      `Your video import ${videoImport.getTargetIdentifier()} is finished.` +
+      `\n\n` +
+      `You can view the imported video on ${videoUrl} ` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: `Your video import ${videoImport.getTargetIdentifier()} is finished`,
       text
     }
 
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  async addVideoAbuseReportJob (videoId: number) {
-    const video = await VideoModel.load(videoId)
-    if (!video) throw new Error('Unknown Video id during Abuse report.')
+  myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) {
+    const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports'
+
+    const text = `Hi dear user,\n\n` +
+      `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` +
+      `\n\n` +
+      `See your videos import dashboard for more information: ${importUrl}` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: `Your video import ${videoImport.getTargetIdentifier()} encountered an error`,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) {
+    const accountName = comment.Account.getDisplayName()
+    const video = comment.Video
+    const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
+
+    const text = `Hi dear user,\n\n` +
+      `A new comment has been posted by ${accountName} on your video ${video.name}` +
+      `\n\n` +
+      `You can view it on ${commentUrl} ` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: 'New comment on your video ' + video.name,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) {
+    const accountName = comment.Account.getDisplayName()
+    const video = comment.Video
+    const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath()
+
+    const text = `Hi dear user,\n\n` +
+      `${accountName} mentioned you on video ${video.name}` +
+      `\n\n` +
+      `You can view the comment on ${commentUrl} ` +
+      `\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to,
+      subject: 'Mention on video ' + video.name,
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) {
+    const videoUrl = CONFIG.WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath()
 
     const text = `Hi,\n\n` +
-      `Your instance received an abuse for the following video ${video.url}\n\n` +
+      `${CONFIG.WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` +
       `Cheers,\n` +
       `PeerTube.`
 
-    const to = await UserModel.listEmailsWithRight(UserRight.MANAGE_VIDEO_ABUSES)
     const emailPayload: EmailPayload = {
       to,
       subject: '[PeerTube] Received a video abuse',
@@ -125,16 +241,27 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  async addVideoBlacklistReportJob (videoId: number, reason?: string) {
-    const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
-    if (!video) throw new Error('Unknown Video id during Blacklist report.')
-    // It's not our user
-    if (video.remote === true) return
+  addNewUserRegistrationNotification (to: string[], user: UserModel) {
+    const text = `Hi,\n\n` +
+      `User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
 
-    const user = await UserModel.loadById(video.VideoChannel.Account.userId)
+    const emailPayload: EmailPayload = {
+      to,
+      subject: '[PeerTube] New user registration on ' + CONFIG.WEBSERVER.HOST,
+      text
+    }
 
-    const reasonString = reason ? ` for the following reason: ${reason}` : ''
-    const blockedString = `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) {
+    const videoName = videoBlacklist.Video.name
+    const videoUrl = CONFIG.WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath()
+
+    const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : ''
+    const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.`
 
     const text = 'Hi,\n\n' +
       blockedString +
@@ -142,33 +269,26 @@ class Emailer {
       'Cheers,\n' +
       `PeerTube.`
 
-    const to = user.email
     const emailPayload: EmailPayload = {
-      to: [ to ],
-      subject: `[PeerTube] Video ${video.name} blacklisted`,
+      to,
+      subject: `[PeerTube] Video ${videoName} blacklisted`,
       text
     }
 
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  async addVideoUnblacklistReportJob (videoId: number) {
-    const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
-    if (!video) throw new Error('Unknown Video id during Blacklist report.')
-    // It's not our user
-    if (video.remote === true) return
-
-    const user = await UserModel.loadById(video.VideoChannel.Account.userId)
+  addVideoUnblacklistNotification (to: string[], video: VideoModel) {
+    const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath()
 
     const text = 'Hi,\n\n' +
-      `Your video ${video.name} on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
+      `Your video ${video.name} (${videoUrl}) on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` +
       '\n\n' +
       'Cheers,\n' +
       `PeerTube.`
 
-    const to = user.email
     const emailPayload: EmailPayload = {
-      to: [ to ],
+      to,
       subject: `[PeerTube] Video ${video.name} unblacklisted`,
       text
     }
@@ -176,6 +296,40 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
+  addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) {
+    const text = `Hi dear user,\n\n` +
+      `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` +
+      `Please follow this link to reset it: ${resetPasswordUrl}\n\n` +
+      `If you are not the person who initiated this request, please ignore this email.\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to: [ to ],
+      subject: 'Reset your PeerTube password',
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  addVerifyEmailJob (to: string, verifyEmailUrl: string) {
+    const text = `Welcome to PeerTube,\n\n` +
+      `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must  verify your email! ` +
+      `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` +
+      `If you are not the person who initiated this request, please ignore this email.\n\n` +
+      `Cheers,\n` +
+      `PeerTube.`
+
+    const emailPayload: EmailPayload = {
+      to: [ to ],
+      subject: 'Verify your PeerTube email',
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
   addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) {
     const reasonString = reason ? ` for the following reason: ${reason}` : ''
     const blockedWord = blocked ? 'blocked' : 'unblocked'
@@ -197,13 +351,32 @@ class Emailer {
     return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
   }
 
-  sendMail (to: string[], subject: string, text: string) {
-    if (!this.transporter) {
+  addContactFormJob (fromEmail: string, fromName: string, body: string) {
+    const text = 'Hello dear admin,\n\n' +
+      fromName + ' sent you a message' +
+      '\n\n---------------------------------------\n\n' +
+      body +
+      '\n\n---------------------------------------\n\n' +
+      'Cheers,\n' +
+      'PeerTube.'
+
+    const emailPayload: EmailPayload = {
+      from: fromEmail,
+      to: [ CONFIG.ADMIN.EMAIL ],
+      subject: '[PeerTube] Contact form submitted',
+      text
+    }
+
+    return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload })
+  }
+
+  sendMail (to: string[], subject: string, text: string, from?: string) {
+    if (!Emailer.isEnabled()) {
       throw new Error('Cannot send mail because SMTP is not configured.')
     }
 
     return this.transporter.sendMail({
-      from: CONFIG.SMTP.FROM_ADDRESS,
+      from: from || CONFIG.SMTP.FROM_ADDRESS,
       to: to.join(','),
       subject,
       text
index 36d0f237bd03dd6d54cde757feefdb9ceb182c1b..b4d381062564d5ba4b59178cedb26d8ad670f301 100644 (file)
@@ -8,6 +8,7 @@ import { getOrCreateActorAndServerAndModel } from '../../activitypub/actor'
 import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { ActorModel } from '../../../models/activitypub/actor'
+import { Notifier } from '../../notifier'
 
 export type ActivitypubFollowPayload = {
   followerActorId: number
@@ -42,7 +43,7 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function follow (fromActor: ActorModel, targetActor: ActorModel) {
+async function follow (fromActor: ActorModel, targetActor: ActorModel) {
   if (fromActor.id === targetActor.id) {
     throw new Error('Follower is the same than target actor.')
   }
@@ -50,7 +51,7 @@ function follow (fromActor: ActorModel, targetActor: ActorModel) {
   // Same server, direct accept
   const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending'
 
-  return sequelizeTypescript.transaction(async t => {
+  const actorFollow = await sequelizeTypescript.transaction(async t => {
     const [ actorFollow ] = await ActorFollowModel.findOrCreate({
       where: {
         actorId: fromActor.id,
@@ -68,5 +69,9 @@ function follow (fromActor: ActorModel, targetActor: ActorModel) {
 
     // Send a notification to remote server if our follow is not already accepted
     if (actorFollow.state !== 'accepted') await sendFollow(actorFollow)
+
+    return actorFollow
   })
+
+  if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewFollow(actorFollow)
 }
index abbd89b3b1a8f4f5f39bf44083294f0acbddaf5b..9493945ff6d9b0aadfc2514c2b4e0cb09b74ccef 100644 (file)
@@ -5,6 +5,7 @@ import { doRequest } from '../../../helpers/requests'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
 import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers'
+import { ActorFollowScoreCache } from '../../cache'
 
 export type ActivitypubHttpBroadcastPayload = {
   uris: string[]
@@ -38,7 +39,7 @@ async function processActivityPubHttpBroadcast (job: Bull.Job) {
       .catch(() => badUrls.push(uri))
   }, { concurrency: BROADCAST_CONCURRENCY })
 
-  return ActorFollowModel.updateActorFollowsScore(goodUrls, badUrls, undefined)
+  return ActorFollowScoreCache.Instance.updateActorFollowsScore(goodUrls, badUrls)
 }
 
 // ---------------------------------------------------------------------------
index d36479032849d5cfc635b54cf8e1cc188c09cdb3..3973dcdc8cc7dced56a3977a6e916b99cc24b612 100644 (file)
@@ -1,9 +1,9 @@
 import * as Bull from 'bull'
 import { logger } from '../../../helpers/logger'
 import { doRequest } from '../../../helpers/requests'
-import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils'
 import { JOB_REQUEST_TIMEOUT } from '../../../initializers'
+import { ActorFollowScoreCache } from '../../cache'
 
 export type ActivitypubHttpUnicastPayload = {
   uri: string
@@ -31,9 +31,9 @@ async function processActivityPubHttpUnicast (job: Bull.Job) {
 
   try {
     await doRequest(options)
-    ActorFollowModel.updateActorFollowsScore([ uri ], [], undefined)
+    ActorFollowScoreCache.Instance.updateActorFollowsScore([ uri ], [])
   } catch (err) {
-    ActorFollowModel.updateActorFollowsScore([], [ uri ], undefined)
+    ActorFollowScoreCache.Instance.updateActorFollowsScore([], [ uri ])
 
     throw err
   }
index 7752b3b4002d962dd55cb51ef47f9cac48fb33e0..454b975fefaeb89dcb5416c5ae08acda995b54bc 100644 (file)
@@ -1,29 +1,33 @@
 import * as Bull from 'bull'
 import { logger } from '../../../helpers/logger'
 import { fetchVideoByUrl } from '../../../helpers/video'
-import { refreshVideoIfNeeded } from '../../activitypub'
+import { refreshVideoIfNeeded, refreshActorIfNeeded } from '../../activitypub'
+import { ActorModel } from '../../../models/activitypub/actor'
 
 export type RefreshPayload = {
-  videoUrl: string
-  type: 'video'
+  type: 'video' | 'actor'
+  url: string
 }
 
 async function refreshAPObject (job: Bull.Job) {
   const payload = job.data as RefreshPayload
-  logger.info('Processing AP refresher in job %d.', job.id)
 
-  if (payload.type === 'video') return refreshAPVideo(payload.videoUrl)
+  logger.info('Processing AP refresher in job %d for %s.', job.id, payload.url)
+
+  if (payload.type === 'video') return refreshVideo(payload.url)
+  if (payload.type === 'actor') return refreshActor(payload.url)
 }
 
 // ---------------------------------------------------------------------------
 
 export {
+  refreshActor,
   refreshAPObject
 }
 
 // ---------------------------------------------------------------------------
 
-async function refreshAPVideo (videoUrl: string) {
+async function refreshVideo (videoUrl: string) {
   const fetchType = 'all' as 'all'
   const syncParam = { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true }
 
@@ -38,3 +42,13 @@ async function refreshAPVideo (videoUrl: string) {
     await refreshVideoIfNeeded(refreshOptions)
   }
 }
+
+async function refreshActor (actorUrl: string) {
+  const fetchType = 'all' as 'all'
+  const actor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorUrl)
+
+  if (actor) {
+    await refreshActorIfNeeded(actor, fetchType)
+  }
+
+}
index 73d98ae548ffa9b54b11ff70f623db667cd85e4b..220d0af32e80ecd62b61f7f5e8da58e234b40439 100644 (file)
@@ -6,13 +6,14 @@ export type EmailPayload = {
   to: string[]
   subject: string
   text: string
+  from?: string
 }
 
 async function processEmail (job: Bull.Job) {
   const payload = job.data as EmailPayload
   logger.info('Processing email in job %d.', job.id)
 
-  return Emailer.Instance.sendMail(payload.to, payload.subject, payload.text)
+  return Emailer.Instance.sendMail(payload.to, payload.subject, payload.text, payload.from)
 }
 
 // ---------------------------------------------------------------------------
index adc0a2a15cbf26d042edb631ff19d9403ba8b6c8..593e43cc5fe95abeb151e932a3bc1da293930584 100644 (file)
@@ -8,7 +8,8 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils'
 import { sequelizeTypescript } from '../../../initializers'
 import * as Bluebird from 'bluebird'
 import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils'
-import { importVideoFile, transcodeOriginalVideofile, optimizeVideofile } from '../../video-transcoding'
+import { importVideoFile, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding'
+import { Notifier } from '../../notifier'
 
 export type VideoFilePayload = {
   videoUUID: string
@@ -67,17 +68,17 @@ async function processVideoFile (job: Bull.Job) {
 async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
   if (video === undefined) return undefined
 
-  return sequelizeTypescript.transaction(async t => {
+  const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
     let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
     // Video does not exist anymore
     if (!videoDatabase) return undefined
 
-    let isNewVideo = false
+    let videoPublished = false
 
     // We transcoded the video file in another format, now we can publish it
     if (videoDatabase.state !== VideoState.PUBLISHED) {
-      isNewVideo = true
+      videoPublished = true
 
       videoDatabase.state = VideoState.PUBLISHED
       videoDatabase.publishedAt = new Date()
@@ -85,21 +86,26 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) {
     }
 
     // If the video was not published, we consider it is a new one for other instances
-    await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
+    await federateVideoIfNeeded(videoDatabase, videoPublished, t)
 
-    return undefined
+    return { videoDatabase, videoPublished }
   })
+
+  if (videoPublished) {
+    Notifier.Instance.notifyOnNewVideo(videoDatabase)
+    Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
+  }
 }
 
-async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boolean) {
-  if (video === undefined) return undefined
+async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) {
+  if (videoArg === undefined) return undefined
 
   // Outside the transaction (IO on disk)
-  const { videoFileResolution } = await video.getOriginalFileResolution()
+  const { videoFileResolution } = await videoArg.getOriginalFileResolution()
 
-  return sequelizeTypescript.transaction(async t => {
+  const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => {
     // Maybe the video changed in database, refresh it
-    const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
+    let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t)
     // Video does not exist anymore
     if (!videoDatabase) return undefined
 
@@ -110,8 +116,10 @@ async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boole
       { resolutions: resolutionsEnabled }
     )
 
+    let videoPublished = false
+
     if (resolutionsEnabled.length !== 0) {
-      const tasks: Bluebird<any>[] = []
+      const tasks: Bluebird<Bull.Job<any>>[] = []
 
       for (const resolution of resolutionsEnabled) {
         const dataInput = {
@@ -127,15 +135,22 @@ async function onVideoFileOptimizerSuccess (video: VideoModel, isNewVideo: boole
 
       logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled })
     } else {
+      videoPublished = true
+
       // No transcoding to do, it's now published
-      video.state = VideoState.PUBLISHED
-      video = await video.save({ transaction: t })
+      videoDatabase.state = VideoState.PUBLISHED
+      videoDatabase = await videoDatabase.save({ transaction: t })
 
-      logger.info('No transcoding jobs created for video %s (no resolutions).', video.uuid)
+      logger.info('No transcoding jobs created for video %s (no resolutions).', videoDatabase.uuid, { privacy: videoDatabase.privacy })
     }
 
-    return federateVideoIfNeeded(video, isNewVideo, t)
+    await federateVideoIfNeeded(videoDatabase, isNewVideo, t)
+
+    return { videoDatabase, videoPublished }
   })
+
+  if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase)
+  if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase)
 }
 
 // ---------------------------------------------------------------------------
index 4de901c0c1f55236e7ff1018955e3aeb5e532cdc..12004dcd7ef5fb7af316bb2c23da7163f92343df 100644 (file)
@@ -7,14 +7,15 @@ import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } fro
 import { extname, join } from 'path'
 import { VideoFileModel } from '../../../models/video/video-file'
 import { CONFIG, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_IMPORT_TIMEOUT } from '../../../initializers'
-import { doRequestAndSaveToFile, downloadImage } from '../../../helpers/requests'
+import { downloadImage } from '../../../helpers/requests'
 import { VideoState } from '../../../../shared'
 import { JobQueue } from '../index'
 import { federateVideoIfNeeded } from '../../activitypub'
 import { VideoModel } from '../../../models/video/video'
 import { downloadWebTorrentVideo } from '../../../helpers/webtorrent'
 import { getSecureTorrentName } from '../../../helpers/utils'
-import { remove, rename, stat } from 'fs-extra'
+import { remove, move, stat } from 'fs-extra'
+import { Notifier } from '../../notifier'
 
 type VideoImportYoutubeDLPayload = {
   type: 'youtube-dl'
@@ -109,6 +110,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
   let tempVideoPath: string
   let videoDestFile: string
   let videoFile: VideoFileModel
+
   try {
     // Download video from youtubeDL
     tempVideoPath = await downloader()
@@ -138,14 +140,13 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
 
     // Move file
     videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile))
-    await rename(tempVideoPath, videoDestFile)
+    await move(tempVideoPath, videoDestFile)
     tempVideoPath = null // This path is not used anymore
 
     // Process thumbnail
     if (options.downloadThumbnail) {
       if (options.thumbnailUrl) {
-        const destThumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoImport.Video.getThumbnailName())
-        await downloadImage(options.thumbnailUrl, destThumbnailPath, THUMBNAILS_SIZE)
+        await downloadImage(options.thumbnailUrl, CONFIG.STORAGE.THUMBNAILS_DIR, videoImport.Video.getThumbnailName(), THUMBNAILS_SIZE)
       } else {
         await videoImport.Video.createThumbnail(videoFile)
       }
@@ -156,8 +157,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     // Process preview
     if (options.downloadPreview) {
       if (options.thumbnailUrl) {
-        const destPreviewPath = join(CONFIG.STORAGE.PREVIEWS_DIR, videoImport.Video.getPreviewName())
-        await downloadImage(options.thumbnailUrl, destPreviewPath, PREVIEWS_SIZE)
+        await downloadImage(options.thumbnailUrl, CONFIG.STORAGE.PREVIEWS_DIR, videoImport.Video.getPreviewName(), PREVIEWS_SIZE)
       } else {
         await videoImport.Video.createPreview(videoFile)
       }
@@ -180,7 +180,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
       // Update video DB object
       video.duration = duration
       video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED
-      const videoUpdated = await video.save({ transaction: t })
+      await video.save({ transaction: t })
 
       // Now we can federate the video (reload from database, we need more attributes)
       const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t)
@@ -192,10 +192,13 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
 
       logger.info('Video %s imported.', video.uuid)
 
-      videoImportUpdated.Video = videoUpdated
+      videoImportUpdated.Video = videoForFederation
       return videoImportUpdated
     })
 
+    Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video)
+    Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true)
+
     // Create transcoding jobs?
     if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) {
       // Put uuid because we don't have id auto incremented for now
@@ -218,6 +221,8 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide
     videoImport.state = VideoImportState.FAILED
     await videoImport.save()
 
+    Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)
+
     throw err
   }
 }
index 038ef43e2c7ea624aea5841c93e44567c44f39c1..fa1fd13b38da28b6080487b35f416120497fb976 100644 (file)
@@ -23,9 +23,7 @@ async function processVideosViews () {
   for (const videoId of videoIds) {
     try {
       const views = await Redis.Instance.getVideoViews(videoId, hour)
-      if (isNaN(views)) {
-        logger.error('Cannot process videos views of video %d in hour %d: views number is NaN (%s).', videoId, hour, views)
-      } else {
+      if (views) {
         logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour)
 
         try {
index 5862e178f81d8b18b4c10e373b2a4b54c290b256..ba9cbe0d99d64b9c0e3f10a579acfec291510180 100644 (file)
@@ -88,7 +88,6 @@ class JobQueue {
 
       queue.on('error', err => {
         logger.error('Error in job queue %s.', handlerName, { err })
-        process.exit(-1)
       })
 
       this.queues[handlerName] = queue
@@ -166,10 +165,10 @@ class JobQueue {
     return total
   }
 
-  removeOldJobs () {
+  async removeOldJobs () {
     for (const key of Object.keys(this.queues)) {
       const queue = this.queues[key]
-      queue.clean(JOB_COMPLETED_LIFETIME, 'completed')
+      await queue.clean(JOB_COMPLETED_LIFETIME, 'completed')
     }
   }
 
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts
new file mode 100644 (file)
index 0000000..d1b3313
--- /dev/null
@@ -0,0 +1,455 @@
+import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users'
+import { logger } from '../helpers/logger'
+import { VideoModel } from '../models/video/video'
+import { Emailer } from './emailer'
+import { UserNotificationModel } from '../models/account/user-notification'
+import { VideoCommentModel } from '../models/video/video-comment'
+import { UserModel } from '../models/account/user'
+import { PeerTubeSocket } from './peertube-socket'
+import { CONFIG } from '../initializers/constants'
+import { VideoPrivacy, VideoState } from '../../shared/models/videos'
+import { VideoAbuseModel } from '../models/video/video-abuse'
+import { VideoBlacklistModel } from '../models/video/video-blacklist'
+import * as Bluebird from 'bluebird'
+import { VideoImportModel } from '../models/video/video-import'
+import { AccountBlocklistModel } from '../models/account/account-blocklist'
+import { ActorFollowModel } from '../models/activitypub/actor-follow'
+import { AccountModel } from '../models/account/account'
+
+class Notifier {
+
+  private static instance: Notifier
+
+  private constructor () {}
+
+  notifyOnNewVideo (video: VideoModel): void {
+    // Only notify on public and published videos
+    if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED) return
+
+    this.notifySubscribersOfNewVideo(video)
+      .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err }))
+  }
+
+  notifyOnPendingVideoPublished (video: VideoModel): void {
+    // Only notify on public videos that has been published while the user waited transcoding/scheduled update
+    if (video.waitTranscoding === false && !video.ScheduleVideoUpdate) return
+
+    this.notifyOwnedVideoHasBeenPublished(video)
+        .catch(err => logger.error('Cannot notify owner that its video %s has been published.', video.url, { err }))
+  }
+
+  notifyOnNewComment (comment: VideoCommentModel): void {
+    this.notifyVideoOwnerOfNewComment(comment)
+        .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err }))
+
+    this.notifyOfCommentMention(comment)
+        .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err }))
+  }
+
+  notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void {
+    this.notifyModeratorsOfNewVideoAbuse(videoAbuse)
+      .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err }))
+  }
+
+  notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void {
+    this.notifyVideoOwnerOfBlacklist(videoBlacklist)
+      .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err }))
+  }
+
+  notifyOnVideoUnblacklist (video: VideoModel): void {
+    this.notifyVideoOwnerOfUnblacklist(video)
+        .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err }))
+  }
+
+  notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void {
+    this.notifyOwnerVideoImportIsFinished(videoImport, success)
+      .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err }))
+  }
+
+  notifyOnNewUserRegistration (user: UserModel): void {
+    this.notifyModeratorsOfNewUserRegistration(user)
+        .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err }))
+  }
+
+  notifyOfNewFollow (actorFollow: ActorFollowModel): void {
+    this.notifyUserOfNewActorFollow(actorFollow)
+      .catch(err => {
+        logger.error(
+          'Cannot notify owner of channel %s of a new follow by %s.',
+          actorFollow.ActorFollowing.VideoChannel.getDisplayName(),
+          actorFollow.ActorFollower.Account.getDisplayName(),
+          err
+        )
+      })
+  }
+
+  private async notifySubscribersOfNewVideo (video: VideoModel) {
+    // List all followers that are users
+    const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId)
+
+    logger.info('Notifying %d users of new video %s.', users.length, video.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.newVideoFromSubscription
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION,
+        userId: user.id,
+        videoId: video.id
+      })
+      notification.Video = video
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addNewVideoFromSubscriberNotification(emails, video)
+    }
+
+    return this.notify({ users, settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) {
+    if (comment.Video.isOwned() === false) return
+
+    const user = await UserModel.loadByVideoId(comment.videoId)
+
+    // Not our user or user comments its own video
+    if (!user || comment.Account.userId === user.id) return
+
+    const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, comment.accountId)
+    if (accountMuted) return
+
+    logger.info('Notifying user %s of new comment %s.', user.username, comment.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.newCommentOnMyVideo
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO,
+        userId: user.id,
+        commentId: comment.id
+      })
+      notification.Comment = comment
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addNewCommentOnMyVideoNotification(emails, comment)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyOfCommentMention (comment: VideoCommentModel) {
+    const usernames = comment.extractMentions()
+    let users = await UserModel.listByUsernames(usernames)
+
+    if (comment.Video.isOwned()) {
+      const userException = await UserModel.loadByVideoId(comment.videoId)
+      users = users.filter(u => u.id !== userException.id)
+    }
+
+    // Don't notify if I mentioned myself
+    users = users.filter(u => u.Account.id !== comment.accountId)
+
+    if (users.length === 0) return
+
+    const accountMutedHash = await AccountBlocklistModel.isAccountMutedByMulti(users.map(u => u.Account.id), comment.accountId)
+
+    logger.info('Notifying %d users of new comment %s.', users.length, comment.url)
+
+    function settingGetter (user: UserModel) {
+      if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE
+
+      return user.NotificationSetting.commentMention
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.COMMENT_MENTION,
+        userId: user.id,
+        commentId: comment.id
+      })
+      notification.Comment = comment
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addNewCommentMentionNotification(emails, comment)
+    }
+
+    return this.notify({ users, settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) {
+    if (actorFollow.ActorFollowing.isOwned() === false) return
+
+    // Account follows one of our account?
+    let followType: 'account' | 'channel' = 'channel'
+    let user = await UserModel.loadByChannelActorId(actorFollow.ActorFollowing.id)
+
+    // Account follows one of our channel?
+    if (!user) {
+      user = await UserModel.loadByAccountActorId(actorFollow.ActorFollowing.id)
+      followType = 'account'
+    }
+
+    if (!user) return
+
+    if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) {
+      actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel
+    }
+    const followerAccount = actorFollow.ActorFollower.Account
+
+    const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id)
+    if (accountMuted) return
+
+    logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName())
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.newFollow
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.NEW_FOLLOW,
+        userId: user.id,
+        actorFollowId: actorFollow.id
+      })
+      notification.ActorFollow = actorFollow
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addNewFollowNotification(emails, actorFollow, followType)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) {
+    const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES)
+    if (moderators.length === 0) return
+
+    logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.videoAbuseAsModerator
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS,
+        userId: user.id,
+        videoAbuseId: videoAbuse.id
+      })
+      notification.VideoAbuse = videoAbuse
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse)
+    }
+
+    return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) {
+    const user = await UserModel.loadByVideoId(videoBlacklist.videoId)
+    if (!user) return
+
+    logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.blacklistOnMyVideo
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.BLACKLIST_ON_MY_VIDEO,
+        userId: user.id,
+        videoBlacklistId: videoBlacklist.id
+      })
+      notification.VideoBlacklist = videoBlacklist
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addVideoBlacklistNotification(emails, videoBlacklist)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyVideoOwnerOfUnblacklist (video: VideoModel) {
+    const user = await UserModel.loadByVideoId(video.id)
+    if (!user) return
+
+    logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.blacklistOnMyVideo
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO,
+        userId: user.id,
+        videoId: video.id
+      })
+      notification.Video = video
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addVideoUnblacklistNotification(emails, video)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyOwnedVideoHasBeenPublished (video: VideoModel) {
+    const user = await UserModel.loadByVideoId(video.id)
+    if (!user) return
+
+    logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url)
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.myVideoPublished
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.MY_VIDEO_PUBLISHED,
+        userId: user.id,
+        videoId: video.id
+      })
+      notification.Video = video
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.myVideoPublishedNotification(emails, video)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) {
+    const user = await UserModel.loadByVideoImportId(videoImport.id)
+    if (!user) return
+
+    logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier())
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.myVideoImportFinished
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR,
+        userId: user.id,
+        videoImportId: videoImport.id
+      })
+      notification.VideoImport = videoImport
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return success
+        ? Emailer.Instance.myVideoImportSuccessNotification(emails, videoImport)
+        : Emailer.Instance.myVideoImportErrorNotification(emails, videoImport)
+    }
+
+    return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) {
+    const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS)
+    if (moderators.length === 0) return
+
+    logger.info(
+      'Notifying %s moderators of new user registration of %s.',
+      moderators.length, registeredUser.Account.Actor.preferredUsername
+    )
+
+    function settingGetter (user: UserModel) {
+      return user.NotificationSetting.newUserRegistration
+    }
+
+    async function notificationCreator (user: UserModel) {
+      const notification = await UserNotificationModel.create({
+        type: UserNotificationType.NEW_USER_REGISTRATION,
+        userId: user.id,
+        accountId: registeredUser.Account.id
+      })
+      notification.Account = registeredUser.Account
+
+      return notification
+    }
+
+    function emailSender (emails: string[]) {
+      return Emailer.Instance.addNewUserRegistrationNotification(emails, registeredUser)
+    }
+
+    return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender })
+  }
+
+  private async notify (options: {
+    users: UserModel[],
+    notificationCreator: (user: UserModel) => Promise<UserNotificationModel>,
+    emailSender: (emails: string[]) => Promise<any> | Bluebird<any>,
+    settingGetter: (user: UserModel) => UserNotificationSettingValue
+  }) {
+    const emails: string[] = []
+
+    for (const user of options.users) {
+      if (this.isWebNotificationEnabled(options.settingGetter(user))) {
+        const notification = await options.notificationCreator(user)
+
+        PeerTubeSocket.Instance.sendNotification(user.id, notification)
+      }
+
+      if (this.isEmailEnabled(user, options.settingGetter(user))) {
+        emails.push(user.email)
+      }
+    }
+
+    if (emails.length !== 0) {
+      await options.emailSender(emails)
+    }
+  }
+
+  private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) {
+    if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified !== true) return false
+
+    return value & UserNotificationSettingValue.EMAIL
+  }
+
+  private isWebNotificationEnabled (value: UserNotificationSettingValue) {
+    return value & UserNotificationSettingValue.WEB
+  }
+
+  static get Instance () {
+    return this.instance || (this.instance = new this())
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  Notifier
+}
index 5cbe60b82c626ca8148ee5545d2da7a05c90fcf3..2cd2ae97cf6c9978837f4ac054d4d968a64e205b 100644 (file)
@@ -1,3 +1,4 @@
+import * as Bluebird from 'bluebird'
 import { AccessDeniedError } from 'oauth2-server'
 import { logger } from '../helpers/logger'
 import { UserModel } from '../models/account/user'
@@ -37,7 +38,7 @@ function clearCacheByToken (token: string) {
 function getAccessToken (bearerToken: string) {
   logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
 
-  if (accessTokenCache[bearerToken] !== undefined) return accessTokenCache[bearerToken]
+  if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken])
 
   return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken)
     .then(tokenModel => {
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts
new file mode 100644 (file)
index 0000000..eb84ecd
--- /dev/null
@@ -0,0 +1,52 @@
+import * as SocketIO from 'socket.io'
+import { authenticateSocket } from '../middlewares'
+import { UserNotificationModel } from '../models/account/user-notification'
+import { logger } from '../helpers/logger'
+import { Server } from 'http'
+
+class PeerTubeSocket {
+
+  private static instance: PeerTubeSocket
+
+  private userNotificationSockets: { [ userId: number ]: SocketIO.Socket } = {}
+
+  private constructor () {}
+
+  init (server: Server) {
+    const io = SocketIO(server)
+
+    io.of('/user-notifications')
+      .use(authenticateSocket)
+      .on('connection', socket => {
+        const userId = socket.handshake.query.user.id
+
+        logger.debug('User %d connected on the notification system.', userId)
+
+        this.userNotificationSockets[userId] = socket
+
+        socket.on('disconnect', () => {
+          logger.debug('User %d disconnected from SocketIO notifications.', userId)
+
+          delete this.userNotificationSockets[userId]
+        })
+      })
+  }
+
+  sendNotification (userId: number, notification: UserNotificationModel) {
+    const socket = this.userNotificationSockets[userId]
+
+    if (!socket) return
+
+    socket.emit('new-notification', notification.toFormattedJSON())
+  }
+
+  static get Instance () {
+    return this.instance || (this.instance = new this())
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  PeerTubeSocket
+}
index abd75d5122d7162276fb245f7c2191ab952ad19b..3628c0583e3006dc91f30ba60fb076285cfe5fa1 100644 (file)
@@ -2,7 +2,13 @@ import * as express from 'express'
 import { createClient, RedisClient } from 'redis'
 import { logger } from '../helpers/logger'
 import { generateRandomString } from '../helpers/utils'
-import { CONFIG, USER_PASSWORD_RESET_LIFETIME, USER_EMAIL_VERIFY_LIFETIME, VIDEO_VIEW_LIFETIME } from '../initializers'
+import {
+  CONFIG,
+  CONTACT_FORM_LIFETIME,
+  USER_EMAIL_VERIFY_LIFETIME,
+  USER_PASSWORD_RESET_LIFETIME,
+  VIDEO_VIEW_LIFETIME
+} from '../initializers'
 
 type CachedRoute = {
   body: string,
@@ -76,6 +82,16 @@ class Redis {
     return this.getValue(this.generateVerifyEmailKey(userId))
   }
 
+  /************* Contact form per IP *************/
+
+  async setContactFormIp (ip: string) {
+    return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME)
+  }
+
+  async isContactFormIpExists (ip: string) {
+    return this.exists(this.generateContactFormKey(ip))
+  }
+
   /************* Views per IP *************/
 
   setIPVideoView (ip: string, videoUUID: string) {
@@ -121,7 +137,14 @@ class Redis {
     const key = this.generateVideoViewKey(videoId, hour)
 
     const valueString = await this.getValue(key)
-    return parseInt(valueString, 10)
+    const valueInt = parseInt(valueString, 10)
+
+    if (isNaN(valueInt)) {
+      logger.error('Cannot get videos views of video %d in hour %d: views number is NaN (%s).', videoId, hour, valueString)
+      return undefined
+    }
+
+    return valueInt
   }
 
   async getVideosIdViewed (hour: number) {
@@ -168,7 +191,11 @@ class Redis {
   }
 
   private generateViewKey (ip: string, videoUUID: string) {
-    return videoUUID + '-' + ip
+    return `views-${videoUUID}-${ip}`
+  }
+
+  private generateContactFormKey (ip: string) {
+    return 'contact-form-' + ip
   }
 
   /************* Redis helpers *************/
index b9d0a4d177b715a8fac48a6df7108afab49294db..86ea7aa38bd931ea04d917447ca02c0b989e011a 100644 (file)
@@ -1,8 +1,11 @@
+import { logger } from '../../helpers/logger'
+
 export abstract class AbstractScheduler {
 
   protected abstract schedulerIntervalMs: number
 
   private interval: NodeJS.Timer
+  private isRunning = false
 
   enable () {
     if (!this.schedulerIntervalMs) throw new Error('Interval is not correctly set.')
@@ -14,5 +17,18 @@ export abstract class AbstractScheduler {
     clearInterval(this.interval)
   }
 
-  abstract execute ()
+  async execute () {
+    if (this.isRunning === true) return
+    this.isRunning = true
+
+    try {
+      await this.internalExecute()
+    } catch (err) {
+      logger.error('Cannot execute %s scheduler.', this.constructor.name, { err })
+    } finally {
+      this.isRunning = false
+    }
+  }
+
+  protected abstract internalExecute (): Promise<any>
 }
similarity index 51%
rename from server/lib/schedulers/bad-actor-follow-scheduler.ts
rename to server/lib/schedulers/actor-follow-scheduler.ts
index 617149aaf363a86dd2faf3dd30d79e51b46d3da7..3967be7f8f41981c7fac75b72e3c6df8a31b7957 100644 (file)
@@ -3,18 +3,35 @@ import { logger } from '../../helpers/logger'
 import { ActorFollowModel } from '../../models/activitypub/actor-follow'
 import { AbstractScheduler } from './abstract-scheduler'
 import { SCHEDULER_INTERVALS_MS } from '../../initializers'
+import { ActorFollowScoreCache } from '../cache'
 
-export class BadActorFollowScheduler extends AbstractScheduler {
+export class ActorFollowScheduler extends AbstractScheduler {
 
   private static instance: AbstractScheduler
 
-  protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.badActorFollow
+  protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.actorFollowScores
 
   private constructor () {
     super()
   }
 
-  async execute () {
+  protected async internalExecute () {
+    await this.processPendingScores()
+
+    await this.removeBadActorFollows()
+  }
+
+  private async processPendingScores () {
+    const pendingScores = ActorFollowScoreCache.Instance.getPendingFollowsScoreCopy()
+
+    ActorFollowScoreCache.Instance.clearPendingFollowsScore()
+
+    for (const inbox of Object.keys(pendingScores)) {
+      await ActorFollowModel.updateFollowScore(inbox, pendingScores[inbox])
+    }
+  }
+
+  private async removeBadActorFollows () {
     if (!isTestInstance()) logger.info('Removing bad actor follows (scheduler).')
 
     try {
index a29a6b80091bf1bc811acf2636f17c2e301a91fb..4a4341ba981250938aa93bd84ce81314a617735f 100644 (file)
@@ -14,10 +14,10 @@ export class RemoveOldJobsScheduler extends AbstractScheduler {
     super()
   }
 
-  async execute () {
-    if (!isTestInstance()) logger.info('Removing old jobs (scheduler).')
+  protected internalExecute () {
+    if (!isTestInstance()) logger.info('Removing old jobs in scheduler.')
 
-    JobQueue.Instance.removeOldJobs()
+    return JobQueue.Instance.removeOldJobs()
   }
 
   static get Instance () {
index fd2edfd1702007665ce8cf03279db14194547c30..2618a5857d24aa77a0c50e6c02693fb3513f4dc3 100644 (file)
@@ -5,6 +5,8 @@ import { retryTransactionWrapper } from '../../helpers/database-utils'
 import { federateVideoIfNeeded } from '../activitypub'
 import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers'
 import { VideoPrivacy } from '../../../shared/models/videos'
+import { Notifier } from '../notifier'
+import { VideoModel } from '../../models/video/video'
 
 export class UpdateVideosScheduler extends AbstractScheduler {
 
@@ -12,30 +14,20 @@ export class UpdateVideosScheduler extends AbstractScheduler {
 
   protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.updateVideos
 
-  private isRunning = false
-
   private constructor () {
     super()
   }
 
-  async execute () {
-    if (this.isRunning === true) return
-    this.isRunning = true
-
-    try {
-      await retryTransactionWrapper(this.updateVideos.bind(this))
-    } catch (err) {
-      logger.error('Cannot execute update videos scheduler.', { err })
-    } finally {
-      this.isRunning = false
-    }
+  protected async internalExecute () {
+    return retryTransactionWrapper(this.updateVideos.bind(this))
   }
 
   private async updateVideos () {
     if (!await ScheduleVideoUpdateModel.areVideosToUpdate()) return undefined
 
-    return sequelizeTypescript.transaction(async t => {
+    const publishedVideos = await sequelizeTypescript.transaction(async t => {
       const schedules = await ScheduleVideoUpdateModel.listVideosToUpdate(t)
+      const publishedVideos: VideoModel[] = []
 
       for (const schedule of schedules) {
         const video = schedule.Video
@@ -50,11 +42,23 @@ export class UpdateVideosScheduler extends AbstractScheduler {
 
           await video.save({ transaction: t })
           await federateVideoIfNeeded(video, isNewVideo, t)
+
+          if (oldPrivacy === VideoPrivacy.UNLISTED || oldPrivacy === VideoPrivacy.PRIVATE) {
+            video.ScheduleVideoUpdate = schedule
+            publishedVideos.push(video)
+          }
         }
 
         await schedule.destroy({ transaction: t })
       }
+
+      return publishedVideos
     })
+
+    for (const v of publishedVideos) {
+      Notifier.Instance.notifyOnNewVideo(v)
+      Notifier.Instance.notifyOnPendingVideoPublished(v)
+    }
   }
 
   static get Instance () {
index 8b7f335398acdf9b419a9d5fcd847cffbbb07966..f643ee2268d38969e2403614949cd29a6b39eb42 100644 (file)
@@ -6,7 +6,7 @@ import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
 import { VideoFileModel } from '../../models/video/video-file'
 import { downloadWebTorrentVideo } from '../../helpers/webtorrent'
 import { join } from 'path'
-import { rename } from 'fs-extra'
+import { move } from 'fs-extra'
 import { getServerActor } from '../../helpers/utils'
 import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send'
 import { getVideoCacheFileActivityPubUrl } from '../activitypub/url'
@@ -16,7 +16,6 @@ import { getOrCreateVideoAndAccountAndChannel } from '../activitypub'
 export class VideosRedundancyScheduler extends AbstractScheduler {
 
   private static instance: AbstractScheduler
-  private executing = false
 
   protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL
 
@@ -24,11 +23,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     super()
   }
 
-  async execute () {
-    if (this.executing) return
-
-    this.executing = true
-
+  protected async internalExecute () {
     for (const obj of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) {
       logger.info('Running redundancy scheduler for strategy %s.', obj.strategy)
 
@@ -57,8 +52,6 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
     await this.extendsLocalExpiration()
 
     await this.purgeRemoteExpired()
-
-    this.executing = false
   }
 
   static get Instance () {
@@ -145,13 +138,13 @@ export class VideosRedundancyScheduler extends AbstractScheduler {
 
       const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT)
 
-      const destPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(file))
-      await rename(tmpPath, destPath)
+      const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file))
+      await move(tmpPath, destPath)
 
       const createdModel = await VideoRedundancyModel.create({
         expiresOn: this.buildNewExpiration(redundancy.minLifetime),
         url: getVideoCacheFileActivityPubUrl(file),
-        fileUrl: video.getVideoFileUrl(file, CONFIG.WEBSERVER.URL),
+        fileUrl: video.getVideoRedundancyUrl(file, CONFIG.WEBSERVER.URL),
         strategy: redundancy.strategy,
         videoFileId: file.id,
         actorId: serverActor.id
index 461cd045ef1256bc9f83a36c334c6d8774ac1085..aa027116d7c93b18fd0573538fcd36c1f9db4402 100644 (file)
@@ -12,7 +12,7 @@ export class YoutubeDlUpdateScheduler extends AbstractScheduler {
     super()
   }
 
-  execute () {
+  protected internalExecute () {
     return updateYoutubeDLBinary()
   }
 
index 29d6d087d1bbde1821815a863f738f0de793d8be..a39ef6c3d98c69c7d0edd7903d142180dcb486ff 100644 (file)
@@ -9,6 +9,8 @@ import { createVideoChannel } from './video-channel'
 import { VideoChannelModel } from '../models/video/video-channel'
 import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
 import { ActorModel } from '../models/activitypub/actor'
+import { UserNotificationSettingModel } from '../models/account/user-notification-setting'
+import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users'
 
 async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) {
   const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => {
@@ -18,6 +20,8 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse
     }
 
     const userCreated = await userToCreate.save(userOptions)
+    userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t)
+
     const accountCreated = await createLocalAccountWithoutKeys(userCreated.username, userCreated.id, null, t)
     userCreated.Account = accountCreated
 
@@ -88,3 +92,22 @@ export {
   createUserAccountAndChannel,
   createLocalAccountWithoutKeys
 }
+
+// ---------------------------------------------------------------------------
+
+function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Transaction | undefined) {
+  const values: UserNotificationSetting & { userId: number } = {
+    userId: user.id,
+    newVideoFromSubscription: UserNotificationSettingValue.WEB,
+    newCommentOnMyVideo: UserNotificationSettingValue.WEB,
+    myVideoImportFinished: UserNotificationSettingValue.WEB,
+    myVideoPublished: UserNotificationSettingValue.WEB,
+    videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    newUserRegistration: UserNotificationSettingValue.WEB,
+    commentMention: UserNotificationSettingValue.WEB,
+    newFollow: UserNotificationSettingValue.WEB
+  }
+
+  return UserNotificationSettingModel.create(values, { transaction: t })
+}
index a78de61e5673052955b8da1d7c48f36441a8b4f8..4460f46e418d5809fbda6ecdf1ce8db112254908 100644 (file)
@@ -1,7 +1,7 @@
 import { CONFIG } from '../initializers'
 import { extname, join } from 'path'
 import { getVideoFileFPS, getVideoFileResolution, transcode } from '../helpers/ffmpeg-utils'
-import { copy, remove, rename, stat } from 'fs-extra'
+import { copy, remove, move, stat } from 'fs-extra'
 import { logger } from '../helpers/logger'
 import { VideoResolution } from '../../shared/models/videos'
 import { VideoFileModel } from '../models/video/video-file'
@@ -30,7 +30,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi
     inputVideoFile.set('extname', newExtname)
 
     const videoOutputPath = video.getVideoFilePath(inputVideoFile)
-    await rename(videoTranscodedPath, videoOutputPath)
+    await move(videoTranscodedPath, videoOutputPath)
     const stats = await stat(videoOutputPath)
     const fps = await getVideoFileFPS(videoOutputPath)
 
diff --git a/server/middlewares/csp.ts b/server/middlewares/csp.ts
new file mode 100644 (file)
index 0000000..8b919af
--- /dev/null
@@ -0,0 +1,44 @@
+import * as helmet from 'helmet'
+import { CONFIG } from '../initializers/constants'
+
+const baseDirectives = Object.assign({},
+  {
+    defaultSrc: ["'none'"], // by default, not specifying default-src = '*'
+    connectSrc: ['*', 'data:'],
+    mediaSrc: ["'self'", 'https:', 'blob:'],
+    fontSrc: ["'self'", 'data:'],
+    imgSrc: ["'self'", 'data:'],
+    scriptSrc: ["'self' 'unsafe-inline' 'unsafe-eval'"],
+    styleSrc: ["'self' 'unsafe-inline'"],
+    objectSrc: ["'none'"], // only define to allow plugins, else let defaultSrc 'none' block it
+    formAction: ["'self'"],
+    frameAncestors: ["'none'"],
+    baseUri: ["'self'"],
+    manifestSrc: ["'self'"],
+    frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed
+    workerSrc: ["'self'"] // instead of deprecated child-src
+  },
+  CONFIG.SERVICES['CSP-LOGGER'] ? { reportUri: CONFIG.SERVICES['CSP-LOGGER'] } : {},
+  CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {}
+)
+
+const baseCSP = helmet.contentSecurityPolicy({
+  directives: baseDirectives,
+  browserSniff: false,
+  reportOnly: true
+})
+
+const embedCSP = helmet.contentSecurityPolicy({
+  directives: Object.assign(baseDirectives, {
+    frameAncestors: ['*']
+  }),
+  browserSniff: false, // assumes a modern browser, but allows CDN in front
+  reportOnly: true
+})
+
+// ---------------------------------------------------------------------------
+
+export {
+  baseCSP,
+  embedCSP
+}
index cabad39c6cce5f2c696f0e80a882b93e085713ed..607def85552bb78d60ad5adde5c2f29a3136afda 100644 (file)
@@ -10,4 +10,4 @@ const advertiseDoNotTrack = (_, res, next) => {
 
 export {
   advertiseDoNotTrack
- }
+}
index 0cef26953d8a4557a1da5a4fdb03c43fa03f36a2..b758a8586b19fb89a3adf9697260ee76eb2c7048 100644 (file)
@@ -6,3 +6,5 @@ export * from './pagination'
 export * from './servers'
 export * from './sort'
 export * from './user-right'
+export * from './dnt'
+export * from './csp'
index 8c1df2c3eaa2688f5edaa302aa49c5b9154d3b48..1d193d467455b675f5a4d4e117555ff56649b17b 100644 (file)
@@ -3,6 +3,8 @@ import * as OAuthServer from 'express-oauth-server'
 import 'express-validator'
 import { OAUTH_LIFETIME } from '../initializers'
 import { logger } from '../helpers/logger'
+import { Socket } from 'socket.io'
+import { getAccessToken } from '../lib/oauth-model'
 
 const oAuthServer = new OAuthServer({
   useErrorHandler: true,
@@ -28,6 +30,25 @@ function authenticate (req: express.Request, res: express.Response, next: expres
   })
 }
 
+function authenticateSocket (socket: Socket, next: (err?: any) => void) {
+  const accessToken = socket.handshake.query.accessToken
+
+  logger.debug('Checking socket access token %s.', accessToken)
+
+  getAccessToken(accessToken)
+    .then(tokenDB => {
+      const now = new Date()
+
+      if (!tokenDB || tokenDB.accessTokenExpiresAt < now || tokenDB.refreshTokenExpiresAt < now) {
+        return next(new Error('Invalid access token.'))
+      }
+
+      socket.handshake.query.user = tokenDB.User
+
+      return next()
+    })
+}
+
 function authenticatePromiseIfNeeded (req: express.Request, res: express.Response) {
   return new Promise(resolve => {
     // Already authenticated? (or tried to)
@@ -68,6 +89,7 @@ function token (req: express.Request, res: express.Response, next: express.NextF
 
 export {
   authenticate,
+  authenticateSocket,
   authenticatePromiseIfNeeded,
   optionalAuthenticate,
   token
index f3f257d573dbefb87457eefbb80efba0e2d1f8b4..90108fa825d9cb57070d3248b31bd92fc4501186 100644 (file)
@@ -1,29 +1,44 @@
 import * as express from 'express'
 import { body } from 'express-validator/check'
-import { isUserNSFWPolicyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users'
+import { isUserNSFWPolicyValid, isUserVideoQuotaValid, isUserVideoQuotaDailyValid } from '../../helpers/custom-validators/users'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
 
 const customConfigUpdateValidator = [
   body('instance.name').exists().withMessage('Should have a valid instance name'),
+  body('instance.shortDescription').exists().withMessage('Should have a valid instance short description'),
   body('instance.description').exists().withMessage('Should have a valid instance description'),
   body('instance.terms').exists().withMessage('Should have a valid instance terms'),
   body('instance.defaultClientRoute').exists().withMessage('Should have a valid instance default client route'),
   body('instance.defaultNSFWPolicy').custom(isUserNSFWPolicyValid).withMessage('Should have a valid NSFW policy'),
   body('instance.customizations.css').exists().withMessage('Should have a valid instance CSS customization'),
   body('instance.customizations.javascript').exists().withMessage('Should have a valid instance JavaScript customization'),
-  body('cache.previews.size').isInt().withMessage('Should have a valid previews size'),
+
+  body('services.twitter.username').exists().withMessage('Should have a valid twitter username'),
+  body('services.twitter.whitelisted').isBoolean().withMessage('Should have a valid twitter whitelisted boolean'),
+
+  body('cache.previews.size').isInt().withMessage('Should have a valid previews cache size'),
+  body('cache.captions.size').isInt().withMessage('Should have a valid captions cache size'),
+
   body('signup.enabled').isBoolean().withMessage('Should have a valid signup enabled boolean'),
   body('signup.limit').isInt().withMessage('Should have a valid signup limit'),
+  body('signup.requiresEmailVerification').isBoolean().withMessage('Should have a valid requiresEmailVerification boolean'),
+
   body('admin.email').isEmail().withMessage('Should have a valid administrator email'),
+  body('contactForm.enabled').isBoolean().withMessage('Should have a valid contact form enabled boolean'),
+
   body('user.videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid video quota'),
+  body('user.videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily video quota'),
+
   body('transcoding.enabled').isBoolean().withMessage('Should have a valid transcoding enabled boolean'),
+  body('transcoding.allowAdditionalExtensions').isBoolean().withMessage('Should have a valid additional extensions boolean'),
   body('transcoding.threads').isInt().withMessage('Should have a valid transcoding threads number'),
   body('transcoding.resolutions.240p').isBoolean().withMessage('Should have a valid transcoding 240p resolution enabled boolean'),
   body('transcoding.resolutions.360p').isBoolean().withMessage('Should have a valid transcoding 360p resolution enabled boolean'),
   body('transcoding.resolutions.480p').isBoolean().withMessage('Should have a valid transcoding 480p resolution enabled boolean'),
   body('transcoding.resolutions.720p').isBoolean().withMessage('Should have a valid transcoding 720p resolution enabled boolean'),
   body('transcoding.resolutions.1080p').isBoolean().withMessage('Should have a valid transcoding 1080p resolution enabled boolean'),
+
   body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'),
   body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'),
 
index 46c7f0f3ab876b467e41d12d66374b8f1b4c4eda..65dd00335ed7d6762c9ba28131fdd7e790e93526 100644 (file)
@@ -12,3 +12,4 @@ export * from './videos'
 export * from './webfinger'
 export * from './search'
 export * from './server'
+export * from './user-history'
index a491dfeb33e62e50d746467d9291a6e0afda984b..d85afc2ffee8875356422d7ad5ae5ec8b0469b60 100644 (file)
@@ -1,9 +1,13 @@
 import * as express from 'express'
 import { logger } from '../../helpers/logger'
 import { areValidationErrors } from './utils'
-import { isHostValid } from '../../helpers/custom-validators/servers'
+import { isHostValid, isValidContactBody } from '../../helpers/custom-validators/servers'
 import { ServerModel } from '../../models/server/server'
 import { body } from 'express-validator/check'
+import { isUserDisplayNameValid } from '../../helpers/custom-validators/users'
+import { Emailer } from '../../lib/emailer'
+import { Redis } from '../../lib/redis'
+import { CONFIG } from '../../initializers/constants'
 
 const serverGetValidator = [
   body('host').custom(isHostValid).withMessage('Should have a valid host'),
@@ -26,8 +30,49 @@ const serverGetValidator = [
   }
 ]
 
+const contactAdministratorValidator = [
+  body('fromName')
+    .custom(isUserDisplayNameValid).withMessage('Should have a valid name'),
+  body('fromEmail')
+    .isEmail().withMessage('Should have a valid email'),
+  body('body')
+    .custom(isValidContactBody).withMessage('Should have a valid body'),
+
+  async (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking contactAdministratorValidator parameters', { parameters: req.body })
+
+    if (areValidationErrors(req, res)) return
+
+    if (CONFIG.CONTACT_FORM.ENABLED === false) {
+      return res
+        .status(409)
+        .send({ error: 'Contact form is not enabled on this instance.' })
+        .end()
+    }
+
+    if (Emailer.isEnabled() === false) {
+      return res
+        .status(409)
+        .send({ error: 'Emailer is not enabled on this instance.' })
+        .end()
+    }
+
+    if (await Redis.Instance.isContactFormIpExists(req.ip)) {
+      logger.info('Refusing a contact form by %s: already sent one recently.', req.ip)
+
+      return res
+        .status(403)
+        .send({ error: 'You already sent a contact form recently.' })
+        .end()
+    }
+
+    return next()
+  }
+]
+
 // ---------------------------------------------------------------------------
 
 export {
-  serverGetValidator
+  serverGetValidator,
+  contactAdministratorValidator
 }
index 4c0577d8f6d84255ff013ad4c97c4d5b8d1d4985..5ceda845fb263bfb6058f9210056f5fcc3c9f41a 100644 (file)
@@ -18,6 +18,7 @@ const SORTABLE_FOLLOWING_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOW
 const SORTABLE_USER_SUBSCRIPTIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_SUBSCRIPTIONS)
 const SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST)
 const SORTABLE_SERVERS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.SERVERS_BLOCKLIST)
+const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_NOTIFICATIONS)
 
 const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS)
 const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS)
@@ -35,6 +36,7 @@ const followingSortValidator = checkSort(SORTABLE_FOLLOWING_COLUMNS)
 const userSubscriptionsSortValidator = checkSort(SORTABLE_USER_SUBSCRIPTIONS_COLUMNS)
 const accountsBlocklistSortValidator = checkSort(SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS)
 const serversBlocklistSortValidator = checkSort(SORTABLE_SERVERS_BLOCKLIST_COLUMNS)
+const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COLUMNS)
 
 // ---------------------------------------------------------------------------
 
@@ -54,5 +56,6 @@ export {
   userSubscriptionsSortValidator,
   videoChannelsSearchSortValidator,
   accountsBlocklistSortValidator,
-  serversBlocklistSortValidator
+  serversBlocklistSortValidator,
+  userNotificationsSortValidator
 }
diff --git a/server/middlewares/validators/user-history.ts b/server/middlewares/validators/user-history.ts
new file mode 100644 (file)
index 0000000..418313d
--- /dev/null
@@ -0,0 +1,26 @@
+import * as express from 'express'
+import 'express-validator'
+import { body } from 'express-validator/check'
+import { logger } from '../../helpers/logger'
+import { areValidationErrors } from './utils'
+import { isDateValid } from '../../helpers/custom-validators/misc'
+
+const userHistoryRemoveValidator = [
+  body('beforeDate')
+    .optional()
+    .custom(isDateValid).withMessage('Should have a valid before date'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking userHistoryRemoveValidator parameters', { parameters: req.body })
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+  userHistoryRemoveValidator
+}
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts
new file mode 100644 (file)
index 0000000..46486e0
--- /dev/null
@@ -0,0 +1,63 @@
+import * as express from 'express'
+import 'express-validator'
+import { body, query } from 'express-validator/check'
+import { logger } from '../../helpers/logger'
+import { areValidationErrors } from './utils'
+import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
+import { isNotEmptyIntArray } from '../../helpers/custom-validators/misc'
+
+const listUserNotificationsValidator = [
+  query('unread')
+    .optional()
+    .toBoolean()
+    .isBoolean().withMessage('Should have a valid unread boolean'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking listUserNotificationsValidator parameters', { parameters: req.query })
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
+const updateNotificationSettingsValidator = [
+  body('newVideoFromSubscription')
+    .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video from subscription notification setting'),
+  body('newCommentOnMyVideo')
+    .custom(isUserNotificationSettingValid).withMessage('Should have a valid new comment on my video notification setting'),
+  body('videoAbuseAsModerator')
+    .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video abuse as moderator notification setting'),
+  body('blacklistOnMyVideo')
+    .custom(isUserNotificationSettingValid).withMessage('Should have a valid new blacklist on my video notification setting'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body })
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
+const markAsReadUserNotificationsValidator = [
+  body('ids')
+    .optional()
+    .custom(isNotEmptyIntArray).withMessage('Should have a valid notification ids to mark as read'),
+
+  (req: express.Request, res: express.Response, next: express.NextFunction) => {
+    logger.debug('Checking markAsReadUserNotificationsValidator parameters', { parameters: req.body })
+
+    if (areValidationErrors(req, res)) return
+
+    return next()
+  }
+]
+
+// ---------------------------------------------------------------------------
+
+export {
+  listUserNotificationsValidator,
+  updateNotificationSettingsValidator,
+  markAsReadUserNotificationsValidator
+}
index ccaf2eeb6f24bfe59f809ea77d3a0220f7fa7d64..1bb0bfb1bc8c11695d28e7604a8fd4c67bdda6d5 100644 (file)
@@ -5,15 +5,16 @@ import { body, param } from 'express-validator/check'
 import { omit } from 'lodash'
 import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
 import {
-  isUserAutoPlayVideoValid, isUserBlockedReasonValid,
+  isUserAutoPlayVideoValid,
+  isUserBlockedReasonValid,
   isUserDescriptionValid,
   isUserDisplayNameValid,
   isUserNSFWPolicyValid,
   isUserPasswordValid,
   isUserRoleValid,
   isUserUsernameValid,
-  isUserVideoQuotaValid,
-  isUserVideoQuotaDailyValid
+  isUserVideoQuotaDailyValid,
+  isUserVideoQuotaValid, isUserVideosHistoryEnabledValid
 } from '../../helpers/custom-validators/users'
 import { isVideoExist } from '../../helpers/custom-validators/videos'
 import { logger } from '../../helpers/logger'
@@ -22,7 +23,6 @@ import { Redis } from '../../lib/redis'
 import { UserModel } from '../../models/account/user'
 import { areValidationErrors } from './utils'
 import { ActorModel } from '../../models/activitypub/actor'
-import { comparePassword } from '../../helpers/peertube-crypto'
 
 const usersAddValidator = [
   body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'),
@@ -144,6 +144,9 @@ const usersUpdateMeValidator = [
   body('email').optional().isEmail().withMessage('Should have a valid email attribute'),
   body('nsfwPolicy').optional().custom(isUserNSFWPolicyValid).withMessage('Should have a valid display Not Safe For Work policy'),
   body('autoPlayVideo').optional().custom(isUserAutoPlayVideoValid).withMessage('Should have a valid automatically plays video attribute'),
+  body('videosHistoryEnabled')
+    .optional()
+    .custom(isUserVideosHistoryEnabledValid).withMessage('Should have a valid videos history enabled attribute'),
 
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') })
index 13da7acff8cbcfc294f544ca44b798f9e2914f79..2688f63ae816e2b67632038069761fcc96b8622b 100644 (file)
@@ -1,10 +1,11 @@
 import * as express from 'express'
 import { body, param } from 'express-validator/check'
-import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
+import { isBooleanValid, isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
 import { isVideoExist } from '../../../helpers/custom-validators/videos'
 import { logger } from '../../../helpers/logger'
 import { areValidationErrors } from '../utils'
 import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../../helpers/custom-validators/video-blacklist'
+import { VideoModel } from '../../../models/video/video'
 
 const videosBlacklistRemoveValidator = [
   param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
@@ -22,6 +23,10 @@ const videosBlacklistRemoveValidator = [
 
 const videosBlacklistAddValidator = [
   param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'),
+  body('unfederate')
+    .optional()
+    .toBoolean()
+    .custom(isBooleanValid).withMessage('Should have a valid unfederate boolean'),
   body('reason')
     .optional()
     .custom(isVideoBlacklistReasonValid).withMessage('Should have a valid reason'),
@@ -32,6 +37,14 @@ const videosBlacklistAddValidator = [
     if (areValidationErrors(req, res)) return
     if (!await isVideoExist(req.params.videoId, res)) return
 
+    const video: VideoModel = res.locals.video
+    if (req.body.unfederate === true && video.remote === true) {
+      return res
+        .status(409)
+        .send({ error: 'You cannot unfederate a remote video.' })
+        .end()
+    }
+
     return next()
   }
 ]
index bca64662fbf9a0814a0af1dabd462915efcaf43a..c38ad8a10d284d18a1e9c3eb288007530ff6fe4f 100644 (file)
@@ -4,6 +4,7 @@ import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc'
 import { isVideoExist } from '../../../helpers/custom-validators/videos'
 import { areValidationErrors } from '../utils'
 import { logger } from '../../../helpers/logger'
+import { UserModel } from '../../../models/account/user'
 
 const videoWatchingValidator = [
   param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'),
@@ -17,6 +18,12 @@ const videoWatchingValidator = [
     if (areValidationErrors(req, res)) return
     if (!await isVideoExist(req.params.videoId, res, 'id')) return
 
+    const user = res.locals.oauth.token.User as UserModel
+    if (user.videosHistoryEnabled === false) {
+      logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id)
+      return res.status(409).end()
+    }
+
     return next()
   }
 ]
index fa281923547be9764d1395d474a25c0b13fe9666..efd6ed59e5a3c1a0b33b713fd51bf44818bd0fab 100644 (file)
@@ -2,6 +2,7 @@ import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, Updated
 import { AccountModel } from './account'
 import { getSort } from '../utils'
 import { AccountBlock } from '../../../shared/models/blocklist'
+import { Op } from 'sequelize'
 
 enum ScopeNames {
   WITH_ACCOUNTS = 'WITH_ACCOUNTS'
@@ -72,6 +73,36 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> {
   })
   BlockedAccount: AccountModel
 
+  static isAccountMutedBy (accountId: number, targetAccountId: number) {
+    return AccountBlocklistModel.isAccountMutedByMulti([ accountId ], targetAccountId)
+      .then(result => result[accountId])
+  }
+
+  static isAccountMutedByMulti (accountIds: number[], targetAccountId: number) {
+    const query = {
+      attributes: [ 'accountId', 'id' ],
+      where: {
+        accountId: {
+          [Op.any]: accountIds
+        },
+        targetAccountId
+      },
+      raw: true
+    }
+
+    return AccountBlocklistModel.unscoped()
+                                .findAll(query)
+                                .then(rows => {
+                                  const result: { [accountId: number]: boolean } = {}
+
+                                  for (const accountId of accountIds) {
+                                    result[accountId] = !!rows.find(r => r.accountId === accountId)
+                                  }
+
+                                  return result
+                                })
+  }
+
   static loadByAccountAndTarget (accountId: number, targetAccountId: number) {
     const query = {
       where: {
index 5a237d733a0efd06c6fa98ed258c323e7b8c9058..84ef0b30d7a96aacd2b21b275e6aada2038858ea 100644 (file)
@@ -241,6 +241,27 @@ export class AccountModel extends Model<AccountModel> {
       })
   }
 
+  static listLocalsForSitemap (sort: string) {
+    const query = {
+      attributes: [ ],
+      offset: 0,
+      order: getSort(sort),
+      include: [
+        {
+          attributes: [ 'preferredUsername', 'serverId' ],
+          model: ActorModel.unscoped(),
+          where: {
+            serverId: null
+          }
+        }
+      ]
+    }
+
+    return AccountModel
+      .unscoped()
+      .findAll(query)
+  }
+
   toFormattedJSON (): Account {
     const actor = this.Actor.toFormattedJSON()
     const account = {
@@ -267,6 +288,10 @@ export class AccountModel extends Model<AccountModel> {
     return this.Actor.isOwned()
   }
 
+  isOutdated () {
+    return this.Actor.isOutdated()
+  }
+
   getDisplayName () {
     return this.name
   }
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts
new file mode 100644 (file)
index 0000000..f1c3ac2
--- /dev/null
@@ -0,0 +1,150 @@
+import {
+  AfterDestroy,
+  AfterUpdate,
+  AllowNull,
+  BelongsTo,
+  Column,
+  CreatedAt,
+  Default,
+  ForeignKey,
+  Is,
+  Model,
+  Table,
+  UpdatedAt
+} from 'sequelize-typescript'
+import { throwIfNotValid } from '../utils'
+import { UserModel } from './user'
+import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
+import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
+import { clearCacheByUserId } from '../../lib/oauth-model'
+
+@Table({
+  tableName: 'userNotificationSetting',
+  indexes: [
+    {
+      fields: [ 'userId' ],
+      unique: true
+    }
+  ]
+})
+export class UserNotificationSettingModel extends Model<UserNotificationSettingModel> {
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingNewVideoFromSubscription',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'newVideoFromSubscription')
+  )
+  @Column
+  newVideoFromSubscription: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingNewCommentOnMyVideo',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'newCommentOnMyVideo')
+  )
+  @Column
+  newCommentOnMyVideo: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingVideoAbuseAsModerator',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'videoAbuseAsModerator')
+  )
+  @Column
+  videoAbuseAsModerator: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingBlacklistOnMyVideo',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'blacklistOnMyVideo')
+  )
+  @Column
+  blacklistOnMyVideo: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingMyVideoPublished',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoPublished')
+  )
+  @Column
+  myVideoPublished: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingMyVideoImportFinished',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'myVideoImportFinished')
+  )
+  @Column
+  myVideoImportFinished: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingNewUserRegistration',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'newUserRegistration')
+  )
+  @Column
+  newUserRegistration: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingNewFollow',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow')
+  )
+  @Column
+  newFollow: UserNotificationSettingValue
+
+  @AllowNull(false)
+  @Default(null)
+  @Is(
+    'UserNotificationSettingCommentMention',
+    value => throwIfNotValid(value, isUserNotificationSettingValid, 'commentMention')
+  )
+  @Column
+  commentMention: UserNotificationSettingValue
+
+  @ForeignKey(() => UserModel)
+  @Column
+  userId: number
+
+  @BelongsTo(() => UserModel, {
+    foreignKey: {
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+  User: UserModel
+
+  @CreatedAt
+  createdAt: Date
+
+  @UpdatedAt
+  updatedAt: Date
+
+  @AfterUpdate
+  @AfterDestroy
+  static removeTokenCache (instance: UserNotificationSettingModel) {
+    return clearCacheByUserId(instance.userId)
+  }
+
+  toFormattedJSON (): UserNotificationSetting {
+    return {
+      newCommentOnMyVideo: this.newCommentOnMyVideo,
+      newVideoFromSubscription: this.newVideoFromSubscription,
+      videoAbuseAsModerator: this.videoAbuseAsModerator,
+      blacklistOnMyVideo: this.blacklistOnMyVideo,
+      myVideoPublished: this.myVideoPublished,
+      myVideoImportFinished: this.myVideoImportFinished,
+      newUserRegistration: this.newUserRegistration,
+      commentMention: this.commentMention,
+      newFollow: this.newFollow
+    }
+  }
+}
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts
new file mode 100644 (file)
index 0000000..6cdbb82
--- /dev/null
@@ -0,0 +1,472 @@
+import {
+  AllowNull,
+  BelongsTo,
+  Column,
+  CreatedAt,
+  Default,
+  ForeignKey,
+  IFindOptions,
+  Is,
+  Model,
+  Scopes,
+  Table,
+  UpdatedAt
+} from 'sequelize-typescript'
+import { UserNotification, UserNotificationType } from '../../../shared'
+import { getSort, throwIfNotValid } from '../utils'
+import { isBooleanValid } from '../../helpers/custom-validators/misc'
+import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
+import { UserModel } from './user'
+import { VideoModel } from '../video/video'
+import { VideoCommentModel } from '../video/video-comment'
+import { Op } from 'sequelize'
+import { VideoChannelModel } from '../video/video-channel'
+import { AccountModel } from './account'
+import { VideoAbuseModel } from '../video/video-abuse'
+import { VideoBlacklistModel } from '../video/video-blacklist'
+import { VideoImportModel } from '../video/video-import'
+import { ActorModel } from '../activitypub/actor'
+import { ActorFollowModel } from '../activitypub/actor-follow'
+import { AvatarModel } from '../avatar/avatar'
+import { ServerModel } from '../server/server'
+
+enum ScopeNames {
+  WITH_ALL = 'WITH_ALL'
+}
+
+function buildActorWithAvatarInclude () {
+  return {
+    attributes: [ 'preferredUsername' ],
+    model: () => ActorModel.unscoped(),
+    required: true,
+    include: [
+      {
+        attributes: [ 'filename' ],
+        model: () => AvatarModel.unscoped(),
+        required: false
+      },
+      {
+        attributes: [ 'host' ],
+        model: () => ServerModel.unscoped(),
+        required: false
+      }
+    ]
+  }
+}
+
+function buildVideoInclude (required: boolean) {
+  return {
+    attributes: [ 'id', 'uuid', 'name' ],
+    model: () => VideoModel.unscoped(),
+    required
+  }
+}
+
+function buildChannelInclude (required: boolean, withActor = false) {
+  return {
+    required,
+    attributes: [ 'id', 'name' ],
+    model: () => VideoChannelModel.unscoped(),
+    include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
+  }
+}
+
+function buildAccountInclude (required: boolean, withActor = false) {
+  return {
+    required,
+    attributes: [ 'id', 'name' ],
+    model: () => AccountModel.unscoped(),
+    include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
+  }
+}
+
+@Scopes({
+  [ScopeNames.WITH_ALL]: {
+    include: [
+      Object.assign(buildVideoInclude(false), {
+        include: [ buildChannelInclude(true, true) ]
+      }),
+
+      {
+        attributes: [ 'id', 'originCommentId' ],
+        model: () => VideoCommentModel.unscoped(),
+        required: false,
+        include: [
+          buildAccountInclude(true, true),
+          buildVideoInclude(true)
+        ]
+      },
+
+      {
+        attributes: [ 'id' ],
+        model: () => VideoAbuseModel.unscoped(),
+        required: false,
+        include: [ buildVideoInclude(true) ]
+      },
+
+      {
+        attributes: [ 'id' ],
+        model: () => VideoBlacklistModel.unscoped(),
+        required: false,
+        include: [ buildVideoInclude(true) ]
+      },
+
+      {
+        attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
+        model: () => VideoImportModel.unscoped(),
+        required: false,
+        include: [ buildVideoInclude(false) ]
+      },
+
+      {
+        attributes: [ 'id' ],
+        model: () => ActorFollowModel.unscoped(),
+        required: false,
+        include: [
+          {
+            attributes: [ 'preferredUsername' ],
+            model: () => ActorModel.unscoped(),
+            required: true,
+            as: 'ActorFollower',
+            include: [
+              {
+                attributes: [ 'id', 'name' ],
+                model: () => AccountModel.unscoped(),
+                required: true
+              },
+              {
+                attributes: [ 'filename' ],
+                model: () => AvatarModel.unscoped(),
+                required: false
+              },
+              {
+                attributes: [ 'host' ],
+                model: () => ServerModel.unscoped(),
+                required: false
+              }
+            ]
+          },
+          {
+            attributes: [ 'preferredUsername' ],
+            model: () => ActorModel.unscoped(),
+            required: true,
+            as: 'ActorFollowing',
+            include: [
+              buildChannelInclude(false),
+              buildAccountInclude(false)
+            ]
+          }
+        ]
+      },
+
+      buildAccountInclude(false, true)
+    ]
+  }
+})
+@Table({
+  tableName: 'userNotification',
+  indexes: [
+    {
+      fields: [ 'userId' ]
+    },
+    {
+      fields: [ 'videoId' ],
+      where: {
+        videoId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'commentId' ],
+      where: {
+        commentId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'videoAbuseId' ],
+      where: {
+        videoAbuseId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'videoBlacklistId' ],
+      where: {
+        videoBlacklistId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'videoImportId' ],
+      where: {
+        videoImportId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'accountId' ],
+      where: {
+        accountId: {
+          [Op.ne]: null
+        }
+      }
+    },
+    {
+      fields: [ 'actorFollowId' ],
+      where: {
+        actorFollowId: {
+          [Op.ne]: null
+        }
+      }
+    }
+  ]
+})
+export class UserNotificationModel extends Model<UserNotificationModel> {
+
+  @AllowNull(false)
+  @Default(null)
+  @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type'))
+  @Column
+  type: UserNotificationType
+
+  @AllowNull(false)
+  @Default(false)
+  @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read'))
+  @Column
+  read: boolean
+
+  @CreatedAt
+  createdAt: Date
+
+  @UpdatedAt
+  updatedAt: Date
+
+  @ForeignKey(() => UserModel)
+  @Column
+  userId: number
+
+  @BelongsTo(() => UserModel, {
+    foreignKey: {
+      allowNull: false
+    },
+    onDelete: 'cascade'
+  })
+  User: UserModel
+
+  @ForeignKey(() => VideoModel)
+  @Column
+  videoId: number
+
+  @BelongsTo(() => VideoModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  Video: VideoModel
+
+  @ForeignKey(() => VideoCommentModel)
+  @Column
+  commentId: number
+
+  @BelongsTo(() => VideoCommentModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  Comment: VideoCommentModel
+
+  @ForeignKey(() => VideoAbuseModel)
+  @Column
+  videoAbuseId: number
+
+  @BelongsTo(() => VideoAbuseModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  VideoAbuse: VideoAbuseModel
+
+  @ForeignKey(() => VideoBlacklistModel)
+  @Column
+  videoBlacklistId: number
+
+  @BelongsTo(() => VideoBlacklistModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  VideoBlacklist: VideoBlacklistModel
+
+  @ForeignKey(() => VideoImportModel)
+  @Column
+  videoImportId: number
+
+  @BelongsTo(() => VideoImportModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  VideoImport: VideoImportModel
+
+  @ForeignKey(() => AccountModel)
+  @Column
+  accountId: number
+
+  @BelongsTo(() => AccountModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  Account: AccountModel
+
+  @ForeignKey(() => ActorFollowModel)
+  @Column
+  actorFollowId: number
+
+  @BelongsTo(() => ActorFollowModel, {
+    foreignKey: {
+      allowNull: true
+    },
+    onDelete: 'cascade'
+  })
+  ActorFollow: ActorFollowModel
+
+  static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
+    const query: IFindOptions<UserNotificationModel> = {
+      offset: start,
+      limit: count,
+      order: getSort(sort),
+      where: {
+        userId
+      }
+    }
+
+    if (unread !== undefined) query.where['read'] = !unread
+
+    return UserNotificationModel.scope(ScopeNames.WITH_ALL)
+                                .findAndCountAll(query)
+                                .then(({ rows, count }) => {
+                                  return {
+                                    data: rows,
+                                    total: count
+                                  }
+                                })
+  }
+
+  static markAsRead (userId: number, notificationIds: number[]) {
+    const query = {
+      where: {
+        userId,
+        id: {
+          [Op.any]: notificationIds
+        }
+      }
+    }
+
+    return UserNotificationModel.update({ read: true }, query)
+  }
+
+  static markAllAsRead (userId: number) {
+    const query = { where: { userId } }
+
+    return UserNotificationModel.update({ read: true }, query)
+  }
+
+  toFormattedJSON (): UserNotification {
+    const video = this.Video
+      ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) })
+      : undefined
+
+    const videoImport = this.VideoImport ? {
+      id: this.VideoImport.id,
+      video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined,
+      torrentName: this.VideoImport.torrentName,
+      magnetUri: this.VideoImport.magnetUri,
+      targetUrl: this.VideoImport.targetUrl
+    } : undefined
+
+    const comment = this.Comment ? {
+      id: this.Comment.id,
+      threadId: this.Comment.getThreadId(),
+      account: this.formatActor(this.Comment.Account),
+      video: this.formatVideo(this.Comment.Video)
+    } : undefined
+
+    const videoAbuse = this.VideoAbuse ? {
+      id: this.VideoAbuse.id,
+      video: this.formatVideo(this.VideoAbuse.Video)
+    } : undefined
+
+    const videoBlacklist = this.VideoBlacklist ? {
+      id: this.VideoBlacklist.id,
+      video: this.formatVideo(this.VideoBlacklist.Video)
+    } : undefined
+
+    const account = this.Account ? this.formatActor(this.Account) : undefined
+
+    const actorFollow = this.ActorFollow ? {
+      id: this.ActorFollow.id,
+      follower: {
+        id: this.ActorFollow.ActorFollower.Account.id,
+        displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
+        name: this.ActorFollow.ActorFollower.preferredUsername,
+        avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getWebserverPath() } : undefined,
+        host: this.ActorFollow.ActorFollower.getHost()
+      },
+      following: {
+        type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account',
+        displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(),
+        name: this.ActorFollow.ActorFollowing.preferredUsername
+      }
+    } : undefined
+
+    return {
+      id: this.id,
+      type: this.type,
+      read: this.read,
+      video,
+      videoImport,
+      comment,
+      videoAbuse,
+      videoBlacklist,
+      account,
+      actorFollow,
+      createdAt: this.createdAt.toISOString(),
+      updatedAt: this.updatedAt.toISOString()
+    }
+  }
+
+  private formatVideo (video: VideoModel) {
+    return {
+      id: video.id,
+      uuid: video.uuid,
+      name: video.name
+    }
+  }
+
+  private formatActor (accountOrChannel: AccountModel | VideoChannelModel) {
+    const avatar = accountOrChannel.Actor.Avatar
+      ? { path: accountOrChannel.Actor.Avatar.getWebserverPath() }
+      : undefined
+
+    return {
+      id: accountOrChannel.id,
+      displayName: accountOrChannel.getDisplayName(),
+      name: accountOrChannel.Actor.preferredUsername,
+      host: accountOrChannel.Actor.getHost(),
+      avatar
+    }
+  }
+}
index 0476cad9deef992bc6347e7d6cbb387d598a6b4f..15cb399c952bc909305e331baf8cece53bccd45d 100644 (file)
@@ -1,6 +1,7 @@
-import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Min, Model, Table, UpdatedAt } from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoModel } from '../video/video'
 import { UserModel } from './user'
+import { Transaction, Op, DestroyOptions } from 'sequelize'
 
 @Table({
   tableName: 'userVideoHistory',
@@ -52,4 +53,34 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> {
     onDelete: 'CASCADE'
   })
   User: UserModel
+
+  static listForApi (user: UserModel, start: number, count: number) {
+    return VideoModel.listForApi({
+      start,
+      count,
+      sort: '-UserVideoHistories.updatedAt',
+      nsfw: null, // All
+      includeLocalVideos: true,
+      withFiles: false,
+      user,
+      historyOfUser: user
+    })
+  }
+
+  static removeHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) {
+    const query: DestroyOptions = {
+      where: {
+        userId: user.id
+      },
+      transaction: t
+    }
+
+    if (beforeDate) {
+      query.where.updatedAt = {
+        [Op.lt]: beforeDate
+      }
+    }
+
+    return UserVideoHistoryModel.destroy(query)
+  }
 }
index 1843603f1e935ec16c37241e75fe42c4357ffa94..017a96657b0a0c6289b13cca65860000848e735a 100644 (file)
@@ -32,6 +32,7 @@ import {
   isUserUsernameValid,
   isUserVideoQuotaDailyValid,
   isUserVideoQuotaValid,
+  isUserVideosHistoryEnabledValid,
   isUserWebTorrentEnabledValid
 } from '../../helpers/custom-validators/users'
 import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
@@ -43,6 +44,11 @@ import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
 import { values } from 'lodash'
 import { NSFW_POLICY_TYPES } from '../../initializers'
 import { clearCacheByUserId } from '../../lib/oauth-model'
+import { UserNotificationSettingModel } from './user-notification-setting'
+import { VideoModel } from '../video/video'
+import { ActorModel } from '../activitypub/actor'
+import { ActorFollowModel } from '../activitypub/actor-follow'
+import { VideoImportModel } from '../video/video-import'
 
 enum ScopeNames {
   WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL'
@@ -53,6 +59,10 @@ enum ScopeNames {
     {
       model: () => AccountModel,
       required: true
+    },
+    {
+      model: () => UserNotificationSettingModel,
+      required: true
     }
   ]
 })
@@ -63,6 +73,10 @@ enum ScopeNames {
         model: () => AccountModel,
         required: true,
         include: [ () => VideoChannelModel ]
+      },
+      {
+        model: () => UserNotificationSettingModel,
+        required: true
       }
     ]
   }
@@ -114,6 +128,12 @@ export class UserModel extends Model<UserModel> {
   @Column
   webTorrentEnabled: boolean
 
+  @AllowNull(false)
+  @Default(true)
+  @Is('UserVideosHistoryEnabled', value => throwIfNotValid(value, isUserVideosHistoryEnabledValid, 'Videos history enabled'))
+  @Column
+  videosHistoryEnabled: boolean
+
   @AllowNull(false)
   @Default(true)
   @Is('UserAutoPlayVideo', value => throwIfNotValid(value, isUserAutoPlayVideoValid, 'auto play video boolean'))
@@ -160,6 +180,19 @@ export class UserModel extends Model<UserModel> {
   })
   Account: AccountModel
 
+  @HasOne(() => UserNotificationSettingModel, {
+    foreignKey: 'userId',
+    onDelete: 'cascade',
+    hooks: true
+  })
+  NotificationSetting: UserNotificationSettingModel
+
+  @HasMany(() => VideoImportModel, {
+    foreignKey: 'userId',
+    onDelete: 'cascade'
+  })
+  VideoImports: VideoImportModel[]
+
   @HasMany(() => OAuthTokenModel, {
     foreignKey: 'userId',
     onDelete: 'cascade'
@@ -242,13 +275,12 @@ export class UserModel extends Model<UserModel> {
       })
   }
 
-  static listEmailsWithRight (right: UserRight) {
+  static listWithRight (right: UserRight) {
     const roles = Object.keys(USER_ROLE_LABELS)
       .map(k => parseInt(k, 10) as UserRole)
       .filter(role => hasUserRight(role, right))
 
     const query = {
-      attribute: [ 'email' ],
       where: {
         role: {
           [Sequelize.Op.in]: roles
@@ -256,9 +288,56 @@ export class UserModel extends Model<UserModel> {
       }
     }
 
-    return UserModel.unscoped()
-      .findAll(query)
-      .then(u => u.map(u => u.email))
+    return UserModel.findAll(query)
+  }
+
+  static listUserSubscribersOf (actorId: number) {
+    const query = {
+      include: [
+        {
+          model: UserNotificationSettingModel.unscoped(),
+          required: true
+        },
+        {
+          attributes: [ 'userId' ],
+          model: AccountModel.unscoped(),
+          required: true,
+          include: [
+            {
+              attributes: [ ],
+              model: ActorModel.unscoped(),
+              required: true,
+              where: {
+                serverId: null
+              },
+              include: [
+                {
+                  attributes: [ ],
+                  as: 'ActorFollowings',
+                  model: ActorFollowModel.unscoped(),
+                  required: true,
+                  where: {
+                    targetActorId: actorId
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+
+    return UserModel.unscoped().findAll(query)
+  }
+
+  static listByUsernames (usernames: string[]) {
+    const query = {
+      where: {
+        username: usernames
+      }
+    }
+
+    return UserModel.findAll(query)
   }
 
   static loadById (id: number) {
@@ -307,6 +386,95 @@ export class UserModel extends Model<UserModel> {
     return UserModel.findOne(query)
   }
 
+  static loadByVideoId (videoId: number) {
+    const query = {
+      include: [
+        {
+          required: true,
+          attributes: [ 'id' ],
+          model: AccountModel.unscoped(),
+          include: [
+            {
+              required: true,
+              attributes: [ 'id' ],
+              model: VideoChannelModel.unscoped(),
+              include: [
+                {
+                  required: true,
+                  attributes: [ 'id' ],
+                  model: VideoModel.unscoped(),
+                  where: {
+                    id: videoId
+                  }
+                }
+              ]
+            }
+          ]
+        }
+      ]
+    }
+
+    return UserModel.findOne(query)
+  }
+
+  static loadByVideoImportId (videoImportId: number) {
+    const query = {
+      include: [
+        {
+          required: true,
+          attributes: [ 'id' ],
+          model: VideoImportModel.unscoped(),
+          where: {
+            id: videoImportId
+          }
+        }
+      ]
+    }
+
+    return UserModel.findOne(query)
+  }
+
+  static loadByChannelActorId (videoChannelActorId: number) {
+    const query = {
+      include: [
+        {
+          required: true,
+          attributes: [ 'id' ],
+          model: AccountModel.unscoped(),
+          include: [
+            {
+              required: true,
+              attributes: [ 'id' ],
+              model: VideoChannelModel.unscoped(),
+              where: {
+                actorId: videoChannelActorId
+              }
+            }
+          ]
+        }
+      ]
+    }
+
+    return UserModel.findOne(query)
+  }
+
+  static loadByAccountActorId (accountActorId: number) {
+    const query = {
+      include: [
+        {
+          required: true,
+          attributes: [ 'id' ],
+          model: AccountModel.unscoped(),
+          where: {
+            actorId: accountActorId
+          }
+        }
+      ]
+    }
+
+    return UserModel.findOne(query)
+  }
+
   static getOriginalVideoFileTotalFromUser (user: UserModel) {
     // Don't use sequelize because we need to use a sub query
     const query = UserModel.generateUserQuotaBaseSQL()
@@ -363,6 +531,7 @@ export class UserModel extends Model<UserModel> {
       emailVerified: this.emailVerified,
       nsfwPolicy: this.nsfwPolicy,
       webTorrentEnabled: this.webTorrentEnabled,
+      videosHistoryEnabled: this.videosHistoryEnabled,
       autoPlayVideo: this.autoPlayVideo,
       role: this.role,
       roleLabel: USER_ROLE_LABELS[ this.role ],
@@ -372,6 +541,7 @@ export class UserModel extends Model<UserModel> {
       blocked: this.blocked,
       blockedReason: this.blockedReason,
       account: this.Account.toFormattedJSON(),
+      notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined,
       videoChannels: [],
       videoQuotaUsed: videoQuotaUsed !== undefined
             ? parseInt(videoQuotaUsed, 10)
index 0a693508354549ff3fbd46d55a9014211535cdb1..796e07a42d783b0cb120805ffac74b114effe095 100644 (file)
@@ -127,22 +127,6 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved)
   }
 
-  static updateActorFollowsScore (goodInboxes: string[], badInboxes: string[], t: Sequelize.Transaction | undefined) {
-    if (goodInboxes.length === 0 && badInboxes.length === 0) return
-
-    logger.info('Updating %d good actor follows and %d bad actor follows scores.', goodInboxes.length, badInboxes.length)
-
-    if (goodInboxes.length !== 0) {
-      ActorFollowModel.incrementScores(goodInboxes, ACTOR_FOLLOW_SCORE.BONUS, t)
-        .catch(err => logger.error('Cannot increment scores of good actor follows.', { err }))
-    }
-
-    if (badInboxes.length !== 0) {
-      ActorFollowModel.incrementScores(badInboxes, ACTOR_FOLLOW_SCORE.PENALTY, t)
-        .catch(err => logger.error('Cannot decrement scores of bad actor follows.', { err }))
-    }
-  }
-
   static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) {
     const query = {
       where: {
@@ -323,7 +307,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
       })
   }
 
-  static listFollowersForApi (id: number, start: number, count: number, sort: string, search?: string) {
+  static listFollowersForApi (actorId: number, start: number, count: number, sort: string, search?: string) {
     const query = {
       distinct: true,
       offset: start,
@@ -351,7 +335,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
           as: 'ActorFollowing',
           required: true,
           where: {
-            id
+            id: actorId
           }
         }
       ]
@@ -366,7 +350,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
                            })
   }
 
-  static listSubscriptionsForApi (id: number, start: number, count: number, sort: string) {
+  static listSubscriptionsForApi (actorId: number, start: number, count: number, sort: string) {
     const query = {
       attributes: [],
       distinct: true,
@@ -374,7 +358,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
       limit: count,
       order: getSort(sort),
       where: {
-        actorId: id
+        actorId: actorId
       },
       include: [
         {
@@ -464,6 +448,22 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     }
   }
 
+  static updateFollowScore (inboxUrl: string, value: number, t?: Sequelize.Transaction) {
+    const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` +
+      'WHERE id IN (' +
+        'SELECT "actorFollow"."id" FROM "actorFollow" ' +
+        'INNER JOIN "actor" ON "actor"."id" = "actorFollow"."actorId" ' +
+        `WHERE "actor"."inboxUrl" = '${inboxUrl}' OR "actor"."sharedInboxUrl" = '${inboxUrl}'` +
+      ')'
+
+    const options = {
+      type: Sequelize.QueryTypes.BULKUPDATE,
+      transaction: t
+    }
+
+    return ActorFollowModel.sequelize.query(query, options)
+  }
+
   private static async createListAcceptedFollowForApiQuery (
     type: 'followers' | 'following',
     actorIds: number[],
@@ -518,24 +518,6 @@ export class ActorFollowModel extends Model<ActorFollowModel> {
     }
   }
 
-  private static incrementScores (inboxUrls: string[], value: number, t: Sequelize.Transaction | undefined) {
-    const inboxUrlsString = inboxUrls.map(url => `'${url}'`).join(',')
-
-    const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` +
-      'WHERE id IN (' +
-        'SELECT "actorFollow"."id" FROM "actorFollow" ' +
-        'INNER JOIN "actor" ON "actor"."id" = "actorFollow"."actorId" ' +
-        'WHERE "actor"."inboxUrl" IN (' + inboxUrlsString + ') OR "actor"."sharedInboxUrl" IN (' + inboxUrlsString + ')' +
-      ')'
-
-    const options = t ? {
-      type: Sequelize.QueryTypes.BULKUPDATE,
-      transaction: t
-    } : undefined
-
-    return ActorFollowModel.sequelize.query(query, options)
-  }
-
   private static listBadActorFollows () {
     const query = {
       where: {
index 12b83916e7c8ecc735ae22d87582e789475f742c..dda57a8ba172264933a03fee9bf9723c40debb05 100644 (file)
@@ -219,6 +219,7 @@ export class ActorModel extends Model<ActorModel> {
       name: 'actorId',
       allowNull: false
     },
+    as: 'ActorFollowings',
     onDelete: 'cascade'
   })
   ActorFollowing: ActorFollowModel[]
index 9de4356b408c45961ea52e7087969c984f1dca3a..8f2ef2d9ac4206ebe1b8ce3905fed6ad4f3bc968 100644 (file)
@@ -15,7 +15,7 @@ import {
 import { ActorModel } from '../activitypub/actor'
 import { getVideoSort, throwIfNotValid } from '../utils'
 import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc'
-import { CONFIG, CONSTRAINTS_FIELDS, VIDEO_EXT_MIMETYPE } from '../../initializers'
+import { CONFIG, CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers'
 import { VideoFileModel } from '../video/video-file'
 import { getServerActor } from '../../helpers/utils'
 import { VideoModel } from '../video/video'
@@ -124,7 +124,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
     const logIdentifier = `${videoFile.Video.uuid}-${videoFile.resolution}`
     logger.info('Removing duplicated video file %s.', logIdentifier)
 
-    videoFile.Video.removeFile(videoFile)
+    videoFile.Video.removeFile(videoFile, true)
              .catch(err => logger.error('Cannot delete %s files.', logIdentifier, { err }))
 
     return undefined
@@ -395,7 +395,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
       ]
     }
 
-    return VideoRedundancyModel.find(query as any) // FIXME: typings
+    return VideoRedundancyModel.findOne(query as any) // FIXME: typings
       .then((r: any) => ({
         totalUsed: parseInt(r.totalUsed.toString(), 10),
         totalVideos: r.totalVideos,
@@ -415,8 +415,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> {
       expires: this.expiresOn.toISOString(),
       url: {
         type: 'Link',
-        mimeType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any,
-        mediaType: VIDEO_EXT_MIMETYPE[ this.VideoFile.extname ] as any,
+        mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any,
+        mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any,
         href: this.fileUrl,
         height: this.VideoFile.resolution,
         size: this.VideoFile.size,
index 60b0906e80964a4d5bf3e128dbd08b0b6289095e..5b4093aec40d7b2aee217810c437da749b167afd 100644 (file)
@@ -29,7 +29,11 @@ function getVideoSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) {
     ]
   }
 
-  return [ [ field, direction ], lastSort ]
+  const firstSort = typeof field === 'string' ?
+    field.split('.').concat([ direction ]) :
+    [ field, direction ]
+
+  return [ firstSort, lastSort ]
 }
 
 function getSortOnModel (model: any, value: string, lastSort: string[] = [ 'id', 'ASC' ]) {
index dbb88ca4565cd197f0d3af90153b8a759dd2fba3..cc47644f233f5e06ab15811321c41468437d7301 100644 (file)
@@ -1,17 +1,4 @@
-import {
-  AfterCreate,
-  AllowNull,
-  BelongsTo,
-  Column,
-  CreatedAt,
-  DataType,
-  Default,
-  ForeignKey,
-  Is,
-  Model,
-  Table,
-  UpdatedAt
-} from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
 import { VideoAbuseObject } from '../../../shared/models/activitypub/objects'
 import { VideoAbuse } from '../../../shared/models/videos'
 import {
@@ -19,7 +6,6 @@ import {
   isVideoAbuseReasonValid,
   isVideoAbuseStateValid
 } from '../../helpers/custom-validators/video-abuses'
-import { Emailer } from '../../lib/emailer'
 import { AccountModel } from '../account/account'
 import { getSort, throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
@@ -40,8 +26,9 @@ import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers'
 export class VideoAbuseModel extends Model<VideoAbuseModel> {
 
   @AllowNull(false)
+  @Default(null)
   @Is('VideoAbuseReason', value => throwIfNotValid(value, isVideoAbuseReasonValid, 'reason'))
-  @Column
+  @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.REASON.max))
   reason: string
 
   @AllowNull(false)
@@ -86,11 +73,6 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> {
   })
   Video: VideoModel
 
-  @AfterCreate
-  static sendEmailNotification (instance: VideoAbuseModel) {
-    return Emailer.Instance.addVideoAbuseReportJob(instance.videoId)
-  }
-
   static loadByIdAndVideoId (id: number, videoId: number) {
     const query = {
       where: {
index 67f7cd4871c0aeef041996ff11f557002e26ebfd..3b567e488cac0bcda98f2c9adc714faf8a8459f4 100644 (file)
@@ -1,21 +1,7 @@
-import {
-  AfterCreate,
-  AfterDestroy,
-  AllowNull,
-  BelongsTo,
-  Column,
-  CreatedAt,
-  DataType,
-  ForeignKey,
-  Is,
-  Model,
-  Table,
-  UpdatedAt
-} from 'sequelize-typescript'
+import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
 import { getSortOnModel, SortType, throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
 import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist'
-import { Emailer } from '../../lib/emailer'
 import { VideoBlacklist } from '../../../shared/models/videos'
 import { CONSTRAINTS_FIELDS } from '../../initializers'
 
@@ -35,6 +21,10 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
   @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max))
   reason: string
 
+  @AllowNull(false)
+  @Column
+  unfederated: boolean
+
   @CreatedAt
   createdAt: Date
 
@@ -53,16 +43,6 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
   })
   Video: VideoModel
 
-  @AfterCreate
-  static sendBlacklistEmailNotification (instance: VideoBlacklistModel) {
-    return Emailer.Instance.addVideoBlacklistReportJob(instance.videoId, instance.reason)
-  }
-
-  @AfterDestroy
-  static sendUnblacklistEmailNotification (instance: VideoBlacklistModel) {
-    return Emailer.Instance.addVideoUnblacklistReportJob(instance.videoId)
-  }
-
   static listForApi (start: number, count: number, sort: SortType) {
     const query = {
       offset: start,
@@ -103,6 +83,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> {
       createdAt: this.createdAt,
       updatedAt: this.updatedAt,
       reason: this.reason,
+      unfederated: this.unfederated,
 
       video: {
         id: video.id,
index f4586917e83918eaee7e1a6936449da870cc4f94..5598d80f615fe53f4ede030caf28b251ab55c42c 100644 (file)
@@ -233,6 +233,27 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
       })
   }
 
+  static listLocalsForSitemap (sort: string) {
+    const query = {
+      attributes: [ ],
+      offset: 0,
+      order: getSort(sort),
+      include: [
+        {
+          attributes: [ 'preferredUsername', 'serverId' ],
+          model: ActorModel.unscoped(),
+          where: {
+            serverId: null
+          }
+        }
+      ]
+    }
+
+    return VideoChannelModel
+      .unscoped()
+      .findAll(query)
+  }
+
   static searchForApi (options: {
     actorId: number
     search: string
@@ -449,4 +470,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> {
   getDisplayName () {
     return this.name
   }
+
+  isOutdated () {
+    return this.Actor.isOutdated()
+  }
 }
index dd6d08139158b295f350f435c053ea8a34e22d0d..cf6278da77399a423f18040cee265f25b294ffb3 100644 (file)
@@ -18,7 +18,7 @@ import { ActivityTagObject } from '../../../shared/models/activitypub/objects/co
 import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
 import { VideoComment } from '../../../shared/models/videos/video-comment.model'
 import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
+import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
 import { sendDeleteVideoComment } from '../../lib/activitypub/send'
 import { AccountModel } from '../account/account'
 import { ActorModel } from '../activitypub/actor'
@@ -29,6 +29,9 @@ import { VideoModel } from './video'
 import { VideoChannelModel } from './video-channel'
 import { getServerActor } from '../../helpers/utils'
 import { UserModel } from '../account/user'
+import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
+import { regexpCapture } from '../../helpers/regexp'
+import { uniq } from 'lodash'
 
 enum ScopeNames {
   WITH_ACCOUNT = 'WITH_ACCOUNT',
@@ -370,9 +373,11 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
         id: {
           [ Sequelize.Op.in ]: Sequelize.literal('(' +
             'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' +
-            'SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ' + comment.id + ' UNION ' +
-            'SELECT p.id, p."inReplyToCommentId" from "videoComment" p ' +
-            'INNER JOIN children c ON c."inReplyToCommentId" = p.id) ' +
+              `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` +
+              'UNION ' +
+              'SELECT "parent"."id", "parent"."inReplyToCommentId" FROM "videoComment" "parent" ' +
+              'INNER JOIN "children" ON "children"."inReplyToCommentId" = "parent"."id"' +
+            ') ' +
             'SELECT id FROM children' +
           ')'),
           [ Sequelize.Op.ne ]: comment.id
@@ -448,6 +453,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
     }
   }
 
+  getCommentStaticPath () {
+    return this.Video.getWatchStaticPath() + ';threadId=' + this.getThreadId()
+  }
+
   getThreadId (): number {
     return this.originCommentId || this.id
   }
@@ -456,6 +465,34 @@ export class VideoCommentModel extends Model<VideoCommentModel> {
     return this.Account.isOwned()
   }
 
+  extractMentions () {
+    if (!this.text) return []
+
+    const localMention = `@(${actorNameAlphabet}+)`
+    const remoteMention = `${localMention}@${CONFIG.WEBSERVER.HOST}`
+
+    const remoteMentionsRegex = new RegExp(' ' + remoteMention + ' ', 'g')
+    const localMentionsRegex = new RegExp(' ' + localMention + ' ', 'g')
+    const firstMentionRegex = new RegExp('^(?:(?:' + remoteMention + ')|(?:' + localMention + ')) ', 'g')
+    const endMentionRegex = new RegExp(' (?:(?:' + remoteMention + ')|(?:' + localMention + '))$', 'g')
+
+    return uniq(
+      [].concat(
+        regexpCapture(this.text, remoteMentionsRegex)
+          .map(([ , username ]) => username),
+
+        regexpCapture(this.text, localMentionsRegex)
+          .map(([ , username ]) => username),
+
+        regexpCapture(this.text, firstMentionRegex)
+          .map(([ , username1, username2 ]) => username1 || username2),
+
+        regexpCapture(this.text, endMentionRegex)
+          .map(([ , username1, username2 ]) => username1 || username2)
+      )
+    )
+  }
+
   toFormattedJSON () {
     return {
       id: this.id,
index adebdf0c75f61ba99436b08631f2d3a90612f458..1f1b76c1e10e0621dffe5ef18af13c64fa07747f 100644 (file)
@@ -1,4 +1,3 @@
-import { values } from 'lodash'
 import {
   AllowNull,
   BelongsTo,
@@ -14,12 +13,12 @@ import {
   UpdatedAt
 } from 'sequelize-typescript'
 import {
+  isVideoFileExtnameValid,
   isVideoFileInfoHashValid,
   isVideoFileResolutionValid,
   isVideoFileSizeValid,
   isVideoFPSResolutionValid
 } from '../../helpers/custom-validators/videos'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
 import { throwIfNotValid } from '../utils'
 import { VideoModel } from './video'
 import * as Sequelize from 'sequelize'
@@ -58,7 +57,8 @@ export class VideoFileModel extends Model<VideoFileModel> {
   size: number
 
   @AllowNull(false)
-  @Column(DataType.ENUM(values(CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)))
+  @Is('VideoFileExtname', value => throwIfNotValid(value, isVideoFileExtnameValid, 'extname'))
+  @Column
   extname: string
 
   @AllowNull(false)
@@ -120,6 +120,26 @@ export class VideoFileModel extends Model<VideoFileModel> {
     return VideoFileModel.findById(id, options)
   }
 
+  static async getStats () {
+    let totalLocalVideoFilesSize = await VideoFileModel.sum('size', {
+      include: [
+        {
+          attributes: [],
+          model: VideoModel.unscoped(),
+          where: {
+            remote: false
+          }
+        }
+      ]
+    } as any)
+    // Sequelize could return null...
+    if (!totalLocalVideoFilesSize) totalLocalVideoFilesSize = 0
+
+    return {
+      totalLocalVideoFilesSize
+    }
+  }
+
   hasSameUniqueKeysThan (other: VideoFileModel) {
     return this.fps === other.fps &&
       this.resolution === other.resolution &&
index e3f8d525b9e3f94bba7443f04909948183b48f23..de0747f2221229b24d29386ac3b0ff300781d928 100644 (file)
@@ -2,7 +2,7 @@ import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos'
 import { VideoModel } from './video'
 import { VideoFileModel } from './video-file'
 import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects'
-import { CONFIG, THUMBNAILS_SIZE, VIDEO_EXT_MIMETYPE } from '../../initializers'
+import { CONFIG, MIMETYPES, THUMBNAILS_SIZE } from '../../initializers'
 import { VideoCaptionModel } from './video-caption'
 import {
   getVideoCommentsActivityPubUrl,
@@ -207,8 +207,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject {
   for (const file of video.VideoFiles) {
     url.push({
       type: 'Link',
-      mimeType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
-      mediaType: VIDEO_EXT_MIMETYPE[ file.extname ] as any,
+      mimeType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any,
+      mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any,
       href: video.getVideoFileUrl(file, baseUrlHttp),
       height: file.resolution,
       size: file.size,
index 8d442b3f849940526fe5207afd157364f772dada..c723e57c0d20fe16f945b98db37811137f124418 100644 (file)
@@ -144,6 +144,10 @@ export class VideoImportModel extends Model<VideoImportModel> {
                            })
   }
 
+  getTargetIdentifier () {
+    return this.targetUrl || this.magnetUri || this.torrentName
+  }
+
   toFormattedJSON (): VideoImport {
     const videoFormatOptions = {
       completeDescription: true,
index 0f18d9f0c82e819371497c62691f6362d388f885..80a6c78320e20bf5a63f7e71ac51bfc951721ee5 100644 (file)
@@ -94,6 +94,7 @@ import {
 import * as validator from 'validator'
 import { UserVideoHistoryModel } from '../account/user-video-history'
 import { UserModel } from '../account/user'
+import { VideoImportModel } from './video-import'
 
 // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation
 const indexes: Sequelize.DefineIndexesOptions[] = [
@@ -102,16 +103,44 @@ const indexes: Sequelize.DefineIndexesOptions[] = [
   { fields: [ 'createdAt' ] },
   { fields: [ 'publishedAt' ] },
   { fields: [ 'duration' ] },
-  { fields: [ 'category' ] },
-  { fields: [ 'licence' ] },
-  { fields: [ 'nsfw' ] },
-  { fields: [ 'language' ] },
-  { fields: [ 'waitTranscoding' ] },
-  { fields: [ 'state' ] },
-  { fields: [ 'remote' ] },
   { fields: [ 'views' ] },
-  { fields: [ 'likes' ] },
   { fields: [ 'channelId' ] },
+  {
+    fields: [ 'category' ], // We don't care videos with an unknown category
+    where: {
+      category: {
+        [Sequelize.Op.ne]: null
+      }
+    }
+  },
+  {
+    fields: [ 'licence' ], // We don't care videos with an unknown licence
+    where: {
+      licence: {
+        [Sequelize.Op.ne]: null
+      }
+    }
+  },
+  {
+    fields: [ 'language' ], // We don't care videos with an unknown language
+    where: {
+      language: {
+        [Sequelize.Op.ne]: null
+      }
+    }
+  },
+  {
+    fields: [ 'nsfw' ], // Most of the videos are not NSFW
+    where: {
+      nsfw: true
+    }
+  },
+  {
+    fields: [ 'remote' ], // Only index local videos
+    where: {
+      remote: false
+    }
+  },
   {
     fields: [ 'uuid' ],
     unique: true
@@ -140,7 +169,7 @@ type ForAPIOptions = {
 
 type AvailableForListIDsOptions = {
   serverAccountId: number
-  actorId: number
+  followerActorId: number
   includeLocalVideos: boolean
   filter?: VideoFilter
   categoryOneOf?: number[]
@@ -153,7 +182,8 @@ type AvailableForListIDsOptions = {
   accountId?: number
   videoChannelId?: number
   trendingDays?: number
-  user?: UserModel
+  user?: UserModel,
+  historyOfUser?: UserModel
 }
 
 @Scopes({
@@ -315,7 +345,7 @@ type AvailableForListIDsOptions = {
       query.include.push(videoChannelInclude)
     }
 
-    if (options.actorId) {
+    if (options.followerActorId) {
       let localVideosReq = ''
       if (options.includeLocalVideos === true) {
         localVideosReq = ' UNION ALL ' +
@@ -327,7 +357,7 @@ type AvailableForListIDsOptions = {
       }
 
       // Force actorId to be a number to avoid SQL injections
-      const actorIdNumber = parseInt(options.actorId.toString(), 10)
+      const actorIdNumber = parseInt(options.followerActorId.toString(), 10)
       query.where[ 'id' ][ Sequelize.Op.and ].push({
         [ Sequelize.Op.in ]: Sequelize.literal(
           '(' +
@@ -416,6 +446,21 @@ type AvailableForListIDsOptions = {
       query.subQuery = false
     }
 
+    if (options.historyOfUser) {
+      query.include.push({
+        model: UserVideoHistoryModel,
+        required: true,
+        where: {
+          userId: options.historyOfUser.id
+        }
+      })
+
+      // Even if the relation is n:m, we know that a user only have 0..1 video history
+      // So we won't have multiple rows for the same video
+      // Without this, we would not be able to sort on "updatedAt" column of UserVideoHistoryModel
+      query.subQuery = false
+    }
+
     return query
   },
   [ ScopeNames.WITH_ACCOUNT_DETAILS ]: {
@@ -741,6 +786,15 @@ export class VideoModel extends Model<VideoModel> {
   })
   VideoBlacklist: VideoBlacklistModel
 
+  @HasOne(() => VideoImportModel, {
+    foreignKey: {
+      name: 'videoId',
+      allowNull: true
+    },
+    onDelete: 'set null'
+  })
+  VideoImport: VideoImportModel
+
   @HasMany(() => VideoCaptionModel, {
     foreignKey: {
       name: 'videoId',
@@ -985,9 +1039,10 @@ export class VideoModel extends Model<VideoModel> {
     filter?: VideoFilter,
     accountId?: number,
     videoChannelId?: number,
-    actorId?: number
+    followerActorId?: number
     trendingDays?: number,
-    user?: UserModel
+    user?: UserModel,
+    historyOfUser?: UserModel
   }, countVideos = true) {
     if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
       throw new Error('Try to filter all-local but no user has not the see all videos right')
@@ -1008,11 +1063,11 @@ export class VideoModel extends Model<VideoModel> {
 
     const serverActor = await getServerActor()
 
-    // actorId === null has a meaning, so just check undefined
-    const actorId = options.actorId !== undefined ? options.actorId : serverActor.id
+    // followerActorId === null has a meaning, so just check undefined
+    const followerActorId = options.followerActorId !== undefined ? options.followerActorId : serverActor.id
 
     const queryOptions = {
-      actorId,
+      followerActorId,
       serverAccountId: serverActor.Account.id,
       nsfw: options.nsfw,
       categoryOneOf: options.categoryOneOf,
@@ -1026,6 +1081,7 @@ export class VideoModel extends Model<VideoModel> {
       videoChannelId: options.videoChannelId,
       includeLocalVideos: options.includeLocalVideos,
       user: options.user,
+      historyOfUser: options.historyOfUser,
       trendingDays
     }
 
@@ -1118,7 +1174,7 @@ export class VideoModel extends Model<VideoModel> {
 
     const serverActor = await getServerActor()
     const queryOptions = {
-      actorId: serverActor.id,
+      followerActorId: serverActor.id,
       serverAccountId: serverActor.Account.id,
       includeLocalVideos: options.includeLocalVideos,
       nsfw: options.nsfw,
@@ -1273,11 +1329,11 @@ export class VideoModel extends Model<VideoModel> {
   // threshold corresponds to how many video the field should have to be returned
   static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
     const serverActor = await getServerActor()
-    const actorId = serverActor.id
+    const followerActorId = serverActor.id
 
     const scopeOptions: AvailableForListIDsOptions = {
       serverAccountId: serverActor.Account.id,
-      actorId,
+      followerActorId,
       includeLocalVideos: true
     }
 
@@ -1341,7 +1397,7 @@ export class VideoModel extends Model<VideoModel> {
     }
 
     const [ count, rowsId ] = await Promise.all([
-      countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve(undefined),
+      countVideos ? VideoModel.scope(countScope).count(countQuery) : Promise.resolve<number>(undefined),
       VideoModel.scope(idsScope).findAll(query)
     ])
     const ids = rowsId.map(r => r.id)
@@ -1481,6 +1537,10 @@ export class VideoModel extends Model<VideoModel> {
     videoFile.infoHash = parsedTorrent.infoHash
   }
 
+  getWatchStaticPath () {
+    return '/videos/watch/' + this.uuid
+  }
+
   getEmbedStaticPath () {
     return '/videos/embed/' + this.uuid
   }
@@ -1538,8 +1598,10 @@ export class VideoModel extends Model<VideoModel> {
       .catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err }))
   }
 
-  removeFile (videoFile: VideoFileModel) {
-    const filePath = join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile))
+  removeFile (videoFile: VideoFileModel, isRedundancy = false) {
+    const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR
+
+    const filePath = join(baseDir, this.getVideoFilename(videoFile))
     return remove(filePath)
       .catch(err => logger.warn('Cannot delete file %s.', filePath, { err }))
   }
@@ -1617,6 +1679,10 @@ export class VideoModel extends Model<VideoModel> {
     return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile)
   }
 
+  getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
+    return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile)
+  }
+
   getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) {
     return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile)
   }
index ea0682634f30d8d8ea6df952b0d97bc3fe9295c8..6d90d8643783d9e7718b7bf59d958b4e8fd1dbe9 100644 (file)
@@ -8,10 +8,10 @@ import {
   flushTests,
   killallServers,
   makeActivityPubGetRequest,
-  runServer,
   ServerInfo,
-  setAccessTokensToServers, uploadVideo
-} from '../../utils'
+  setAccessTokensToServers,
+  uploadVideo
+} from '../../../../shared/utils'
 
 const expect = chai.expect
 
index a42c606c6cccc8ddcce6d7f267907dfeb02752df..03609c1a977cad092f04c9d02dbd43c2fb2b2ebb 100644 (file)
@@ -11,12 +11,13 @@ import {
   killallServers,
   ServerInfo,
   setAccessTokensToServers,
+  setActorField,
+  setVideoField,
   uploadVideo,
-  userLogin
-} from '../../utils'
+  userLogin,
+  waitJobs
+} from '../../../../shared/utils'
 import * as chai from 'chai'
-import { setActorField, setVideoField } from '../../utils/miscs/sql'
-import { waitJobs } from '../../utils/server/jobs'
 import { Video } from '../../../../shared/models/videos'
 
 const expect = chai.expect
index 61084624784674585b1b99ab6c53e4c967238550..ac6e755c3678a298c553239d08b77d44622d4cfc 100644 (file)
@@ -2,7 +2,7 @@
 
 import 'mocha'
 import { expect } from 'chai'
-import { buildRequestStub } from '../../utils'
+import { buildRequestStub } from '../../../../shared/utils/miscs/stubs'
 import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto'
 import { cloneDeep } from 'lodash'
 import { buildSignedActivity } from '../../../helpers/activitypub'
@@ -91,7 +91,7 @@ describe('Test activity pub helpers', function () {
       req.headers = mastodonObject.headers
       req.headers.signature = 'Signature ' + req.headers.signature
 
-      const parsed = parseHTTPSignature(req, 3600 * 365 * 3)
+      const parsed = parseHTTPSignature(req, 3600 * 1000 * 365 * 10)
       const publicKey = require('./json/mastodon/public-key.json').publicKey
 
       const actor = { publicKey }
@@ -110,7 +110,7 @@ describe('Test activity pub helpers', function () {
       req.headers = mastodonObject.headers
       req.headers.signature = 'Signature ' + req.headers.signature
 
-      const parsed = parseHTTPSignature(req, 3600 * 365 * 3)
+      const parsed = parseHTTPSignature(req, 3600 * 1000 * 365 * 10)
       const publicKey = require('./json/mastodon/bad-public-key.json').publicKey
 
       const actor = { publicKey }
@@ -150,7 +150,7 @@ describe('Test activity pub helpers', function () {
 
       let errored = false
       try {
-        parseHTTPSignature(req, 3600 * 365 * 3)
+        parseHTTPSignature(req, 3600 * 1000 * 365 * 10)
       } catch {
         errored = true
       }
@@ -168,7 +168,7 @@ describe('Test activity pub helpers', function () {
       req.headers = mastodonObject.headers
       req.headers.signature = 'Signature ' + req.headers.signature
 
-      const parsed = parseHTTPSignature(req, 3600 * 365 * 3)
+      const parsed = parseHTTPSignature(req, 3600 * 1000 * 365 * 10)
       const publicKey = require('./json/mastodon/public-key.json').publicKey
 
       const actor = { publicKey }
index 67e04f79eaf5b7719ef86401a633b86613cab9c5..62ad8a0b566e97013c4a999174979dddaa2b11a5 100644 (file)
@@ -1,10 +1,19 @@
 /* tslint:disable:no-unused-expression */
 
 import 'mocha'
-import { doubleFollow, getVideo, reRunServer } from '../../utils'
-import { flushAndRunMultipleServers, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo, wait } from '../../utils/index'
-import { waitJobs } from '../../utils/server/jobs'
-import { setVideoField } from '../../utils/miscs/sql'
+import {
+  doubleFollow,
+  flushAndRunMultipleServers,
+  getVideo,
+  killallServers,
+  reRunServer,
+  ServerInfo,
+  setAccessTokensToServers,
+  uploadVideo,
+  wait,
+  setVideoField,
+  waitJobs
+} from '../../../../shared/utils'
 
 describe('Test AP refresher', function () {
   let servers: ServerInfo[] = []
@@ -13,7 +22,7 @@ describe('Test AP refresher', function () {
   let videoUUID3: string
 
   before(async function () {
-    this.timeout(30000)
+    this.timeout(60000)
 
     servers = await flushAndRunMultipleServers(2)
 
index 7349749f1fc8e8b574a46554bef5bac0444369c3..342ae0fa13e346fcddef8f4c799123d3a6cee706 100644 (file)
@@ -2,13 +2,19 @@
 
 import 'mocha'
 
-import { flushAndRunMultipleServers, flushTests, killallServers, ServerInfo } from '../../utils'
+import {
+  flushAndRunMultipleServers,
+  flushTests,
+  killallServers,
+  makeFollowRequest,
+  makePOSTAPRequest,
+  ServerInfo,
+  setActorField
+} from '../../../../shared/utils'
 import { HTTP_SIGNATURE } from '../../../initializers'
 import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
 import * as chai from 'chai'
-import { setActorField } from '../../utils/miscs/sql'
 import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub'
-import { makeFollowRequest, makePOSTAPRequest } from '../../utils/requests/activitypub'
 
 const expect = chai.expect
 
index 9e0b1e35c609df0d00c4b565e5a5247e2681d3a4..68f9519c6b328e89b6b898bef290b2966bd7992b 100644 (file)
@@ -2,11 +2,15 @@
 
 import 'mocha'
 
-import { flushTests, killallServers, runServer, ServerInfo } from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { getAccount } from '../../utils/users/accounts'
-
-describe('Test users API validators', function () {
+import { flushTests, killallServers, runServer, ServerInfo } from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { getAccount } from '../../../../shared/utils/users/accounts'
+
+describe('Test accounts API validators', function () {
   const path = '/api/v1/accounts/'
   let server: ServerInfo
 
index c745ac975f04d4d1bc2bf2aded538d4e55ad10e6..c20453c160e0a1cb9046f13704fdc3ab2887edd5 100644 (file)
@@ -13,8 +13,12 @@ import {
   makePostBodyRequest,
   ServerInfo,
   setAccessTokensToServers, userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
 
 describe('Test blocklist API validators', function () {
   let servers: ServerInfo[]
index d807f910bde37a639f77832a984d155be4c01ee9..4038ecbf051e691c0445a312611f8204917740e0 100644 (file)
@@ -7,7 +7,7 @@ import { CustomConfig } from '../../../../shared/models/server/custom-config.mod
 import {
   createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo,
   setAccessTokensToServers, userLogin, immutableAssign
-} from '../../utils'
+} from '../../../../shared/utils'
 
 describe('Test config API validators', function () {
   const path = '/api/v1/config/custom'
@@ -48,12 +48,16 @@ describe('Test config API validators', function () {
     admin: {
       email: 'superadmin1@example.com'
     },
+    contactForm: {
+      enabled: false
+    },
     user: {
       videoQuota: 5242881,
       videoQuotaDaily: 318742
     },
     transcoding: {
       enabled: true,
+      allowAdditionalExtensions: true,
       threads: 1,
       resolutions: {
         '240p': false,
diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts
new file mode 100644 (file)
index 0000000..c7e014b
--- /dev/null
@@ -0,0 +1,96 @@
+/* tslint:disable:no-unused-expression */
+
+import 'mocha'
+
+import {
+  flushTests,
+  immutableAssign,
+  killallServers,
+  reRunServer,
+  runServer,
+  ServerInfo,
+  setAccessTokensToServers
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { getAccount } from '../../../../shared/utils/users/accounts'
+import { sendContactForm } from '../../../../shared/utils/server/contact-form'
+import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
+
+describe('Test contact form API validators', function () {
+  let server: ServerInfo
+  const emails: object[] = []
+  const defaultBody = {
+    fromName: 'super name',
+    fromEmail: 'toto@example.com',
+    body: 'Hello, how are you?'
+  }
+
+  // ---------------------------------------------------------------
+
+  before(async function () {
+    this.timeout(60000)
+
+    await flushTests()
+    await MockSmtpServer.Instance.collectEmails(emails)
+
+    // Email is disabled
+    server = await runServer(1)
+  })
+
+  it('Should not accept a contact form if emails are disabled', async function () {
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 409 }))
+  })
+
+  it('Should not accept a contact form if it is disabled in the configuration', async function () {
+    this.timeout(10000)
+
+    killallServers([ server ])
+
+    // Contact form is disabled
+    await reRunServer(server, { smtp: { hostname: 'localhost' }, contact_form: { enabled: false } })
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 409 }))
+  })
+
+  it('Should not accept a contact form if from email is invalid', async function () {
+    this.timeout(10000)
+
+    killallServers([ server ])
+
+    // Email & contact form enabled
+    await reRunServer(server, { smtp: { hostname: 'localhost' } })
+
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: 'badEmail' }))
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: 'badEmail@' }))
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: undefined }))
+  })
+
+  it('Should not accept a contact form if from name is invalid', async function () {
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromName: 'name'.repeat(100) }))
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromName: '' }))
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromName: undefined }))
+  })
+
+  it('Should not accept a contact form if body is invalid', async function () {
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, body: 'body'.repeat(5000) }))
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, body: 'a' }))
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, body: undefined }))
+  })
+
+  it('Should accept a contact form with the correct parameters', async function () {
+    await sendContactForm(immutableAssign(defaultBody, { url: server.url }))
+  })
+
+  after(async function () {
+    MockSmtpServer.Instance.kill()
+    killallServers([ server ])
+
+    // Keep the logs if the test failed
+    if (this['ok']) {
+      await flushTests()
+    }
+  })
+})
index cdc95c81a6fc848de1b2b2b049efded4ad572ade..2ad1575a3b07c98d567221bddcbb95cf1e4ef2d1 100644 (file)
@@ -5,8 +5,12 @@ import 'mocha'
 import {
   createUser, flushTests, killallServers, makeDeleteRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers,
   userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
 
 describe('Test server follows API validators', function () {
   let server: ServerInfo
index 877ceb0a7671f73fa2f5c1fa010c6c2818693128..77c17036ac67e3cbe5d27b52cf1b17a1fa8e53eb 100644 (file)
@@ -1,12 +1,13 @@
-// Order of the tests we want to execute
 import './accounts'
 import './blocklist'
 import './config'
+import './contact-form'
 import './follows'
 import './jobs'
 import './redundancy'
 import './search'
 import './services'
+import './user-notifications'
 import './user-subscriptions'
 import './users'
 import './video-abuses'
index ce3ac880920f1dea9b40cf20b6980e2d4ad9dd53..89760ff98b097891117ef4eef653d358b28f3ba7 100644 (file)
@@ -2,9 +2,21 @@
 
 import 'mocha'
 
-import { createUser, flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, userLogin } from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { makeGetRequest } from '../../utils/requests/requests'
+import {
+  createUser,
+  flushTests,
+  killallServers,
+  runServer,
+  ServerInfo,
+  setAccessTokensToServers,
+  userLogin
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { makeGetRequest } from '../../../../shared/utils/requests/requests'
 
 describe('Test jobs API validators', function () {
   const path = '/api/v1/jobs/failed'
index aa588e3dda7ebdc437c04bd296532b870965da7a..ff4726ceb55475709d790bb1bcd1fb3ad07a3943 100644 (file)
@@ -12,7 +12,7 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   userLogin
-} from '../../utils'
+} from '../../../../shared/utils'
 
 describe('Test server redundancy API validators', function () {
   let servers: ServerInfo[]
index eabf602acffb601200708631e16341d6b1ac36f1..aa81965f33362e32d29890e8c0b2c226185fec4e 100644 (file)
@@ -2,8 +2,12 @@
 
 import 'mocha'
 
-import { flushTests, immutableAssign, killallServers, makeGetRequest, runServer, ServerInfo } from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+import { flushTests, immutableAssign, killallServers, makeGetRequest, runServer, ServerInfo } from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
 
 describe('Test videos API validator', function () {
   let server: ServerInfo
index fcde7e17965563514a47e2b77eba27fdf8328bb7..28591af9ddb36a849928484a6ddf7c7cc169e64b 100644 (file)
@@ -2,7 +2,15 @@
 
 import 'mocha'
 
-import { flushTests, killallServers, makeGetRequest, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils'
+import {
+  flushTests,
+  killallServers,
+  makeGetRequest,
+  runServer,
+  ServerInfo,
+  setAccessTokensToServers,
+  uploadVideo
+} from '../../../../shared/utils'
 
 describe('Test services API validators', function () {
   let server: ServerInfo
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts
new file mode 100644 (file)
index 0000000..714f481
--- /dev/null
@@ -0,0 +1,297 @@
+/* tslint:disable:no-unused-expression */
+
+import 'mocha'
+import * as io from 'socket.io-client'
+
+import {
+  flushTests,
+  immutableAssign,
+  killallServers,
+  makeGetRequest,
+  makePostBodyRequest,
+  makePutBodyRequest,
+  runServer,
+  ServerInfo,
+  setAccessTokensToServers,
+  wait
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { UserNotificationSetting, UserNotificationSettingValue } from '../../../../shared/models/users'
+
+describe('Test user notifications API validators', function () {
+  let server: ServerInfo
+
+  // ---------------------------------------------------------------
+
+  before(async function () {
+    this.timeout(30000)
+
+    await flushTests()
+
+    server = await runServer(1)
+
+    await setAccessTokensToServers([ server ])
+  })
+
+  describe('When listing my notifications', function () {
+    const path = '/api/v1/users/me/notifications'
+
+    it('Should fail with a bad start pagination', async function () {
+      await checkBadStartPagination(server.url, path, server.accessToken)
+    })
+
+    it('Should fail with a bad count pagination', async function () {
+      await checkBadCountPagination(server.url, path, server.accessToken)
+    })
+
+    it('Should fail with an incorrect sort', async function () {
+      await checkBadSortPagination(server.url, path, server.accessToken)
+    })
+
+    it('Should fail with an incorrect unread parameter', async function () {
+      await makeGetRequest({
+        url: server.url,
+        path,
+        query: {
+          unread: 'toto'
+        },
+        token: server.accessToken,
+        statusCodeExpected: 200
+      })
+    })
+
+    it('Should fail with a non authenticated user', async function () {
+      await makeGetRequest({
+        url: server.url,
+        path,
+        statusCodeExpected: 401
+      })
+    })
+
+    it('Should succeed with the correct parameters', async function () {
+      await makeGetRequest({
+        url: server.url,
+        path,
+        token: server.accessToken,
+        statusCodeExpected: 200
+      })
+    })
+  })
+
+  describe('When marking as read my notifications', function () {
+    const path = '/api/v1/users/me/notifications/read'
+
+    it('Should fail with wrong ids parameters', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        fields: {
+          ids: [ 'hello' ]
+        },
+        token: server.accessToken,
+        statusCodeExpected: 400
+      })
+
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        fields: {
+          ids: [ ]
+        },
+        token: server.accessToken,
+        statusCodeExpected: 400
+      })
+
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        fields: {
+          ids: 5
+        },
+        token: server.accessToken,
+        statusCodeExpected: 400
+      })
+    })
+
+    it('Should fail with a non authenticated user', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        fields: {
+          ids: [ 5 ]
+        },
+        statusCodeExpected: 401
+      })
+    })
+
+    it('Should succeed with the correct parameters', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        fields: {
+          ids: [ 5 ]
+        },
+        token: server.accessToken,
+        statusCodeExpected: 204
+      })
+    })
+  })
+
+  describe('When marking as read my notifications', function () {
+    const path = '/api/v1/users/me/notifications/read-all'
+
+    it('Should fail with a non authenticated user', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        statusCodeExpected: 401
+      })
+    })
+
+    it('Should succeed with the correct parameters', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        path,
+        token: server.accessToken,
+        statusCodeExpected: 204
+      })
+    })
+  })
+
+  describe('When updating my notification settings', function () {
+    const path = '/api/v1/users/me/notification-settings'
+    const correctFields: UserNotificationSetting = {
+      newVideoFromSubscription: UserNotificationSettingValue.WEB,
+      newCommentOnMyVideo: UserNotificationSettingValue.WEB,
+      videoAbuseAsModerator: UserNotificationSettingValue.WEB,
+      blacklistOnMyVideo: UserNotificationSettingValue.WEB,
+      myVideoImportFinished: UserNotificationSettingValue.WEB,
+      myVideoPublished: UserNotificationSettingValue.WEB,
+      commentMention: UserNotificationSettingValue.WEB,
+      newFollow: UserNotificationSettingValue.WEB,
+      newUserRegistration: UserNotificationSettingValue.WEB
+    }
+
+    it('Should fail with missing fields', async function () {
+      await makePutBodyRequest({
+        url: server.url,
+        path,
+        token: server.accessToken,
+        fields: { newVideoFromSubscription: UserNotificationSettingValue.WEB },
+        statusCodeExpected: 400
+      })
+    })
+
+    it('Should fail with incorrect field values', async function () {
+      {
+        const fields = immutableAssign(correctFields, { newCommentOnMyVideo: 15 })
+
+        await makePutBodyRequest({
+          url: server.url,
+          path,
+          token: server.accessToken,
+          fields,
+          statusCodeExpected: 400
+        })
+      }
+
+      {
+        const fields = immutableAssign(correctFields, { newCommentOnMyVideo: 'toto' })
+
+        await makePutBodyRequest({
+          url: server.url,
+          path,
+          fields,
+          token: server.accessToken,
+          statusCodeExpected: 400
+        })
+      }
+    })
+
+    it('Should fail with a non authenticated user', async function () {
+      await makePutBodyRequest({
+        url: server.url,
+        path,
+        fields: correctFields,
+        statusCodeExpected: 401
+      })
+    })
+
+    it('Should succeed with the correct parameters', async function () {
+      await makePutBodyRequest({
+        url: server.url,
+        path,
+        token: server.accessToken,
+        fields: correctFields,
+        statusCodeExpected: 204
+      })
+    })
+  })
+
+  describe('When connecting to my notification socket', function () {
+    it('Should fail with no token', function (next) {
+      const socket = io('http://localhost:9001/user-notifications', { reconnection: false })
+
+      socket.on('error', () => {
+        socket.removeListener('error', this)
+        socket.disconnect()
+        next()
+      })
+
+      socket.on('connect', () => {
+        socket.disconnect()
+        next(new Error('Connected with a missing token.'))
+      })
+    })
+
+    it('Should fail with an invalid token', function (next) {
+      const socket = io('http://localhost:9001/user-notifications', {
+        query: { accessToken: 'bad_access_token' },
+        reconnection: false
+      })
+
+      socket.on('error', () => {
+        socket.removeListener('error', this)
+        socket.disconnect()
+        next()
+      })
+
+      socket.on('connect', () => {
+        socket.disconnect()
+        next(new Error('Connected with an invalid token.'))
+      })
+    })
+
+    it('Should success with the correct token', function (next) {
+      const socket = io('http://localhost:9001/user-notifications', {
+        query: { accessToken: server.accessToken },
+        reconnection: false
+      })
+
+      const errorListener = socket.on('error', err => {
+        next(new Error('Error in connection: ' + err))
+      })
+
+      socket.on('connect', async () => {
+        socket.removeListener('error', errorListener)
+        socket.disconnect()
+
+        await wait(500)
+        next()
+      })
+    })
+  })
+
+  after(async function () {
+    killallServers([ server ])
+
+    // Keep the logs if the test failed
+    if (this['ok']) {
+      await flushTests()
+    }
+  })
+})
index 6af7ed43b1fd3552f8e9fc17b766e5361d1912a2..8a9ced7c1526408aae5e7ee66387f0a39cbb0351 100644 (file)
@@ -13,9 +13,14 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 describe('Test user subscriptions API validators', function () {
   const path = '/api/v1/users/me/subscriptions'
index 273be1679f3e2a1fc417f5d423798270266a9a70..a3e8e2e9c1b960dced92fe927add68075ec32a09 100644 (file)
@@ -9,11 +9,15 @@ import {
   createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
   makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
   updateUser, uploadVideo, userLogin, deleteMe, unblockUser, blockUser
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../utils/videos/video-imports'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/utils/videos/video-imports'
 import { VideoPrivacy } from '../../../../shared/models/videos'
-import { waitJobs } from '../../utils/server/jobs'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { expect } from 'chai'
 
 describe('Test users API validators', function () {
@@ -99,13 +103,13 @@ describe('Test users API validators', function () {
     }
 
     it('Should fail with a too small username', async function () {
-      const fields = immutableAssign(baseCorrectParams, { username: 'fi' })
+      const fields = immutableAssign(baseCorrectParams, { username: '' })
 
       await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
 
     it('Should fail with a too long username', async function () {
-      const fields = immutableAssign(baseCorrectParams, { username: 'my_super_username_which_is_very_long' })
+      const fields = immutableAssign(baseCorrectParams, { username: 'super'.repeat(50) })
 
       await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
@@ -304,6 +308,14 @@ describe('Test users API validators', function () {
       await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
     })
 
+    it('Should fail with an invalid videosHistoryEnabled attribute', async function () {
+      const fields = {
+        videosHistoryEnabled: -1
+      }
+
+      await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields })
+    })
+
     it('Should fail with an non authenticated user', async function () {
       const fields = {
         currentPassword: 'my super password',
@@ -473,11 +485,10 @@ describe('Test users API validators', function () {
         email: 'email@example.com',
         emailVerified: true,
         videoQuota: 42,
-        role: UserRole.MODERATOR
+        role: UserRole.USER
       }
 
       await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields, statusCodeExpected: 204 })
-      userAccessToken = await userLogin(server, user)
     })
   })
 
@@ -550,13 +561,13 @@ describe('Test users API validators', function () {
     }
 
     it('Should fail with a too small username', async function () {
-      const fields = immutableAssign(baseCorrectParams, { username: 'ji' })
+      const fields = immutableAssign(baseCorrectParams, { username: '' })
 
       await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
     })
 
     it('Should fail with a too long username', async function () {
-      const fields = immutableAssign(baseCorrectParams, { username: 'my_super_username_which_is_very_long' })
+      const fields = immutableAssign(baseCorrectParams, { username: 'super'.repeat(50) })
 
       await makePostBodyRequest({ url: server.url, path: registrationPath, token: server.accessToken, fields })
     })
index d2bed6a2a2e59cf60145f60517b76b99f751fde4..3b8f5f14d80425420918dd46b70c8e8d7f7abec1 100644 (file)
@@ -15,8 +15,12 @@ import {
   updateVideoAbuse,
   uploadVideo,
   userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
 import { VideoAbuseState } from '../../../../shared/models/videos'
 
 describe('Test video abuses API validators', function () {
@@ -109,8 +113,8 @@ describe('Test video abuses API validators', function () {
       await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
 
-    it('Should fail with a reason too big', async function () {
-      const fields = { reason: 'super'.repeat(61) }
+    it('Should fail with a too big reason', async function () {
+      const fields = { reason: 'super'.repeat(605) }
 
       await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
     })
@@ -150,7 +154,7 @@ describe('Test video abuses API validators', function () {
     })
 
     it('Should fail with a bad moderation comment', async function () {
-      const body = { moderationComment: 'b'.repeat(305) }
+      const body = { moderationComment: 'b'.repeat(3001) }
       await updateVideoAbuse(server.url, server.accessToken, server.video.uuid, videoAbuseId, body, 400)
     })
 
index 47321623676379633cc747a16424e4f3bcd667de..6b82643f4fbcbfa2e9f74f144e224ee9f98025a6 100644 (file)
@@ -4,25 +4,33 @@ import 'mocha'
 
 import {
   createUser,
+  doubleFollow,
+  flushAndRunMultipleServers,
   flushTests,
-  getBlacklistedVideosList, getVideo, getVideoWithToken,
+  getBlacklistedVideosList,
+  getVideo,
+  getVideoWithToken,
   killallServers,
   makePostBodyRequest,
   makePutBodyRequest,
   removeVideoFromBlacklist,
-  runServer,
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo,
-  userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+  userLogin, waitJobs
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
 import { VideoDetails } from '../../../../shared/models/videos'
 import { expect } from 'chai'
 
 describe('Test video blacklist API validators', function () {
-  let server: ServerInfo
+  let servers: ServerInfo[]
   let notBlacklistedVideoId: number
+  let remoteVideoUUID: string
   let userAccessToken1 = ''
   let userAccessToken2 = ''
 
@@ -32,75 +40,89 @@ describe('Test video blacklist API validators', function () {
     this.timeout(120000)
 
     await flushTests()
+    servers = await flushAndRunMultipleServers(2)
 
-    server = await runServer(1)
-
-    await setAccessTokensToServers([ server ])
+    await setAccessTokensToServers(servers)
+    await doubleFollow(servers[0], servers[1])
 
     {
       const username = 'user1'
       const password = 'my super password'
-      await createUser(server.url, server.accessToken, username, password)
-      userAccessToken1 = await userLogin(server, { username, password })
+      await createUser(servers[0].url, servers[0].accessToken, username, password)
+      userAccessToken1 = await userLogin(servers[0], { username, password })
     }
 
     {
       const username = 'user2'
       const password = 'my super password'
-      await createUser(server.url, server.accessToken, username, password)
-      userAccessToken2 = await userLogin(server, { username, password })
+      await createUser(servers[0].url, servers[0].accessToken, username, password)
+      userAccessToken2 = await userLogin(servers[0], { username, password })
     }
 
     {
-      const res = await uploadVideo(server.url, userAccessToken1, {})
-      server.video = res.body.video
+      const res = await uploadVideo(servers[0].url, userAccessToken1, {})
+      servers[0].video = res.body.video
     }
 
     {
-      const res = await uploadVideo(server.url, server.accessToken, {})
+      const res = await uploadVideo(servers[0].url, servers[0].accessToken, {})
       notBlacklistedVideoId = res.body.video.uuid
     }
+
+    {
+      const res = await uploadVideo(servers[1].url, servers[1].accessToken, {})
+      remoteVideoUUID = res.body.video.uuid
+    }
+
+    await waitJobs(servers)
   })
 
   describe('When adding a video in blacklist', function () {
     const basePath = '/api/v1/videos/'
 
     it('Should fail with nothing', async function () {
-      const path = basePath + server.video + '/blacklist'
+      const path = basePath + servers[0].video + '/blacklist'
       const fields = {}
-      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+      await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
     })
 
     it('Should fail with a wrong video', async function () {
       const wrongPath = '/api/v1/videos/blabla/blacklist'
       const fields = {}
-      await makePostBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
+      await makePostBodyRequest({ url: servers[0].url, path: wrongPath, token: servers[0].accessToken, fields })
     })
 
     it('Should fail with a non authenticated user', async function () {
-      const path = basePath + server.video + '/blacklist'
+      const path = basePath + servers[0].video + '/blacklist'
       const fields = {}
-      await makePostBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
+      await makePostBodyRequest({ url: servers[0].url, path, token: 'hello', fields, statusCodeExpected: 401 })
     })
 
     it('Should fail with a non admin user', async function () {
-      const path = basePath + server.video + '/blacklist'
+      const path = basePath + servers[0].video + '/blacklist'
       const fields = {}
-      await makePostBodyRequest({ url: server.url, path, token: userAccessToken2, fields, statusCodeExpected: 403 })
+      await makePostBodyRequest({ url: servers[0].url, path, token: userAccessToken2, fields, statusCodeExpected: 403 })
     })
 
     it('Should fail with an invalid reason', async function () {
-      const path = basePath + server.video.uuid + '/blacklist'
+      const path = basePath + servers[0].video.uuid + '/blacklist'
       const fields = { reason: 'a'.repeat(305) }
 
-      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+      await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
+    })
+
+    it('Should fail to unfederate a remote video', async function () {
+      const path = basePath + remoteVideoUUID + '/blacklist'
+      const fields = { unfederate: true }
+
+      await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 409 })
     })
 
     it('Should succeed with the correct params', async function () {
-      const path = basePath + server.video.uuid + '/blacklist'
+      const path = basePath + servers[0].video.uuid + '/blacklist'
       const fields = { }
 
-      await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 204 })
+      await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 })
     })
   })
 
@@ -110,61 +132,61 @@ describe('Test video blacklist API validators', function () {
     it('Should fail with a wrong video', async function () {
       const wrongPath = '/api/v1/videos/blabla/blacklist'
       const fields = {}
-      await makePutBodyRequest({ url: server.url, path: wrongPath, token: server.accessToken, fields })
+      await makePutBodyRequest({ url: servers[0].url, path: wrongPath, token: servers[0].accessToken, fields })
     })
 
     it('Should fail with a video not blacklisted', async function () {
       const path = '/api/v1/videos/' + notBlacklistedVideoId + '/blacklist'
       const fields = {}
-      await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 404 })
+      await makePutBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 404 })
     })
 
     it('Should fail with a non authenticated user', async function () {
-      const path = basePath + server.video + '/blacklist'
+      const path = basePath + servers[0].video + '/blacklist'
       const fields = {}
-      await makePutBodyRequest({ url: server.url, path, token: 'hello', fields, statusCodeExpected: 401 })
+      await makePutBodyRequest({ url: servers[0].url, path, token: 'hello', fields, statusCodeExpected: 401 })
     })
 
     it('Should fail with a non admin user', async function () {
-      const path = basePath + server.video + '/blacklist'
+      const path = basePath + servers[0].video + '/blacklist'
       const fields = {}
-      await makePutBodyRequest({ url: server.url, path, token: userAccessToken2, fields, statusCodeExpected: 403 })
+      await makePutBodyRequest({ url: servers[0].url, path, token: userAccessToken2, fields, statusCodeExpected: 403 })
     })
 
     it('Should fail with an invalid reason', async function () {
-      const path = basePath + server.video.uuid + '/blacklist'
+      const path = basePath + servers[0].video.uuid + '/blacklist'
       const fields = { reason: 'a'.repeat(305) }
 
-      await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields })
+      await makePutBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields })
     })
 
     it('Should succeed with the correct params', async function () {
-      const path = basePath + server.video.uuid + '/blacklist'
+      const path = basePath + servers[0].video.uuid + '/blacklist'
       const fields = { reason: 'hello' }
 
-      await makePutBodyRequest({ url: server.url, path, token: server.accessToken, fields, statusCodeExpected: 204 })
+      await makePutBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 })
     })
   })
 
   describe('When getting blacklisted video', function () {
 
     it('Should fail with a non authenticated user', async function () {
-      await getVideo(server.url, server.video.uuid, 401)
+      await getVideo(servers[0].url, servers[0].video.uuid, 401)
     })
 
     it('Should fail with another user', async function () {
-      await getVideoWithToken(server.url, userAccessToken2, server.video.uuid, 403)
+      await getVideoWithToken(servers[0].url, userAccessToken2, servers[0].video.uuid, 403)
     })
 
     it('Should succeed with the owner authenticated user', async function () {
-      const res = await getVideoWithToken(server.url, userAccessToken1, server.video.uuid, 200)
+      const res = await getVideoWithToken(servers[0].url, userAccessToken1, servers[0].video.uuid, 200)
       const video: VideoDetails = res.body
 
       expect(video.blacklisted).to.be.true
     })
 
     it('Should succeed with an admin', async function () {
-      const res = await getVideoWithToken(server.url, server.accessToken, server.video.uuid, 200)
+      const res = await getVideoWithToken(servers[0].url, servers[0].accessToken, servers[0].video.uuid, 200)
       const video: VideoDetails = res.body
 
       expect(video.blacklisted).to.be.true
@@ -173,24 +195,24 @@ describe('Test video blacklist API validators', function () {
 
   describe('When removing a video in blacklist', function () {
     it('Should fail with a non authenticated user', async function () {
-      await removeVideoFromBlacklist(server.url, 'fake token', server.video.uuid, 401)
+      await removeVideoFromBlacklist(servers[0].url, 'fake token', servers[0].video.uuid, 401)
     })
 
     it('Should fail with a non admin user', async function () {
-      await removeVideoFromBlacklist(server.url, userAccessToken2, server.video.uuid, 403)
+      await removeVideoFromBlacklist(servers[0].url, userAccessToken2, servers[0].video.uuid, 403)
     })
 
     it('Should fail with an incorrect id', async function () {
-      await removeVideoFromBlacklist(server.url, server.accessToken, 'hello', 400)
+      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, 'hello', 400)
     })
 
     it('Should fail with a not blacklisted video', async function () {
       // The video was not added to the blacklist so it should fail
-      await removeVideoFromBlacklist(server.url, server.accessToken, notBlacklistedVideoId, 404)
+      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, notBlacklistedVideoId, 404)
     })
 
     it('Should succeed with the correct params', async function () {
-      await removeVideoFromBlacklist(server.url, server.accessToken, server.video.uuid, 204)
+      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, servers[0].video.uuid, 204)
     })
   })
 
@@ -198,28 +220,28 @@ describe('Test video blacklist API validators', function () {
     const basePath = '/api/v1/videos/blacklist/'
 
     it('Should fail with a non authenticated user', async function () {
-      await getBlacklistedVideosList(server.url, 'fake token', 401)
+      await getBlacklistedVideosList(servers[0].url, 'fake token', 401)
     })
 
     it('Should fail with a non admin user', async function () {
-      await getBlacklistedVideosList(server.url, userAccessToken2, 403)
+      await getBlacklistedVideosList(servers[0].url, userAccessToken2, 403)
     })
 
     it('Should fail with a bad start pagination', async function () {
-      await checkBadStartPagination(server.url, basePath, server.accessToken)
+      await checkBadStartPagination(servers[0].url, basePath, servers[0].accessToken)
     })
 
     it('Should fail with a bad count pagination', async function () {
-      await checkBadCountPagination(server.url, basePath, server.accessToken)
+      await checkBadCountPagination(servers[0].url, basePath, servers[0].accessToken)
     })
 
     it('Should fail with an incorrect sort', async function () {
-      await checkBadSortPagination(server.url, basePath, server.accessToken)
+      await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken)
     })
   })
 
   after(async function () {
-    killallServers([ server ])
+    killallServers(servers)
 
     // Keep the logs if the test failed
     if (this['ok']) {
index 8d46971a16c37bf3977cef6acc1ab41f1d5483fa..e4d36fd4fe7e84c20cc87cbc454167c7ab323075 100644 (file)
@@ -13,9 +13,9 @@ import {
   setAccessTokensToServers,
   uploadVideo,
   userLogin
-} from '../../utils'
+} from '../../../../shared/utils'
 import { join } from 'path'
-import { createVideoCaption } from '../../utils/videos/video-captions'
+import { createVideoCaption } from '../../../../shared/utils/videos/video-captions'
 
 describe('Test video captions API validator', function () {
   const path = '/api/v1/videos/'
index e5696224dacdcfccfe7f099e29cce2f938145d8c..14e4deaf7680b1fa7068bcd62b73db2bf1f97b15 100644 (file)
@@ -20,8 +20,12 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
 import { User } from '../../../../shared/models/users'
 import { join } from 'path'
 
index 5241832fead9f4920007cd0d2e62a44dafaf98a4..5981780ede0409bc8cf74c357ddb15263e0519a2 100644 (file)
@@ -6,9 +6,13 @@ import {
   createUser,
   flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers,
   uploadVideo, userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { addVideoCommentThread } from '../../utils/videos/video-comments'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
 
 const expect = chai.expect
 
index b51f3d2cd6a8d3b45b98402671154599637fb3d3..7bf187007c90243b0e516a27f61a9586a6e9d29e 100644 (file)
@@ -18,9 +18,13 @@ import {
   setAccessTokensToServers,
   updateCustomSubConfig,
   userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { getMagnetURI, getYoutubeVideoUrl } from '../../utils/videos/video-imports'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { getMagnetURI, getYoutubeVideoUrl } from '../../../../shared/utils/videos/video-imports'
 
 describe('Test video imports API validator', function () {
   const path = '/api/v1/videos/imports'
index 784cd8ba10e15a7a29f41b8896a7a101b65c7f0d..e998c8a3dafcbb445f0eb2c1abfe1f26c9120353 100644 (file)
@@ -11,7 +11,7 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   userLogin
-} from '../../utils'
+} from '../../../../shared/utils'
 import { UserRole } from '../../../../shared/models/users'
 
 const expect = chai.expect
index 808c3b616fce92d04cfea6629f25367c217bc054..8c079a956c455aec6964fbcced561726bae6bafb 100644 (file)
@@ -3,20 +3,25 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
+  checkBadCountPagination,
+  checkBadStartPagination,
   flushTests,
   killallServers,
+  makeGetRequest,
   makePostBodyRequest,
   makePutBodyRequest,
   runServer,
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
-} from '../../utils'
+} from '../../../../shared/utils'
 
 const expect = chai.expect
 
 describe('Test videos history API validator', function () {
-  let path: string
+  let watchingPath: string
+  let myHistoryPath = '/api/v1/users/me/history/videos'
+  let myHistoryRemove = myHistoryPath + '/remove'
   let server: ServerInfo
 
   // ---------------------------------------------------------------
@@ -33,14 +38,14 @@ describe('Test videos history API validator', function () {
     const res = await uploadVideo(server.url, server.accessToken, {})
     const videoUUID = res.body.video.uuid
 
-    path = '/api/v1/videos/' + videoUUID + '/watching'
+    watchingPath = '/api/v1/videos/' + videoUUID + '/watching'
   })
 
   describe('When notifying a user is watching a video', function () {
 
     it('Should fail with an unauthenticated user', async function () {
       const fields = { currentTime: 5 }
-      await makePutBodyRequest({ url: server.url, path, fields, statusCodeExpected: 401 })
+      await makePutBodyRequest({ url: server.url, path: watchingPath, fields, statusCodeExpected: 401 })
     })
 
     it('Should fail with an incorrect video id', async function () {
@@ -58,13 +63,68 @@ describe('Test videos history API validator', function () {
 
     it('Should fail with a bad current time', async function () {
       const fields = { currentTime: 'hello' }
-      await makePutBodyRequest({ url: server.url, path, fields, token: server.accessToken, statusCodeExpected: 400 })
+      await makePutBodyRequest({ url: server.url, path: watchingPath, fields, token: server.accessToken, statusCodeExpected: 400 })
     })
 
     it('Should succeed with the correct parameters', async function () {
       const fields = { currentTime: 5 }
 
-      await makePutBodyRequest({ url: server.url, path, fields, token: server.accessToken, statusCodeExpected: 204 })
+      await makePutBodyRequest({ url: server.url, path: watchingPath, fields, token: server.accessToken, statusCodeExpected: 204 })
+    })
+  })
+
+  describe('When listing user videos history', function () {
+    it('Should fail with a bad start pagination', async function () {
+      await checkBadStartPagination(server.url, myHistoryPath, server.accessToken)
+    })
+
+    it('Should fail with a bad count pagination', async function () {
+      await checkBadCountPagination(server.url, myHistoryPath, server.accessToken)
+    })
+
+    it('Should fail with an unauthenticated user', async function () {
+      await makeGetRequest({ url: server.url, path: myHistoryPath, statusCodeExpected: 401 })
+    })
+
+    it('Should succeed with the correct params', async function () {
+      await makeGetRequest({ url: server.url, token: server.accessToken, path: myHistoryPath, statusCodeExpected: 200 })
+    })
+  })
+
+  describe('When removing user videos history', function () {
+    it('Should fail with an unauthenticated user', async function () {
+      await makePostBodyRequest({ url: server.url, path: myHistoryPath + '/remove', statusCodeExpected: 401 })
+    })
+
+    it('Should fail with a bad beforeDate parameter', async function () {
+      const body = { beforeDate: '15' }
+      await makePostBodyRequest({
+        url: server.url,
+        token: server.accessToken,
+        path: myHistoryRemove,
+        fields: body,
+        statusCodeExpected: 400
+      })
+    })
+
+    it('Should succeed with a valid beforeDate param', async function () {
+      const body = { beforeDate: new Date().toISOString() }
+      await makePostBodyRequest({
+        url: server.url,
+        token: server.accessToken,
+        path: myHistoryRemove,
+        fields: body,
+        statusCodeExpected: 204
+      })
+    })
+
+    it('Should succeed without body', async function () {
+      await makePostBodyRequest({
+        url: server.url,
+        token: server.accessToken,
+        path: myHistoryRemove,
+        statusCodeExpected: 204
+      })
     })
   })
 
index 699f135c79410c1c010be94c0f4aaed83da0375b..f26b91435e5499d52c3fba1ffad41df3c544bc68 100644 (file)
@@ -8,9 +8,13 @@ import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enu
 import {
   createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest,
   makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin
-} from '../../utils'
-import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
-import { getAccountsList } from '../../utils/users/accounts'
+} from '../../../../shared/utils'
+import {
+  checkBadCountPagination,
+  checkBadSortPagination,
+  checkBadStartPagination
+} from '../../../../shared/utils/requests/check-api-params'
+import { getAccountsList } from '../../../../shared/utils/users/accounts'
 
 const expect = chai.expect
 
@@ -316,10 +320,15 @@ describe('Test videos API validator', function () {
 
     it('Should fail without an incorrect input file', async function () {
       const fields = baseCorrectParams
-      const attaches = {
+      let attaches = {
         'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short_fake.webm')
       }
       await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
+
+      attaches = {
+        'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mkv')
+      }
+      await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches })
     })
 
     it('Should fail with an incorrect thumbnail file', async function () {
index a8a2f305fd5a91c2b2c3413f11e7838faa5c0521..9d3ce815339f82e8fe4ad8e37cda9f05ac0eebc3 100644 (file)
@@ -18,15 +18,16 @@ import {
   wait,
   waitUntilLog,
   checkVideoFilesWereRemoved, removeVideo, getVideoWithToken
-} from '../../utils'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+
 import * as magnetUtil from 'magnet-uri'
-import { updateRedundancy } from '../../utils/server/redundancy'
+import { updateRedundancy } from '../../../../shared/utils/server/redundancy'
 import { ActorFollow } from '../../../../shared/models/actors'
 import { readdir } from 'fs-extra'
 import { join } from 'path'
 import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy'
-import { getStats } from '../../utils/server/stats'
+import { getStats } from '../../../../shared/utils/server/stats'
 import { ServerStats } from '../../../../shared/models/server/server-stats.model'
 
 const expect = chai.expect
@@ -136,7 +137,7 @@ async function check2Webseeds (strategy: VideoRedundancyStrategy, videoUUID?: st
   if (!videoUUID) videoUUID = video1Server2UUID
 
   const webseeds = [
-    'http://localhost:9001/static/webseed/' + videoUUID,
+    'http://localhost:9001/static/redundancy/' + videoUUID,
     'http://localhost:9002/static/webseed/' + videoUUID
   ]
 
@@ -148,20 +149,23 @@ async function check2Webseeds (strategy: VideoRedundancyStrategy, videoUUID?: st
     for (const file of video.files) {
       checkMagnetWebseeds(file, webseeds, server)
 
-      // Only servers 1 and 2 have the video
-      if (server.serverNumber !== 3) {
-        await makeGetRequest({
-          url: server.url,
-          statusCodeExpected: 200,
-          path: '/static/webseed/' + `${videoUUID}-${file.resolution.id}.mp4`,
-          contentType: null
-        })
-      }
+      await makeGetRequest({
+        url: servers[0].url,
+        statusCodeExpected: 200,
+        path: '/static/redundancy/' + `${videoUUID}-${file.resolution.id}.mp4`,
+        contentType: null
+      })
+      await makeGetRequest({
+        url: servers[1].url,
+        statusCodeExpected: 200,
+        path: '/static/webseed/' + `${videoUUID}-${file.resolution.id}.mp4`,
+        contentType: null
+      })
     }
   }
 
-  for (const directory of [ 'test1', 'test2' ]) {
-    const files = await readdir(join(root(), directory, 'videos'))
+  for (const directory of [ 'test1/redundancy', 'test2/videos' ]) {
+    const files = await readdir(join(root(), directory))
     expect(files).to.have.length.at.least(4)
 
     for (const resolution of [ 240, 360, 480, 720 ]) {
index a287c5bdf64354e9fb940fdf1de7db62345ab093..a411e973bdc1bca4058860a1796e100895ac9466 100644 (file)
@@ -17,10 +17,10 @@ import {
   uploadVideo,
   userLogin,
   wait
-} from '../../utils'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { VideoChannel } from '../../../../shared/models/videos'
-import { searchVideoChannel } from '../../utils/search/video-channels'
+import { searchVideoChannel } from '../../../../shared/utils/search/video-channels'
 
 const expect = chai.expect
 
index 28f4fac50d2c680c346182e03d212f9a16cbf6b1..f881917e75403f49db9b538e49be43824b4cdb4d 100644 (file)
@@ -16,8 +16,8 @@ import {
   uploadVideo,
   wait,
   searchVideo
-} from '../../utils'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { Video, VideoPrivacy } from '../../../../shared/models/videos'
 
 const expect = chai.expect
index f1392ffea7a7b25b3fb316d17fe44298aef35bf5..50da837da8c974325c4186d67479bdca8050b799 100644 (file)
@@ -13,7 +13,7 @@ import {
   uploadVideo,
   wait,
   immutableAssign
-} from '../../utils'
+} from '../../../../shared/utils'
 
 const expect = chai.expect
 
index facd1688d33998090ca71b8a19dec141c7052c68..bebfc739865bb1ca50b54bc2b7b6192a2dec7c89 100644 (file)
@@ -4,8 +4,11 @@ import 'mocha'
 import * as chai from 'chai'
 import { About } from '../../../../shared/models/server/about.model'
 import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
-import { deleteCustomConfig, getAbout, killallServers, reRunServer } from '../../utils'
 import {
+  deleteCustomConfig,
+  getAbout,
+  killallServers,
+  reRunServer,
   flushTests,
   getConfig,
   getCustomConfig,
@@ -13,7 +16,8 @@ import {
   runServer,
   setAccessTokensToServers,
   updateCustomConfig
-} from '../../utils/index'
+} from '../../../../shared/utils'
+import { ServerConfig } from '../../../../shared/models'
 
 const expect = chai.expect
 
@@ -29,17 +33,24 @@ function checkInitialConfig (data: CustomConfig) {
   expect(data.instance.defaultNSFWPolicy).to.equal('display')
   expect(data.instance.customizations.css).to.be.empty
   expect(data.instance.customizations.javascript).to.be.empty
+
   expect(data.services.twitter.username).to.equal('@Chocobozzz')
   expect(data.services.twitter.whitelisted).to.be.false
+
   expect(data.cache.previews.size).to.equal(1)
   expect(data.cache.captions.size).to.equal(1)
+
   expect(data.signup.enabled).to.be.true
   expect(data.signup.limit).to.equal(4)
   expect(data.signup.requiresEmailVerification).to.be.false
+
   expect(data.admin.email).to.equal('admin1@example.com')
+  expect(data.contactForm.enabled).to.be.true
+
   expect(data.user.videoQuota).to.equal(5242880)
   expect(data.user.videoQuotaDaily).to.equal(-1)
   expect(data.transcoding.enabled).to.be.false
+  expect(data.transcoding.allowAdditionalExtensions).to.be.false
   expect(data.transcoding.threads).to.equal(2)
   expect(data.transcoding.resolutions['240p']).to.be.true
   expect(data.transcoding.resolutions['360p']).to.be.true
@@ -59,23 +70,32 @@ function checkUpdatedConfig (data: CustomConfig) {
   expect(data.instance.defaultNSFWPolicy).to.equal('blur')
   expect(data.instance.customizations.javascript).to.equal('alert("coucou")')
   expect(data.instance.customizations.css).to.equal('body { background-color: red; }')
+
   expect(data.services.twitter.username).to.equal('@Kuja')
   expect(data.services.twitter.whitelisted).to.be.true
+
   expect(data.cache.previews.size).to.equal(2)
   expect(data.cache.captions.size).to.equal(3)
+
   expect(data.signup.enabled).to.be.false
   expect(data.signup.limit).to.equal(5)
   expect(data.signup.requiresEmailVerification).to.be.true
+
   expect(data.admin.email).to.equal('superadmin1@example.com')
+  expect(data.contactForm.enabled).to.be.false
+
   expect(data.user.videoQuota).to.equal(5242881)
   expect(data.user.videoQuotaDaily).to.equal(318742)
+
   expect(data.transcoding.enabled).to.be.true
   expect(data.transcoding.threads).to.equal(1)
+  expect(data.transcoding.allowAdditionalExtensions).to.be.true
   expect(data.transcoding.resolutions['240p']).to.be.false
   expect(data.transcoding.resolutions['360p']).to.be.true
   expect(data.transcoding.resolutions['480p']).to.be.true
   expect(data.transcoding.resolutions['720p']).to.be.false
   expect(data.transcoding.resolutions['1080p']).to.be.false
+
   expect(data.import.videos.http.enabled).to.be.false
   expect(data.import.videos.torrent.enabled).to.be.false
 }
@@ -93,7 +113,7 @@ describe('Test config', function () {
 
   it('Should have a correct config on a server with registration enabled', async function () {
     const res = await getConfig(server.url)
-    const data = res.body
+    const data: ServerConfig = res.body
 
     expect(data.signup.allowed).to.be.true
   })
@@ -108,11 +128,23 @@ describe('Test config', function () {
     ])
 
     const res = await getConfig(server.url)
-    const data = res.body
+    const data: ServerConfig = res.body
 
     expect(data.signup.allowed).to.be.false
   })
 
+  it('Should have the correct video allowed extensions', async function () {
+    const res = await getConfig(server.url)
+    const data: ServerConfig = res.body
+
+    expect(data.video.file.extensions).to.have.lengthOf(3)
+    expect(data.video.file.extensions).to.contain('.mp4')
+    expect(data.video.file.extensions).to.contain('.webm')
+    expect(data.video.file.extensions).to.contain('.ogv')
+
+    expect(data.contactForm.enabled).to.be.true
+  })
+
   it('Should get the customized configuration', async function () {
     const res = await getCustomConfig(server.url, server.accessToken)
     const data = res.body as CustomConfig
@@ -156,12 +188,16 @@ describe('Test config', function () {
       admin: {
         email: 'superadmin1@example.com'
       },
+      contactForm: {
+        enabled: false
+      },
       user: {
         videoQuota: 5242881,
         videoQuotaDaily: 318742
       },
       transcoding: {
         enabled: true,
+        allowAdditionalExtensions: true,
         threads: 1,
         resolutions: {
           '240p': false,
@@ -190,6 +226,18 @@ describe('Test config', function () {
     checkUpdatedConfig(data)
   })
 
+  it('Should have the correct updated video allowed extensions', async function () {
+    const res = await getConfig(server.url)
+    const data: ServerConfig = res.body
+
+    expect(data.video.file.extensions).to.have.length.above(3)
+    expect(data.video.file.extensions).to.contain('.mp4')
+    expect(data.video.file.extensions).to.contain('.webm')
+    expect(data.video.file.extensions).to.contain('.ogv')
+    expect(data.video.file.extensions).to.contain('.flv')
+    expect(data.video.file.extensions).to.contain('.mkv')
+  })
+
   it('Should have the configuration updated after a restart', async function () {
     this.timeout(10000)
 
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts
new file mode 100644 (file)
index 0000000..93221d0
--- /dev/null
@@ -0,0 +1,86 @@
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import { flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, wait } from '../../../../shared/utils'
+import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+import { sendContactForm } from '../../../../shared/utils/server/contact-form'
+
+const expect = chai.expect
+
+describe('Test contact form', function () {
+  let server: ServerInfo
+  const emails: object[] = []
+
+  before(async function () {
+    this.timeout(30000)
+
+    await MockSmtpServer.Instance.collectEmails(emails)
+
+    await flushTests()
+
+    const overrideConfig = {
+      smtp: {
+        hostname: 'localhost'
+      }
+    }
+    server = await runServer(1, overrideConfig)
+    await setAccessTokensToServers([ server ])
+  })
+
+  it('Should send a contact form', async function () {
+    this.timeout(10000)
+
+    await sendContactForm({
+      url: server.url,
+      fromEmail: 'toto@example.com',
+      body: 'my super message',
+      fromName: 'Super toto'
+    })
+
+    await waitJobs(server)
+
+    expect(emails).to.have.lengthOf(1)
+
+    const email = emails[0]
+
+    expect(email['from'][0]['address']).equal('toto@example.com')
+    expect(email['to'][0]['address']).equal('admin1@example.com')
+    expect(email['subject']).contains('Contact form')
+    expect(email['text']).contains('my super message')
+  })
+
+  it('Should not be able to send another contact form because of the anti spam checker', async function () {
+    await sendContactForm({
+      url: server.url,
+      fromEmail: 'toto@example.com',
+      body: 'my super message',
+      fromName: 'Super toto'
+    })
+
+    await sendContactForm({
+      url: server.url,
+      fromEmail: 'toto@example.com',
+      body: 'my super message',
+      fromName: 'Super toto',
+      expectedStatus: 403
+    })
+  })
+
+  it('Should be able to send another contact form after a while', async function () {
+    await wait(1000)
+
+    await sendContactForm({
+      url: server.url,
+      fromEmail: 'toto@example.com',
+      body: 'my super message',
+      fromName: 'Super toto'
+    })
+  })
+
+  after(async function () {
+    MockSmtpServer.Instance.kill()
+    killallServers([ server ])
+  })
+})
index 713a27143123a6e621ec2b704c40835196540e18..f96c57b6636721360a23e2d1145d7fe2602c413b 100644 (file)
@@ -14,11 +14,14 @@ import {
   unblockUser,
   uploadVideo,
   userLogin,
-  verifyEmail
-} from '../../utils'
-import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../utils/index'
-import { mockSmtpServer } from '../../utils/miscs/email'
-import { waitJobs } from '../../utils/server/jobs'
+  verifyEmail,
+  flushTests,
+  killallServers,
+  ServerInfo,
+  setAccessTokensToServers
+} from '../../../../shared/utils'
+import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
@@ -38,7 +41,7 @@ describe('Test emails', function () {
   before(async function () {
     this.timeout(30000)
 
-    await mockSmtpServer(emails)
+    await MockSmtpServer.Instance.collectEmails(emails)
 
     await flushTests()
 
@@ -248,6 +251,7 @@ describe('Test emails', function () {
   })
 
   after(async function () {
+    MockSmtpServer.Instance.kill()
     killallServers([ server ])
   })
 })
index 3135fc5682520a176ea65dbc4647be1edbac786f..8bb073c413061b0d6826dd02d0eff3764652b896 100644 (file)
@@ -2,11 +2,21 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { doubleFollow, getAccountVideos, getVideo, getVideoChannelVideos, getVideoWithToken } from '../../utils'
-import { flushAndRunMultipleServers, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils/index'
-import { unfollow } from '../../utils/server/follows'
-import { userLogin } from '../../utils/users/login'
-import { createUser } from '../../utils/users/users'
+import {
+  doubleFollow,
+  getAccountVideos,
+  getVideo,
+  getVideoChannelVideos,
+  getVideoWithToken,
+  flushAndRunMultipleServers,
+  killallServers,
+  ServerInfo,
+  setAccessTokensToServers,
+  uploadVideo
+} from '../../../../shared/utils'
+import { unfollow } from '../../../../shared/utils/server/follows'
+import { userLogin } from '../../../../shared/utils/users/login'
+import { createUser } from '../../../../shared/utils/users/users'
 
 const expect = chai.expect
 
index e80e93e7f5b33e8a5ced35831f1d977334b0bc42..b0fc5d2939e7d7773db31799d5fd7b87ab80e1bf 100644 (file)
@@ -4,7 +4,7 @@ import * as chai from 'chai'
 import 'mocha'
 import { Video, VideoPrivacy } from '../../../../shared/models/videos'
 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
-import { completeVideoCheck } from '../../utils'
+import { completeVideoCheck } from '../../../../shared/utils'
 import {
   flushAndRunMultipleServers,
   getVideosList,
@@ -12,21 +12,26 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
-} from '../../utils/index'
-import { dateIsValid } from '../../utils/miscs/miscs'
-import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort, unfollow } from '../../utils/server/follows'
-import { expectAccountFollows } from '../../utils/users/accounts'
-import { userLogin } from '../../utils/users/login'
-import { createUser } from '../../utils/users/users'
+} from '../../../../shared/utils/index'
+import { dateIsValid } from '../../../../shared/utils/miscs/miscs'
+import {
+  follow,
+  getFollowersListPaginationAndSort,
+  getFollowingListPaginationAndSort,
+  unfollow
+} from '../../../../shared/utils/server/follows'
+import { expectAccountFollows } from '../../../../shared/utils/users/accounts'
+import { userLogin } from '../../../../shared/utils/users/login'
+import { createUser } from '../../../../shared/utils/users/users'
 import {
   addVideoCommentReply,
   addVideoCommentThread,
   getVideoCommentThreads,
   getVideoThreadComments
-} from '../../utils/videos/video-comments'
-import { rateVideo } from '../../utils/videos/videos'
-import { waitJobs } from '../../utils/server/jobs'
-import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../utils/videos/video-captions'
+} from '../../../../shared/utils/videos/video-comments'
+import { rateVideo } from '../../../../shared/utils/videos/videos'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/utils/videos/video-captions'
 import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
 
 const expect = chai.expect
index 0421b2b40eeeeeec281b52b9f5ded9b908fcdaac..cd7baadad6a1c499b8058e0feaade8baa915b5a7 100644 (file)
@@ -5,24 +5,30 @@ import 'mocha'
 import { JobState, Video } from '../../../../shared/models'
 import { VideoPrivacy } from '../../../../shared/models/videos'
 import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
-import { completeVideoCheck, getVideo, immutableAssign, reRunServer, unfollow, updateVideo, viewVideo } from '../../utils'
+
 import {
+  completeVideoCheck,
   flushAndRunMultipleServers,
+  getVideo,
   getVideosList,
+  immutableAssign,
   killallServers,
+  reRunServer,
   ServerInfo,
   setAccessTokensToServers,
+  unfollow,
+  updateVideo,
   uploadVideo,
   wait
-} from '../../utils/index'
-import { follow, getFollowersListPaginationAndSort } from '../../utils/server/follows'
-import { getJobsListPaginationAndSort, waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { follow, getFollowersListPaginationAndSort } from '../../../../shared/utils/server/follows'
+import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/utils/server/jobs'
 import {
   addVideoCommentReply,
   addVideoCommentThread,
   getVideoCommentThreads,
   getVideoThreadComments
-} from '../../utils/videos/video-comments'
+} from '../../../../shared/utils/videos/video-comments'
 
 const expect = chai.expect
 
index 6afcab1f96f173f2d3622affca26808a41341622..1f80cc6cf5ee8445fb10875fcccf5362c4f55208 100644 (file)
@@ -1,4 +1,5 @@
 import './config'
+import './contact-form'
 import './email'
 import './follow-constraints'
 import './follows'
index cd59d9a1bc913cce365f3e327ace640d0854d55d..52948b1d64164dfef47f84c2e5204bc35276724f 100644 (file)
@@ -2,12 +2,12 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { killallServers, ServerInfo, setAccessTokensToServers } from '../../utils/index'
-import { doubleFollow } from '../../utils/server/follows'
-import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../utils/server/jobs'
-import { flushAndRunMultipleServers } from '../../utils/server/servers'
-import { uploadVideo } from '../../utils/videos/videos'
-import { dateIsValid } from '../../utils/miscs/miscs'
+import { killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/utils/index'
+import { doubleFollow } from '../../../../shared/utils/server/follows'
+import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/utils/server/jobs'
+import { flushAndRunMultipleServers } from '../../../../shared/utils/server/servers'
+import { uploadVideo } from '../../../../shared/utils/videos/videos'
+import { dateIsValid } from '../../../../shared/utils/miscs/miscs'
 
 const expect = chai.expect
 
index 6d6ce8532a761a14e7a21342993d051259dee201..3b95ce945bab0dd456df09f458cf911b3bafa662 100644 (file)
@@ -4,8 +4,8 @@ import {
   flushTests,
   killallServers,
   ServerInfo
-} from '../../utils/index'
-import { runServer } from '../../utils/server/servers'
+} from '../../../../shared/utils'
+import { runServer } from '../../../../shared/utils/server/servers'
 
 describe('Start and stop server without web client routes', function () {
   let server: ServerInfo
index e2c2a293e97e1c323dbef21a376cb6d25cdd6a18..d4c08c3467c82115a64f787ae72fe856a9f6998b 100644 (file)
@@ -15,7 +15,7 @@ import {
   userLogin,
   viewVideo,
   wait
-} from '../../utils'
+} from '../../../../shared/utils'
 const expect = chai.expect
 
 import {
@@ -23,7 +23,7 @@ import {
   flushTests,
   runServer,
   registerUser, getCustomConfig, setAccessTokensToServers, updateCustomConfig
-} from '../../utils/index'
+} from '../../../../shared/utils/index'
 
 describe('Test application behind a reverse proxy', function () {
   let server = null
index cb229e876f8fd8d1c5170e4cfc5543ee0d59b34c..aaa6c62f7d2a720ff1b7517228514e5b43038662 100644 (file)
@@ -13,11 +13,11 @@ import {
   uploadVideo,
   viewVideo,
   wait
-} from '../../utils'
-import { flushTests, setAccessTokensToServers } from '../../utils/index'
-import { getStats } from '../../utils/server/stats'
-import { addVideoCommentThread } from '../../utils/videos/video-comments'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index'
+import { getStats } from '../../../../shared/utils/server/stats'
+import { addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
@@ -39,7 +39,7 @@ describe('Test stats (excluding redundancy)', function () {
     }
     await createUser(servers[0].url, servers[0].accessToken, user.username, user.password)
 
-    const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, {})
+    const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' })
     const videoUUID = resVideo.body.video.uuid
 
     await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, 'comment')
@@ -60,6 +60,7 @@ describe('Test stats (excluding redundancy)', function () {
     expect(data.totalLocalVideoComments).to.equal(1)
     expect(data.totalLocalVideos).to.equal(1)
     expect(data.totalLocalVideoViews).to.equal(1)
+    expect(data.totalLocalVideoFilesSize).to.equal(218910)
     expect(data.totalUsers).to.equal(2)
     expect(data.totalVideoComments).to.equal(1)
     expect(data.totalVideos).to.equal(1)
@@ -74,6 +75,7 @@ describe('Test stats (excluding redundancy)', function () {
     expect(data.totalLocalVideoComments).to.equal(0)
     expect(data.totalLocalVideos).to.equal(0)
     expect(data.totalLocalVideoViews).to.equal(0)
+    expect(data.totalLocalVideoFilesSize).to.equal(0)
     expect(data.totalUsers).to.equal(1)
     expect(data.totalVideoComments).to.equal(1)
     expect(data.totalVideos).to.equal(1)
index 856f2f4d10676ffcfa61ce62717735ee01cb6f37..25ca00029bc9fc8d94731b27be1b9daf257f2257 100644 (file)
@@ -2,8 +2,8 @@
 
 import * as magnetUtil from 'magnet-uri'
 import 'mocha'
-import { getVideo, killallServers, runServer, ServerInfo, uploadVideo } from '../../utils'
-import { flushTests, setAccessTokensToServers } from '../../utils/index'
+import { getVideo, killallServers, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils'
+import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index'
 import { VideoDetails } from '../../../../shared/models/videos'
 import * as WebTorrent from 'webtorrent'
 
index eed4b9f3efb94f6c52585cc2fbdaf07881ac524f..4bca27a94985d446e04aa5646e500907290733a2 100644 (file)
@@ -12,16 +12,16 @@ import {
   ServerInfo,
   uploadVideo,
   userLogin
-} from '../../utils/index'
-import { setAccessTokensToServers } from '../../utils/users/login'
-import { getVideosListWithToken, getVideosList } from '../../utils/videos/videos'
+} from '../../../../shared/utils/index'
+import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
+import { getVideosListWithToken, getVideosList } from '../../../../shared/utils/videos/videos'
 import {
   addVideoCommentReply,
   addVideoCommentThread,
   getVideoCommentThreads,
   getVideoThreadComments
-} from '../../utils/videos/video-comments'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils/videos/video-comments'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
 import {
   addAccountToAccountBlocklist,
@@ -36,7 +36,7 @@ import {
   removeAccountFromServerBlocklist,
   removeServerFromAccountBlocklist,
   removeServerFromServerBlocklist
-} from '../../utils/users/blocklist'
+} from '../../../../shared/utils/users/blocklist'
 
 const expect = chai.expect
 
index ff433315d38762071e2b256c95273d1840c7c722..52ba6984eb7cdd96dfaa5adfaec066af9e785351 100644 (file)
@@ -1,5 +1,6 @@
+import './users-verification'
+import './user-notifications'
 import './blocklist'
 import './user-subscriptions'
 import './users'
 import './users-multiple-servers'
-import './users-verification'
diff --git a/server/tests/api/users/user-notifications.ts b/server/tests/api/users/user-notifications.ts
new file mode 100644 (file)
index 0000000..5260d64
--- /dev/null
@@ -0,0 +1,1017 @@
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import {
+  addVideoToBlacklist,
+  createUser,
+  doubleFollow,
+  flushAndRunMultipleServers,
+  flushTests,
+  getMyUserInformation,
+  immutableAssign,
+  registerUser,
+  removeVideoFromBlacklist,
+  reportVideoAbuse,
+  updateMyUser,
+  updateVideo,
+  updateVideoChannel,
+  userLogin,
+  wait
+} from '../../../../shared/utils'
+import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index'
+import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+import { getUserNotificationSocket } from '../../../../shared/utils/socket/socket-io'
+import {
+  checkCommentMention,
+  CheckerBaseParams,
+  checkMyVideoImportIsFinished,
+  checkNewActorFollow,
+  checkNewBlacklistOnMyVideo,
+  checkNewCommentOnMyVideo,
+  checkNewVideoAbuseForModerators,
+  checkNewVideoFromSubscription,
+  checkUserRegistered,
+  checkVideoIsPublished,
+  getLastNotification,
+  getUserNotifications,
+  markAsReadNotifications,
+  updateMyNotificationSettings,
+  markAsReadAllNotifications
+} from '../../../../shared/utils/users/user-notifications'
+import {
+  User,
+  UserNotification,
+  UserNotificationSetting,
+  UserNotificationSettingValue,
+  UserNotificationType
+} from '../../../../shared/models/users'
+import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
+import { addUserSubscription, removeUserSubscription } from '../../../../shared/utils/users/user-subscriptions'
+import { VideoPrivacy } from '../../../../shared/models/videos'
+import { getBadVideoUrl, getYoutubeVideoUrl, importVideo } from '../../../../shared/utils/videos/video-imports'
+import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments'
+import * as uuidv4 from 'uuid/v4'
+import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/utils/users/blocklist'
+
+const expect = chai.expect
+
+async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalParams: any = {}) {
+  const name = 'remote video ' + uuidv4()
+
+  const data = Object.assign({ name }, additionalParams)
+  const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data)
+
+  await waitJobs(servers)
+
+  return { uuid: res.body.video.uuid, name }
+}
+
+async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParams: any = {}) {
+  const name = 'local video ' + uuidv4()
+
+  const data = Object.assign({ name }, additionalParams)
+  const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data)
+
+  await waitJobs(servers)
+
+  return { uuid: res.body.video.uuid, name }
+}
+
+describe('Test users notifications', function () {
+  let servers: ServerInfo[] = []
+  let userAccessToken: string
+  let userNotifications: UserNotification[] = []
+  let adminNotifications: UserNotification[] = []
+  let adminNotificationsServer2: UserNotification[] = []
+  const emails: object[] = []
+  let channelId: number
+
+  const allNotificationSettings: UserNotificationSetting = {
+    newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL,
+    newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
+  }
+
+  before(async function () {
+    this.timeout(120000)
+
+    await MockSmtpServer.Instance.collectEmails(emails)
+
+    await flushTests()
+
+    const overrideConfig = {
+      smtp: {
+        hostname: 'localhost'
+      }
+    }
+    servers = await flushAndRunMultipleServers(2, overrideConfig)
+
+    // Get the access tokens
+    await setAccessTokensToServers(servers)
+
+    // Server 1 and server 2 follow each other
+    await doubleFollow(servers[0], servers[1])
+
+    await waitJobs(servers)
+
+    const user = {
+      username: 'user_1',
+      password: 'super password'
+    }
+    await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000)
+    userAccessToken = await userLogin(servers[0], user)
+
+    await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings)
+    await updateMyNotificationSettings(servers[0].url, servers[0].accessToken, allNotificationSettings)
+    await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings)
+
+    {
+      const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken)
+      socket.on('new-notification', n => userNotifications.push(n))
+    }
+    {
+      const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken)
+      socket.on('new-notification', n => adminNotifications.push(n))
+    }
+    {
+      const socket = getUserNotificationSocket(servers[ 1 ].url, servers[1].accessToken)
+      socket.on('new-notification', n => adminNotificationsServer2.push(n))
+    }
+
+    {
+      const resChannel = await getMyUserInformation(servers[0].url, servers[0].accessToken)
+      channelId = resChannel.body.videoChannels[0].id
+    }
+  })
+
+  describe('New video from my subscription notification', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: userNotifications,
+        token: userAccessToken
+      }
+    })
+
+    it('Should not send notifications if the user does not follow the video publisher', async function () {
+      await uploadVideoByLocalAccount(servers)
+
+      const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
+      expect(notification).to.be.undefined
+
+      expect(emails).to.have.lengthOf(0)
+      expect(userNotifications).to.have.lengthOf(0)
+    })
+
+    it('Should send a new video notification if the user follows the local video publisher', async function () {
+      this.timeout(15000)
+
+      await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9001')
+      await waitJobs(servers)
+
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a new video notification from a remote account', async function () {
+      this.timeout(50000) // Server 2 has transcoding enabled
+
+      await addUserSubscription(servers[0].url, userAccessToken, 'root_channel@localhost:9002')
+      await waitJobs(servers)
+
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a new video notification on a scheduled publication', async function () {
+      this.timeout(20000)
+
+      // In 2 seconds
+      let updateAt = new Date(new Date().getTime() + 2000)
+
+      const data = {
+        privacy: VideoPrivacy.PRIVATE,
+        scheduleUpdate: {
+          updateAt: updateAt.toISOString(),
+          privacy: VideoPrivacy.PUBLIC
+        }
+      }
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
+
+      await wait(6000)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a new video notification on a remote scheduled publication', async function () {
+      this.timeout(20000)
+
+      // In 2 seconds
+      let updateAt = new Date(new Date().getTime() + 2000)
+
+      const data = {
+        privacy: VideoPrivacy.PRIVATE,
+        scheduleUpdate: {
+          updateAt: updateAt.toISOString(),
+          privacy: VideoPrivacy.PUBLIC
+        }
+      }
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
+      await waitJobs(servers)
+
+      await wait(6000)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should not send a notification before the video is published', async function () {
+      this.timeout(20000)
+
+      let updateAt = new Date(new Date().getTime() + 100000)
+
+      const data = {
+        privacy: VideoPrivacy.PRIVATE,
+        scheduleUpdate: {
+          updateAt: updateAt.toISOString(),
+          privacy: VideoPrivacy.PUBLIC
+        }
+      }
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
+
+      await wait(6000)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
+    })
+
+    it('Should send a new video notification when a video becomes public', async function () {
+      this.timeout(10000)
+
+      const data = { privacy: VideoPrivacy.PRIVATE }
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
+
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
+
+      await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
+
+      await wait(500)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a new video notification when a remote video becomes public', async function () {
+      this.timeout(20000)
+
+      const data = { privacy: VideoPrivacy.PRIVATE }
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
+
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
+
+      await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.PUBLIC })
+
+      await waitJobs(servers)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should not send a new video notification when a video becomes unlisted', async function () {
+      this.timeout(20000)
+
+      const data = { privacy: VideoPrivacy.PRIVATE }
+      const { name, uuid } = await uploadVideoByLocalAccount(servers, data)
+
+      await updateVideo(servers[0].url, servers[0].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
+
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
+    })
+
+    it('Should not send a new video notification when a remote video becomes unlisted', async function () {
+      this.timeout(20000)
+
+      const data = { privacy: VideoPrivacy.PRIVATE }
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
+
+      await updateVideo(servers[1].url, servers[1].accessToken, uuid, { privacy: VideoPrivacy.UNLISTED })
+
+      await waitJobs(servers)
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'absence')
+    })
+
+    it('Should send a new video notification after a video import', async function () {
+      this.timeout(30000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PUBLIC,
+        targetUrl: getYoutubeVideoUrl()
+      }
+      const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+  })
+
+  describe('Comment on my video notifications', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: userNotifications,
+        token: userAccessToken
+      }
+    })
+
+    it('Should not send a new comment notification after a comment on another video', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
+    })
+
+    it('Should not send a new comment notification if I comment my own video', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, uuid, 'comment')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
+    })
+
+    it('Should not send a new comment notification if the account is muted', async function () {
+      this.timeout(10000)
+
+      await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence')
+
+      await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
+    })
+
+    it('Should send a new comment notification after a local comment on my video', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence')
+    })
+
+    it('Should send a new comment notification after a remote comment on my video', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      await waitJobs(servers)
+
+      const resComment = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment')
+      const commentId = resComment.body.comment.id
+
+      await waitJobs(servers)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence')
+    })
+
+    it('Should send a new comment notification after a local reply on my video', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resThread = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, 'comment')
+      const threadId = resThread.body.comment.id
+
+      const resComment = await addVideoCommentReply(servers[0].url, servers[0].accessToken, uuid, threadId, 'reply')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, threadId, 'presence')
+    })
+
+    it('Should send a new comment notification after a remote reply on my video', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+      await waitJobs(servers)
+
+      const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment')
+      const threadId = resThread.body.comment.id
+
+      const resComment = await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, 'reply')
+      const commentId = resComment.body.comment.id
+
+      await waitJobs(servers)
+      await checkNewCommentOnMyVideo(baseParams, uuid, commentId, threadId, 'presence')
+    })
+  })
+
+  describe('Mention notifications', function () {
+    let baseParams: CheckerBaseParams
+
+    before(async () => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: userNotifications,
+        token: userAccessToken
+      }
+
+      await updateMyUser({
+        url: servers[0].url,
+        accessToken: servers[0].accessToken,
+        displayName: 'super root name'
+      })
+
+      await updateMyUser({
+        url: servers[1].url,
+        accessToken: servers[1].accessToken,
+        displayName: 'super root 2 name'
+      })
+    })
+
+    it('Should not send a new mention comment notification if I mention the video owner', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
+    })
+
+    it('Should not send a new mention comment notification if I mention myself', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, userAccessToken, uuid, '@user_1 hello')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
+    })
+
+    it('Should not send a new mention notification if the account is muted', async function () {
+      this.timeout(10000)
+
+      await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
+
+      const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence')
+
+      await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root')
+    })
+
+    it('Should send a new mention notification after local comments', async function () {
+      this.timeout(10000)
+
+      const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      const resThread = await addVideoCommentThread(servers[0].url, servers[0].accessToken, uuid, '@user_1 hello 1')
+      const threadId = resThread.body.comment.id
+
+      await wait(500)
+      await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root name', 'presence')
+
+      const resComment = await addVideoCommentReply(servers[0].url, servers[0].accessToken, uuid, threadId, 'hello 2 @user_1')
+      const commentId = resComment.body.comment.id
+
+      await wait(500)
+      await checkCommentMention(baseParams, uuid, commentId, threadId, 'super root name', 'presence')
+    })
+
+    it('Should send a new mention notification after remote comments', async function () {
+      this.timeout(20000)
+
+      const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' })
+      const uuid = resVideo.body.video.uuid
+
+      await waitJobs(servers)
+      const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'hello @user_1@localhost:9001 1')
+      const threadId = resThread.body.comment.id
+
+      await waitJobs(servers)
+      await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root 2 name', 'presence')
+
+      const text = '@user_1@localhost:9001 hello 2 @root@localhost:9001'
+      const resComment = await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, text)
+      const commentId = resComment.body.comment.id
+
+      await waitJobs(servers)
+      await checkCommentMention(baseParams, uuid, commentId, threadId, 'super root 2 name', 'presence')
+    })
+  })
+
+  describe('Video abuse for moderators notification' , function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: adminNotifications,
+        token: servers[0].accessToken
+      }
+    })
+
+    it('Should send a notification to moderators on local video abuse', async function () {
+      this.timeout(10000)
+
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
+      const uuid = resVideo.body.video.uuid
+
+      await reportVideoAbuse(servers[0].url, servers[0].accessToken, uuid, 'super reason')
+
+      await waitJobs(servers)
+      await checkNewVideoAbuseForModerators(baseParams, uuid, name, 'presence')
+    })
+
+    it('Should send a notification to moderators on remote video abuse', async function () {
+      this.timeout(10000)
+
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
+      const uuid = resVideo.body.video.uuid
+
+      await waitJobs(servers)
+
+      await reportVideoAbuse(servers[1].url, servers[1].accessToken, uuid, 'super reason')
+
+      await waitJobs(servers)
+      await checkNewVideoAbuseForModerators(baseParams, uuid, name, 'presence')
+    })
+  })
+
+  describe('Video blacklist on my video', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: userNotifications,
+        token: userAccessToken
+      }
+    })
+
+    it('Should send a notification to video owner on blacklist', async function () {
+      this.timeout(10000)
+
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
+      const uuid = resVideo.body.video.uuid
+
+      await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
+
+      await waitJobs(servers)
+      await checkNewBlacklistOnMyVideo(baseParams, uuid, name, 'blacklist')
+    })
+
+    it('Should send a notification to video owner on unblacklist', async function () {
+      this.timeout(10000)
+
+      const name = 'video for abuse ' + uuidv4()
+      const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name })
+      const uuid = resVideo.body.video.uuid
+
+      await addVideoToBlacklist(servers[0].url, servers[0].accessToken, uuid)
+
+      await waitJobs(servers)
+      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, uuid)
+      await waitJobs(servers)
+
+      await wait(500)
+      await checkNewBlacklistOnMyVideo(baseParams, uuid, name, 'unblacklist')
+    })
+  })
+
+  describe('My video is published', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[1],
+        emails,
+        socketNotifications: adminNotificationsServer2,
+        token: servers[1].accessToken
+      }
+    })
+
+    it('Should not send a notification if transcoding is not enabled', async function () {
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+      await waitJobs(servers)
+
+      await checkVideoIsPublished(baseParams, name, uuid, 'absence')
+    })
+
+    it('Should not send a notification if the wait transcoding is false', async function () {
+      this.timeout(50000)
+
+      await uploadVideoByRemoteAccount(servers, { waitTranscoding: false })
+      await waitJobs(servers)
+
+      const notification = await getLastNotification(servers[ 0 ].url, userAccessToken)
+      if (notification) {
+        expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED)
+      }
+    })
+
+    it('Should send a notification even if the video is not transcoded in other resolutions', async function () {
+      this.timeout(50000)
+
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, { waitTranscoding: true, fixture: 'video_short_240p.mp4' })
+      await waitJobs(servers)
+
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a notification with a transcoded video', async function () {
+      this.timeout(50000)
+
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, { waitTranscoding: true })
+      await waitJobs(servers)
+
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a notification when an imported video is transcoded', async function () {
+      this.timeout(50000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PUBLIC,
+        targetUrl: getYoutubeVideoUrl(),
+        waitTranscoding: true
+      }
+      const res = await importVideo(servers[1].url, servers[1].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+
+    it('Should send a notification when the scheduled update has been proceeded', async function () {
+      this.timeout(70000)
+
+      // In 2 seconds
+      let updateAt = new Date(new Date().getTime() + 2000)
+
+      const data = {
+        privacy: VideoPrivacy.PRIVATE,
+        scheduleUpdate: {
+          updateAt: updateAt.toISOString(),
+          privacy: VideoPrivacy.PUBLIC
+        }
+      }
+      const { name, uuid } = await uploadVideoByRemoteAccount(servers, data)
+
+      await wait(6000)
+      await checkVideoIsPublished(baseParams, name, uuid, 'presence')
+    })
+  })
+
+  describe('My video is imported', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: adminNotifications,
+        token: servers[0].accessToken
+      }
+    })
+
+    it('Should send a notification when the video import failed', async function () {
+      this.timeout(70000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PRIVATE,
+        targetUrl: getBadVideoUrl()
+      }
+      const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+      await checkMyVideoImportIsFinished(baseParams, name, uuid, getBadVideoUrl(), false, 'presence')
+    })
+
+    it('Should send a notification when the video import succeeded', async function () {
+      this.timeout(70000)
+
+      const name = 'video import ' + uuidv4()
+
+      const attributes = {
+        name,
+        channelId,
+        privacy: VideoPrivacy.PRIVATE,
+        targetUrl: getYoutubeVideoUrl()
+      }
+      const res = await importVideo(servers[0].url, servers[0].accessToken, attributes)
+      const uuid = res.body.video.uuid
+
+      await waitJobs(servers)
+      await checkMyVideoImportIsFinished(baseParams, name, uuid, getYoutubeVideoUrl(), true, 'presence')
+    })
+  })
+
+  describe('New registration', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: adminNotifications,
+        token: servers[0].accessToken
+      }
+    })
+
+    it('Should send a notification only to moderators when a user registers on the instance', async function () {
+      await registerUser(servers[0].url, 'user_45', 'password')
+
+      await waitJobs(servers)
+
+      await checkUserRegistered(baseParams, 'user_45', 'presence')
+
+      const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } }
+      await checkUserRegistered(immutableAssign(baseParams, userOverride), 'user_45', 'absence')
+    })
+  })
+
+  describe('New actor follow', function () {
+    let baseParams: CheckerBaseParams
+    let myChannelName = 'super channel name'
+    let myUserName = 'super user name'
+
+    before(async () => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: userNotifications,
+        token: userAccessToken
+      }
+
+      await updateMyUser({
+        url: servers[0].url,
+        accessToken: servers[0].accessToken,
+        displayName: 'super root name'
+      })
+
+      await updateMyUser({
+        url: servers[0].url,
+        accessToken: userAccessToken,
+        displayName: myUserName
+      })
+
+      await updateMyUser({
+        url: servers[1].url,
+        accessToken: servers[1].accessToken,
+        displayName: 'super root 2 name'
+      })
+
+      await updateVideoChannel(servers[0].url, userAccessToken, 'user_1_channel', { displayName: myChannelName })
+    })
+
+    it('Should notify when a local channel is following one of our channel', async function () {
+      this.timeout(10000)
+
+      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
+      await waitJobs(servers)
+
+      await checkNewActorFollow(baseParams, 'channel', 'root', 'super root name', myChannelName, 'presence')
+
+      await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001')
+    })
+
+    it('Should notify when a remote channel is following one of our channel', async function () {
+      this.timeout(10000)
+
+      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
+      await waitJobs(servers)
+
+      await checkNewActorFollow(baseParams, 'channel', 'root', 'super root 2 name', myChannelName, 'presence')
+
+      await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001')
+    })
+
+    it('Should notify when a local account is following one of our channel', async function () {
+      await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:9001')
+
+      await waitJobs(servers)
+
+      await checkNewActorFollow(baseParams, 'account', 'root', 'super root name', myUserName, 'presence')
+    })
+
+    it('Should notify when a remote account is following one of our channel', async function () {
+      await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:9001')
+
+      await waitJobs(servers)
+
+      await checkNewActorFollow(baseParams, 'account', 'root', 'super root 2 name', myUserName, 'presence')
+    })
+  })
+
+  describe('Mark as read', function () {
+    it('Should mark as read some notifications', async function () {
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3)
+      const ids = res.body.data.map(n => n.id)
+
+      await markAsReadNotifications(servers[ 0 ].url, userAccessToken, ids)
+    })
+
+    it('Should have the notifications marked as read', async function () {
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10)
+
+      const notifications = res.body.data as UserNotification[]
+      expect(notifications[ 0 ].read).to.be.false
+      expect(notifications[ 1 ].read).to.be.false
+      expect(notifications[ 2 ].read).to.be.true
+      expect(notifications[ 3 ].read).to.be.true
+      expect(notifications[ 4 ].read).to.be.true
+      expect(notifications[ 5 ].read).to.be.false
+    })
+
+    it('Should only list read notifications', async function () {
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, false)
+
+      const notifications = res.body.data as UserNotification[]
+      for (const notification of notifications) {
+        expect(notification.read).to.be.true
+      }
+    })
+
+    it('Should only list unread notifications', async function () {
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true)
+
+      const notifications = res.body.data as UserNotification[]
+      for (const notification of notifications) {
+        expect(notification.read).to.be.false
+      }
+    })
+
+    it('Should mark as read all notifications', async function () {
+      await markAsReadAllNotifications(servers[ 0 ].url, userAccessToken)
+
+      const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true)
+
+      expect(res.body.total).to.equal(0)
+      expect(res.body.data).to.have.lengthOf(0)
+    })
+  })
+
+  describe('Notification settings', function () {
+    let baseParams: CheckerBaseParams
+
+    before(() => {
+      baseParams = {
+        server: servers[0],
+        emails,
+        socketNotifications: userNotifications,
+        token: userAccessToken
+      }
+    })
+
+    it('Should not have notifications', async function () {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
+        newVideoFromSubscription: UserNotificationSettingValue.NONE
+      }))
+
+      {
+        const res = await getMyUserInformation(servers[0].url, userAccessToken)
+        const info = res.body as User
+        expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.NONE)
+      }
+
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+
+      const check = { web: true, mail: true }
+      await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
+    })
+
+    it('Should only have web notifications', async function () {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
+        newVideoFromSubscription: UserNotificationSettingValue.WEB
+      }))
+
+      {
+        const res = await getMyUserInformation(servers[0].url, userAccessToken)
+        const info = res.body as User
+        expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.WEB)
+      }
+
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+
+      {
+        const check = { mail: true, web: false }
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
+      }
+
+      {
+        const check = { mail: false, web: true }
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'presence')
+      }
+    })
+
+    it('Should only have mail notifications', async function () {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
+        newVideoFromSubscription: UserNotificationSettingValue.EMAIL
+      }))
+
+      {
+        const res = await getMyUserInformation(servers[0].url, userAccessToken)
+        const info = res.body as User
+        expect(info.notificationSettings.newVideoFromSubscription).to.equal(UserNotificationSettingValue.EMAIL)
+      }
+
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+
+      {
+        const check = { mail: false, web: true }
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'absence')
+      }
+
+      {
+        const check = { mail: true, web: false }
+        await checkNewVideoFromSubscription(immutableAssign(baseParams, { check }), name, uuid, 'presence')
+      }
+    })
+
+    it('Should have email and web notifications', async function () {
+      await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, {
+        newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
+      }))
+
+      {
+        const res = await getMyUserInformation(servers[0].url, userAccessToken)
+        const info = res.body as User
+        expect(info.notificationSettings.newVideoFromSubscription).to.equal(
+          UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL
+        )
+      }
+
+      const { name, uuid } = await uploadVideoByLocalAccount(servers)
+
+      await checkNewVideoFromSubscription(baseParams, name, uuid, 'presence')
+    })
+  })
+
+  after(async function () {
+    MockSmtpServer.Instance.kill()
+
+    killallServers(servers)
+  })
+})
index 65b80540c858210367c2e93386a3ea18c6a67bd8..88a7187d64a0c4443c758e66805a41a032b75f3f 100644 (file)
@@ -2,18 +2,27 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { createUser, doubleFollow, flushAndRunMultipleServers, follow, getVideosList, unfollow, updateVideo, userLogin } from '../../utils'
-import { killallServers, ServerInfo, uploadVideo } from '../../utils/index'
-import { setAccessTokensToServers } from '../../utils/users/login'
+import {
+  createUser,
+  doubleFollow,
+  flushAndRunMultipleServers,
+  follow,
+  getVideosList,
+  unfollow,
+  updateVideo,
+  userLogin
+} from '../../../../shared/utils'
+import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index'
+import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
 import { Video, VideoChannel } from '../../../../shared/models/videos'
-import { waitJobs } from '../../utils/server/jobs'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import {
   addUserSubscription,
   listUserSubscriptions,
   listUserSubscriptionVideos,
   removeUserSubscription,
   getUserSubscription, areSubscriptionsExist
-} from '../../utils/users/user-subscriptions'
+} from '../../../../shared/utils/users/user-subscriptions'
 
 const expect = chai.expect
 
index d8699db17b274986d2137cb3c4bbc6fbb7952ba7..006d6cdf0e17da82d395e476bac0bb935620bd15 100644 (file)
@@ -13,13 +13,13 @@ import {
   removeUser,
   updateMyUser,
   userLogin
-} from '../../utils'
-import { getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../utils/index'
-import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../utils/users/accounts'
-import { setAccessTokensToServers } from '../../utils/users/login'
+} from '../../../../shared/utils'
+import { getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../../../shared/utils/index'
+import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../../../shared/utils/users/accounts'
+import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
 import { User } from '../../../../shared/models/users'
 import { VideoChannel } from '../../../../shared/models/videos'
-import { waitJobs } from '../../utils/server/jobs'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index fa5f5e3717394dc39d85c11a32b2d8c71b06297b..babeda2b892327a66f251d24ed5d990d60f8ae80 100644 (file)
@@ -4,11 +4,11 @@ import * as chai from 'chai'
 import 'mocha'
 import {
   registerUser, flushTests, getUserInformation, getMyUserInformation, killallServers,
-  userLogin, login, runServer, ServerInfo, verifyEmail, updateCustomSubConfig
-} from '../../utils'
-import { setAccessTokensToServers } from '../../utils/users/login'
-import { mockSmtpServer } from '../../utils/miscs/email'
-import { waitJobs } from '../../utils/server/jobs'
+  userLogin, login, runServer, ServerInfo, verifyEmail, updateCustomSubConfig, wait
+} from '../../../../shared/utils'
+import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
+import { MockSmtpServer } from '../../../../shared/utils/miscs/email'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
@@ -30,7 +30,7 @@ describe('Test users account verification', function () {
   before(async function () {
     this.timeout(30000)
 
-    await mockSmtpServer(emails)
+    await MockSmtpServer.Instance.collectEmails(emails)
 
     await flushTests()
 
@@ -123,6 +123,7 @@ describe('Test users account verification', function () {
   })
 
   after(async function () {
+    MockSmtpServer.Instance.kill()
     killallServers([ server ])
 
     // Keep the logs if the test failed
index e7bb845b9bf7f3117c35ebaa5994f7a680bcc24f..ad98ab1c7571dc531dafad11c4ba86b5387fd9a9 100644 (file)
@@ -32,10 +32,10 @@ import {
   updateUser,
   uploadVideo,
   userLogin
-} from '../../utils/index'
-import { follow } from '../../utils/server/follows'
-import { setAccessTokensToServers } from '../../utils/users/login'
-import { getMyVideos } from '../../utils/videos/videos'
+} from '../../../../shared/utils/index'
+import { follow } from '../../../../shared/utils/server/follows'
+import { setAccessTokensToServers } from '../../../../shared/utils/users/login'
+import { getMyVideos } from '../../../../shared/utils/videos/videos'
 
 const expect = chai.expect
 
@@ -501,10 +501,6 @@ describe('Test users', function () {
     accessTokenUser = await userLogin(server, user)
   })
 
-  it('Should not be able to delete a user by a moderator', async function () {
-    await removeUser(server.url, 2, accessTokenUser, 403)
-  })
-
   it('Should be able to list video blacklist by a moderator', async function () {
     await getBlacklistedVideosList(server.url, accessTokenUser)
   })
index 9bdb78491a185add90902680343adb4a5d45309a..97f467aae45a4eafb9394b2b6defc01a7ca368e7 100644 (file)
@@ -3,7 +3,6 @@ import './services'
 import './single-server'
 import './video-abuse'
 import './video-blacklist'
-import './video-blacklist-management'
 import './video-captions'
 import './video-change-ownership'
 import './video-channels'
index b9ace2885874f14e1d3d87c32ee59027fe0a157f..6c281e49e850b6b9235db85455ee2dbff5e1a9c8 100644 (file)
@@ -8,6 +8,7 @@ import { VideoPrivacy } from '../../../../shared/models/videos'
 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
 import {
   addVideoChannel,
+  checkTmpIsEmpty,
   checkVideoFilesWereRemoved,
   completeVideoCheck,
   createUser,
@@ -31,15 +32,15 @@ import {
   viewVideo,
   wait,
   webtorrentAdd
-} from '../../utils'
+} from '../../../../shared/utils'
 import {
   addVideoCommentReply,
   addVideoCommentThread,
   deleteVideoComment,
   getVideoCommentThreads,
   getVideoThreadComments
-} from '../../utils/videos/video-comments'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils/videos/video-comments'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
@@ -1008,6 +1009,14 @@ describe('Test multiple servers', function () {
     })
   })
 
+  describe('TMP directory', function () {
+    it('Should have an empty tmp directory', async function () {
+      for (const server of servers) {
+        await checkTmpIsEmpty(server)
+      }
+    })
+  })
+
   after(async function () {
     killallServers(servers)
 
index 2f14242920dd96cb500b0809b9e195446964b670..2da86964ff6273a859444096b556ba9a87197663 100644 (file)
@@ -2,8 +2,16 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { flushTests, getOEmbed, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils/index'
-import { runServer } from '../../utils/server/servers'
+import {
+  flushTests,
+  getOEmbed,
+  getVideosList,
+  killallServers,
+  ServerInfo,
+  setAccessTokensToServers,
+  uploadVideo
+} from '../../../../shared/utils/index'
+import { runServer } from '../../../../shared/utils/server/servers'
 
 const expect = chai.expect
 
index 089c3df25990b07f2a831f896b2fb816b1489dff..069dec67c8680b3bf33085492d5fad6438a9c407 100644 (file)
@@ -28,7 +28,7 @@ import {
   uploadVideo,
   viewVideo,
   wait
-} from '../../utils'
+} from '../../../../shared/utils'
 
 const expect = chai.expect
 
index a17f3c8de953dae10b2ab20a2a2455d2c12a1c24..3a7b623daa2ba3d46ede1aa5ef8ad308cace0e80 100644 (file)
@@ -14,9 +14,9 @@ import {
   setAccessTokensToServers,
   updateVideoAbuse,
   uploadVideo
-} from '../../utils/index'
-import { doubleFollow } from '../../utils/server/follows'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils/index'
+import { doubleFollow } from '../../../../shared/utils/server/follows'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
diff --git a/server/tests/api/videos/video-blacklist-management.ts b/server/tests/api/videos/video-blacklist-management.ts
deleted file mode 100644 (file)
index fab577b..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-/* tslint:disable:no-unused-expression */
-
-import * as chai from 'chai'
-import { orderBy } from 'lodash'
-import 'mocha'
-import {
-  addVideoToBlacklist,
-  flushAndRunMultipleServers,
-  getBlacklistedVideosList,
-  getMyVideos,
-  getSortedBlacklistedVideosList,
-  getVideosList,
-  killallServers,
-  removeVideoFromBlacklist,
-  ServerInfo,
-  setAccessTokensToServers,
-  updateVideoBlacklist,
-  uploadVideo
-} from '../../utils/index'
-import { doubleFollow } from '../../utils/server/follows'
-import { waitJobs } from '../../utils/server/jobs'
-import { VideoAbuse } from '../../../../shared/models/videos'
-
-const expect = chai.expect
-
-describe('Test video blacklist management', function () {
-  let servers: ServerInfo[] = []
-  let videoId: number
-
-  async function blacklistVideosOnServer (server: ServerInfo) {
-    const res = await getVideosList(server.url)
-
-    const videos = res.body.data
-    for (let video of videos) {
-      await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason')
-    }
-  }
-
-  before(async function () {
-    this.timeout(50000)
-
-    // Run servers
-    servers = await flushAndRunMultipleServers(2)
-
-    // Get the access tokens
-    await setAccessTokensToServers(servers)
-
-    // Server 1 and server 2 follow each other
-    await doubleFollow(servers[0], servers[1])
-
-    // Upload 2 videos on server 2
-    await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'My 1st video', description: 'A video on server 2' })
-    await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'My 2nd video', description: 'A video on server 2' })
-
-    // Wait videos propagation, server 2 has transcoding enabled
-    await waitJobs(servers)
-
-    // Blacklist the two videos on server 1
-    await blacklistVideosOnServer(servers[0])
-  })
-
-  describe('When listing blacklisted videos', function () {
-    it('Should display all the blacklisted videos', async function () {
-      const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken)
-
-      expect(res.body.total).to.equal(2)
-
-      const blacklistedVideos = res.body.data
-      expect(blacklistedVideos).to.be.an('array')
-      expect(blacklistedVideos.length).to.equal(2)
-
-      for (const blacklistedVideo of blacklistedVideos) {
-        expect(blacklistedVideo.reason).to.equal('super reason')
-        videoId = blacklistedVideo.video.id
-      }
-    })
-
-    it('Should get the correct sort when sorting by descending id', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
-      expect(res.body.total).to.equal(2)
-
-      const blacklistedVideos = res.body.data
-      expect(blacklistedVideos).to.be.an('array')
-      expect(blacklistedVideos.length).to.equal(2)
-
-      const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ])
-
-      expect(blacklistedVideos).to.deep.equal(result)
-    })
-
-    it('Should get the correct sort when sorting by descending video name', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
-      expect(res.body.total).to.equal(2)
-
-      const blacklistedVideos = res.body.data
-      expect(blacklistedVideos).to.be.an('array')
-      expect(blacklistedVideos.length).to.equal(2)
-
-      const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ])
-
-      expect(blacklistedVideos).to.deep.equal(result)
-    })
-
-    it('Should get the correct sort when sorting by ascending creation date', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
-      expect(res.body.total).to.equal(2)
-
-      const blacklistedVideos = res.body.data
-      expect(blacklistedVideos).to.be.an('array')
-      expect(blacklistedVideos.length).to.equal(2)
-
-      const result = orderBy(res.body.data, [ 'createdAt' ])
-
-      expect(blacklistedVideos).to.deep.equal(result)
-    })
-  })
-
-  describe('When updating blacklisted videos', function () {
-    it('Should change the reason', async function () {
-      await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
-
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
-      const video = res.body.data.find(b => b.video.id === videoId)
-
-      expect(video.reason).to.equal('my super reason updated')
-    })
-  })
-
-  describe('When listing my videos', function () {
-    it('Should display blacklisted videos', async function () {
-      await blacklistVideosOnServer(servers[1])
-
-      const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 5)
-
-      expect(res.body.total).to.equal(2)
-      expect(res.body.data).to.have.lengthOf(2)
-
-      for (const video of res.body.data) {
-        expect(video.blacklisted).to.be.true
-        expect(video.blacklistedReason).to.equal('super reason')
-      }
-    })
-  })
-
-  describe('When removing a blacklisted video', function () {
-    let videoToRemove: VideoAbuse
-    let blacklist = []
-
-    it('Should not have any video in videos list on server 1', async function () {
-      const res = await getVideosList(servers[0].url)
-      expect(res.body.total).to.equal(0)
-      expect(res.body.data).to.be.an('array')
-      expect(res.body.data.length).to.equal(0)
-    })
-
-    it('Should remove a video from the blacklist on server 1', async function () {
-      // Get one video in the blacklist
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
-      videoToRemove = res.body.data[0]
-      blacklist = res.body.data.slice(1)
-
-      // Remove it
-      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.video.id)
-    })
-
-    it('Should have the ex-blacklisted video in videos list on server 1', async function () {
-      const res = await getVideosList(servers[0].url)
-      expect(res.body.total).to.equal(1)
-
-      const videos = res.body.data
-      expect(videos).to.be.an('array')
-      expect(videos.length).to.equal(1)
-
-      expect(videos[0].name).to.equal(videoToRemove.video.name)
-      expect(videos[0].id).to.equal(videoToRemove.video.id)
-    })
-
-    it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
-      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
-      expect(res.body.total).to.equal(1)
-
-      const videos = res.body.data
-      expect(videos).to.be.an('array')
-      expect(videos.length).to.equal(1)
-      expect(videos).to.deep.equal(blacklist)
-    })
-  })
-
-  after(async function () {
-    killallServers(servers)
-  })
-})
index de4c68f1dd10a4c218d3dc171272854a06b0db81..d39ad63b45bd972f6a8be67cc70bd33c84e82224 100644 (file)
@@ -1,24 +1,43 @@
 /* tslint:disable:no-unused-expression */
 
 import * as chai from 'chai'
+import { orderBy } from 'lodash'
 import 'mocha'
 import {
   addVideoToBlacklist,
   flushAndRunMultipleServers,
+  getBlacklistedVideosList,
+  getMyVideos,
+  getSortedBlacklistedVideosList,
   getVideosList,
   killallServers,
+  removeVideoFromBlacklist,
   searchVideo,
   ServerInfo,
   setAccessTokensToServers,
-  uploadVideo
-} from '../../utils/index'
-import { doubleFollow } from '../../utils/server/follows'
-import { waitJobs } from '../../utils/server/jobs'
+  updateVideo,
+  updateVideoBlacklist,
+  uploadVideo,
+  viewVideo
+} from '../../../../shared/utils/index'
+import { doubleFollow } from '../../../../shared/utils/server/follows'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+import { VideoBlacklist } from '../../../../shared/models/videos'
 
 const expect = chai.expect
 
-describe('Test video blacklists', function () {
+describe('Test video blacklist management', function () {
   let servers: ServerInfo[] = []
+  let videoId: number
+
+  async function blacklistVideosOnServer (server: ServerInfo) {
+    const res = await getVideosList(server.url)
+
+    const videos = res.body.data
+    for (let video of videos) {
+      await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason')
+    }
+  }
 
   before(async function () {
     this.timeout(50000)
@@ -32,58 +51,270 @@ describe('Test video blacklists', function () {
     // Server 1 and server 2 follow each other
     await doubleFollow(servers[0], servers[1])
 
-    // Upload a video on server 2
-    const videoAttributes = {
-      name: 'my super name for server 2',
-      description: 'my super description for server 2'
-    }
-    await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
+    // Upload 2 videos on server 2
+    await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'My 1st video', description: 'A video on server 2' })
+    await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'My 2nd video', description: 'A video on server 2' })
 
     // Wait videos propagation, server 2 has transcoding enabled
     await waitJobs(servers)
 
-    const res = await getVideosList(servers[0].url)
-    const videos = res.body.data
+    // Blacklist the two videos on server 1
+    await blacklistVideosOnServer(servers[0])
+  })
+
+  describe('When listing/searching videos', function () {
 
-    expect(videos.length).to.equal(1)
+    it('Should not have the video blacklisted in videos list/search on server 1', async function () {
+      {
+        const res = await getVideosList(servers[ 0 ].url)
 
-    servers[0].remoteVideo = videos.find(video => video.name === 'my super name for server 2')
+        expect(res.body.total).to.equal(0)
+        expect(res.body.data).to.be.an('array')
+        expect(res.body.data.length).to.equal(0)
+      }
+
+      {
+        const res = await searchVideo(servers[ 0 ].url, 'name')
+
+        expect(res.body.total).to.equal(0)
+        expect(res.body.data).to.be.an('array')
+        expect(res.body.data.length).to.equal(0)
+      }
+    })
+
+    it('Should have the blacklisted video in videos list/search on server 2', async function () {
+      {
+        const res = await getVideosList(servers[ 1 ].url)
+
+        expect(res.body.total).to.equal(2)
+        expect(res.body.data).to.be.an('array')
+        expect(res.body.data.length).to.equal(2)
+      }
+
+      {
+        const res = await searchVideo(servers[ 1 ].url, 'video')
+
+        expect(res.body.total).to.equal(2)
+        expect(res.body.data).to.be.an('array')
+        expect(res.body.data.length).to.equal(2)
+      }
+    })
   })
 
-  it('Should blacklist a remote video on server 1', async function () {
-    await addVideoToBlacklist(servers[0].url, servers[0].accessToken, servers[0].remoteVideo.id)
+  describe('When listing blacklisted videos', function () {
+    it('Should display all the blacklisted videos', async function () {
+      const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken)
+
+      expect(res.body.total).to.equal(2)
+
+      const blacklistedVideos = res.body.data
+      expect(blacklistedVideos).to.be.an('array')
+      expect(blacklistedVideos.length).to.equal(2)
+
+      for (const blacklistedVideo of blacklistedVideos) {
+        expect(blacklistedVideo.reason).to.equal('super reason')
+        videoId = blacklistedVideo.video.id
+      }
+    })
+
+    it('Should get the correct sort when sorting by descending id', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id')
+      expect(res.body.total).to.equal(2)
+
+      const blacklistedVideos = res.body.data
+      expect(blacklistedVideos).to.be.an('array')
+      expect(blacklistedVideos.length).to.equal(2)
+
+      const result = orderBy(res.body.data, [ 'id' ], [ 'desc' ])
+
+      expect(blacklistedVideos).to.deep.equal(result)
+    })
+
+    it('Should get the correct sort when sorting by descending video name', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      expect(res.body.total).to.equal(2)
+
+      const blacklistedVideos = res.body.data
+      expect(blacklistedVideos).to.be.an('array')
+      expect(blacklistedVideos.length).to.equal(2)
+
+      const result = orderBy(res.body.data, [ 'name' ], [ 'desc' ])
+
+      expect(blacklistedVideos).to.deep.equal(result)
+    })
+
+    it('Should get the correct sort when sorting by ascending creation date', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
+      expect(res.body.total).to.equal(2)
+
+      const blacklistedVideos = res.body.data
+      expect(blacklistedVideos).to.be.an('array')
+      expect(blacklistedVideos.length).to.equal(2)
+
+      const result = orderBy(res.body.data, [ 'createdAt' ])
+
+      expect(blacklistedVideos).to.deep.equal(result)
+    })
   })
 
-  it('Should not have the video blacklisted in videos list on server 1', async function () {
-    const res = await getVideosList(servers[0].url)
+  describe('When updating blacklisted videos', function () {
+    it('Should change the reason', async function () {
+      await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated')
+
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      const video = res.body.data.find(b => b.video.id === videoId)
 
-    expect(res.body.total).to.equal(0)
-    expect(res.body.data).to.be.an('array')
-    expect(res.body.data.length).to.equal(0)
+      expect(video.reason).to.equal('my super reason updated')
+    })
   })
 
-  it('Should not have the video blacklisted in videos search on server 1', async function () {
-    const res = await searchVideo(servers[0].url, 'name')
+  describe('When listing my videos', function () {
+    it('Should display blacklisted videos', async function () {
+      await blacklistVideosOnServer(servers[1])
+
+      const res = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 5)
 
-    expect(res.body.total).to.equal(0)
-    expect(res.body.data).to.be.an('array')
-    expect(res.body.data.length).to.equal(0)
+      expect(res.body.total).to.equal(2)
+      expect(res.body.data).to.have.lengthOf(2)
+
+      for (const video of res.body.data) {
+        expect(video.blacklisted).to.be.true
+        expect(video.blacklistedReason).to.equal('super reason')
+      }
+    })
   })
 
-  it('Should have the blacklisted video in videos list on server 2', async function () {
-    const res = await getVideosList(servers[1].url)
+  describe('When removing a blacklisted video', function () {
+    let videoToRemove: VideoBlacklist
+    let blacklist = []
+
+    it('Should not have any video in videos list on server 1', async function () {
+      const res = await getVideosList(servers[0].url)
+      expect(res.body.total).to.equal(0)
+      expect(res.body.data).to.be.an('array')
+      expect(res.body.data.length).to.equal(0)
+    })
+
+    it('Should remove a video from the blacklist on server 1', async function () {
+      // Get one video in the blacklist
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      videoToRemove = res.body.data[0]
+      blacklist = res.body.data.slice(1)
+
+      // Remove it
+      await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoToRemove.video.id)
+    })
+
+    it('Should have the ex-blacklisted video in videos list on server 1', async function () {
+      const res = await getVideosList(servers[0].url)
+      expect(res.body.total).to.equal(1)
+
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(1)
+
+      expect(videos[0].name).to.equal(videoToRemove.video.name)
+      expect(videos[0].id).to.equal(videoToRemove.video.id)
+    })
+
+    it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name')
+      expect(res.body.total).to.equal(1)
 
-    expect(res.body.total).to.equal(1)
-    expect(res.body.data).to.be.an('array')
-    expect(res.body.data.length).to.equal(1)
+      const videos = res.body.data
+      expect(videos).to.be.an('array')
+      expect(videos.length).to.equal(1)
+      expect(videos).to.deep.equal(blacklist)
+    })
   })
 
-  it('Should have the video blacklisted in videos search on server 2', async function () {
-    const res = await searchVideo(servers[1].url, 'name')
+  describe('When blacklisting local videos', function () {
+    let video3UUID: string
+    let video4UUID: string
+
+    before(async function () {
+      this.timeout(10000)
+
+      {
+        const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'Video 3' })
+        video3UUID = res.body.video.uuid
+      }
+      {
+        const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'Video 4' })
+        video4UUID = res.body.video.uuid
+      }
+
+      await waitJobs(servers)
+    })
+
+    it('Should blacklist video 3 and keep it federated', async function () {
+      this.timeout(10000)
+
+      await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video3UUID, 'super reason', false)
+
+      await waitJobs(servers)
+
+      {
+        const res = await getVideosList(servers[ 0 ].url)
+        expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined
+      }
+
+      {
+        const res = await getVideosList(servers[ 1 ].url)
+        expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined
+      }
+    })
+
+    it('Should unfederate the video', async function () {
+      this.timeout(10000)
+
+      await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, 'super reason', true)
+
+      await waitJobs(servers)
+
+      for (const server of servers) {
+        const res = await getVideosList(server.url)
+        expect(res.body.data.find(v => v.uuid === video4UUID)).to.be.undefined
+      }
+    })
+
+    it('Should have the video unfederated even after an Update AP message', async function () {
+      this.timeout(10000)
+
+      await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, { description: 'super description' })
+
+      await waitJobs(servers)
+
+      for (const server of servers) {
+        const res = await getVideosList(server.url)
+        expect(res.body.data.find(v => v.uuid === video4UUID)).to.be.undefined
+      }
+    })
+
+    it('Should have the correct video blacklist unfederate attribute', async function () {
+      const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt')
+
+      const blacklistedVideos: VideoBlacklist[] = res.body.data
+      const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID)
+      const video4Blacklisted = blacklistedVideos.find(b => b.video.uuid === video4UUID)
+
+      expect(video3Blacklisted.unfederated).to.be.false
+      expect(video4Blacklisted.unfederated).to.be.true
+    })
+
+    it('Should remove the video from blacklist and refederate the video', async function () {
+      this.timeout(10000)
+
+      await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID)
+
+      await waitJobs(servers)
+
+      for (const server of servers) {
+        const res = await getVideosList(server.url)
+        expect(res.body.data.find(v => v.uuid === video4UUID)).to.not.be.undefined
+      }
+    })
 
-    expect(res.body.total).to.equal(1)
-    expect(res.body.data).to.be.an('array')
-    expect(res.body.data.length).to.equal(1)
   })
 
   after(async function () {
index 6e441410d3fb1e9e4143b829e6b5639796a1c24c..57bee713f9c1ce182e02b1ce728c03803189c749 100644 (file)
@@ -2,10 +2,17 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { checkVideoFilesWereRemoved, doubleFollow, flushAndRunMultipleServers, removeVideo, uploadVideo, wait } from '../../utils'
-import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../utils/index'
-import { waitJobs } from '../../utils/server/jobs'
-import { createVideoCaption, deleteVideoCaption, listVideoCaptions, testCaptionFile } from '../../utils/videos/video-captions'
+import {
+  checkVideoFilesWereRemoved,
+  doubleFollow,
+  flushAndRunMultipleServers,
+  removeVideo,
+  uploadVideo,
+  wait
+} from '../../../../shared/utils'
+import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/utils/index'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+import { createVideoCaption, deleteVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/utils/videos/video-captions'
 import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model'
 
 const expect = chai.expect
index 1578a471d8ddb51aa0f98c4ef13da5d659ced9f4..25675a9663aa31b68d7695626055e0d2573cfac6 100644 (file)
@@ -18,8 +18,8 @@ import {
   uploadVideo,
   userLogin,
   getVideo
-} from '../../utils'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { User } from '../../../../shared/models/users'
 import { VideoDetails } from '../../../../shared/models/videos'
 
index 41429a3d85f8b11f43d72354333a0c62ae4c8f05..63514d69c413ed5fc8b96abbf68b0f2c87dc4a06 100644 (file)
@@ -13,7 +13,7 @@ import {
   updateVideoChannelAvatar,
   uploadVideo,
   userLogin
-} from '../../utils'
+} from '../../../../shared/utils'
 import {
   addVideoChannel,
   deleteVideoChannel,
@@ -26,8 +26,8 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   updateVideoChannel
-} from '../../utils/index'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils/index'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index d6e07c5b3945dbba0d31dc603a709202fef87e40..ce1b17e35f881dfb4cc569d36cea642d68ed47dc 100644 (file)
@@ -3,7 +3,7 @@
 import * as chai from 'chai'
 import 'mocha'
 import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model'
-import { testImage } from '../../utils'
+import { testImage } from '../../../../shared/utils'
 import {
   dateIsValid,
   flushTests,
@@ -13,14 +13,14 @@ import {
   setAccessTokensToServers,
   updateMyAvatar,
   uploadVideo
-} from '../../utils/index'
+} from '../../../../shared/utils/index'
 import {
   addVideoCommentReply,
   addVideoCommentThread,
   deleteVideoComment,
   getVideoCommentThreads,
   getVideoThreadComments
-} from '../../utils/videos/video-comments'
+} from '../../../../shared/utils/videos/video-comments'
 
 const expect = chai.expect
 
index dd5cd78c0556def192d31b327e501f82c006246c..cbda0b9a6cfc72c9c1be1fd55699143681926828 100644 (file)
@@ -12,9 +12,9 @@ import {
   setAccessTokensToServers,
   updateVideo,
   uploadVideo
-} from '../../utils/index'
-import { doubleFollow } from '../../utils/server/follows'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils/index'
+import { doubleFollow } from '../../../../shared/utils/server/follows'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index aaee79a4abab1351ab9fa950d29dafc652644e32..cd4988553db79e2294410c40b33a24aea7d3f49b 100644 (file)
@@ -14,9 +14,9 @@ import {
   killallServers,
   ServerInfo,
   setAccessTokensToServers
-} from '../../utils'
-import { waitJobs } from '../../utils/server/jobs'
-import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../utils/videos/video-imports'
+} from '../../../../shared/utils'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
+import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../../../shared/utils/videos/video-imports'
 
 const expect = chai.expect
 
index eab7a6991e5a6c145687d4b9a0bc14793bf8d552..df1ee2eb97d513deb72faf3a55b2b3f8f3bd471d 100644 (file)
@@ -2,10 +2,17 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { flushTests, getVideosList, killallServers, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils/index'
-import { userLogin } from '../../utils/users/login'
-import { createUser } from '../../utils/users/users'
-import { getMyVideos } from '../../utils/videos/videos'
+import {
+  flushTests,
+  getVideosList,
+  killallServers,
+  ServerInfo,
+  setAccessTokensToServers,
+  uploadVideo
+} from '../../../../shared/utils/index'
+import { userLogin } from '../../../../shared/utils/users/login'
+import { createUser } from '../../../../shared/utils/users/users'
+import { getMyVideos } from '../../../../shared/utils/videos/videos'
 import {
   getAccountVideos,
   getConfig,
@@ -18,7 +25,7 @@ import {
   searchVideoWithToken,
   updateCustomConfig,
   updateMyUser
-} from '../../utils'
+} from '../../../../shared/utils'
 import { ServerConfig } from '../../../../shared/models'
 import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
 import { User } from '../../../../shared/models/users'
index 9fefca7e3d919e90e280a3592a0aa164dc477643..0b4e66369f66b18fd10b81243c93eb9865109952 100644 (file)
@@ -10,12 +10,12 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
-} from '../../utils/index'
-import { doubleFollow } from '../../utils/server/follows'
-import { userLogin } from '../../utils/users/login'
-import { createUser } from '../../utils/users/users'
-import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../utils/videos/videos'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils/index'
+import { doubleFollow } from '../../../../shared/utils/server/follows'
+import { userLogin } from '../../../../shared/utils/users/login'
+import { createUser } from '../../../../shared/utils/users/users'
+import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/utils/videos/videos'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index b226a9d5016cdbc0476522bbc0f78673de8f03e9..632c4244ce22caa7fe8a953a3c9302fc5c0da918 100644 (file)
@@ -15,8 +15,8 @@ import {
   updateVideo,
   uploadVideo,
   wait
-} from '../../utils'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index 23920d45258ec1ef7df09f8246016b470df112e5..eefd32ef81a34e0785a1b2709962542b3bacd1a6 100644 (file)
@@ -19,9 +19,9 @@ import {
   setAccessTokensToServers,
   uploadVideo,
   webtorrentAdd
-} from '../../utils'
-import { join } from 'path'
-import { waitJobs } from '../../utils/server/jobs'
+} from '../../../../shared/utils'
+import { extname, join } from 'path'
+import { waitJobs } from '../../../../shared/utils/server/jobs'
 import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
 
 const expect = chai.expect
@@ -321,6 +321,34 @@ describe('Test video transcoding', function () {
     }
   })
 
+  it('Should accept and transcode additional extensions', async function () {
+    this.timeout(300000)
+
+    for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
+      const videoAttributes = {
+        name: fixture,
+        fixture
+      }
+
+      await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes)
+
+      await waitJobs(servers)
+
+      for (const server of servers) {
+        const res = await getVideosList(server.url)
+
+        const video = res.body.data.find(v => v.name === videoAttributes.name)
+        const res2 = await getVideo(server.url, video.id)
+        const videoDetails = res2.body
+
+        expect(videoDetails.files).to.have.lengthOf(4)
+
+        const magnetUri = videoDetails.files[ 0 ].magnetUri
+        expect(magnetUri).to.contain('.mp4')
+      }
+    }
+  })
+
   after(async function () {
     killallServers(servers)
   })
index a7588129fe2c9830f2acabb89aeba832a0eeec15..59e37ad86b647afbe1ee1d30f8fa1960a81f8bee 100644 (file)
@@ -13,7 +13,7 @@ import {
   setAccessTokensToServers,
   uploadVideo,
   userLogin
-} from '../../utils'
+} from '../../../../shared/utils'
 import { Video, VideoPrivacy } from '../../../../shared/models/videos'
 import { UserRole } from '../../../../shared/models/users'
 
index 6d289b288c82f655df1f34dd51a1cea1e05c984e..f654a422bd45eff49ae18dd0fd50944df6f1b775 100644 (file)
@@ -3,17 +3,21 @@
 import * as chai from 'chai'
 import 'mocha'
 import {
+  createUser,
   flushTests,
   getVideosListWithToken,
   getVideoWithToken,
-  killallServers, makePutBodyRequest,
-  runServer, searchVideoWithToken,
+  killallServers,
+  runServer,
+  searchVideoWithToken,
   ServerInfo,
   setAccessTokensToServers,
-  uploadVideo
-} from '../../utils'
+  updateMyUser,
+  uploadVideo,
+  userLogin
+} from '../../../../shared/utils'
 import { Video, VideoDetails } from '../../../../shared/models/videos'
-import { userWatchVideo } from '../../utils/videos/video-history'
+import { listMyVideosHistory, removeMyVideosHistory, userWatchVideo } from '../../../../shared/utils/videos/video-history'
 
 const expect = chai.expect
 
@@ -22,6 +26,8 @@ describe('Test videos history', function () {
   let video1UUID: string
   let video2UUID: string
   let video3UUID: string
+  let video3WatchedDate: Date
+  let userAccessToken: string
 
   before(async function () {
     this.timeout(30000)
@@ -46,6 +52,13 @@ describe('Test videos history', function () {
       const res = await uploadVideo(server.url, server.accessToken, { name: 'video 3' })
       video3UUID = res.body.video.uuid
     }
+
+    const user = {
+      username: 'user_1',
+      password: 'super password'
+    }
+    await createUser(server.url, server.accessToken, user.username, user.password)
+    userAccessToken = await userLogin(server, user)
   })
 
   it('Should get videos, without watching history', async function () {
@@ -62,8 +75,8 @@ describe('Test videos history', function () {
   })
 
   it('Should watch the first and second video', async function () {
-    await userWatchVideo(server.url, server.accessToken, video1UUID, 3)
     await userWatchVideo(server.url, server.accessToken, video2UUID, 8)
+    await userWatchVideo(server.url, server.accessToken, video1UUID, 3)
   })
 
   it('Should return the correct history when listing, searching and getting videos', async function () {
@@ -117,6 +130,68 @@ describe('Test videos history', function () {
     }
   })
 
+  it('Should have these videos when listing my history', async function () {
+    video3WatchedDate = new Date()
+    await userWatchVideo(server.url, server.accessToken, video3UUID, 2)
+
+    const res = await listMyVideosHistory(server.url, server.accessToken)
+
+    expect(res.body.total).to.equal(3)
+
+    const videos: Video[] = res.body.data
+    expect(videos[0].name).to.equal('video 3')
+    expect(videos[1].name).to.equal('video 1')
+    expect(videos[2].name).to.equal('video 2')
+  })
+
+  it('Should not have videos history on another user', async function () {
+    const res = await listMyVideosHistory(server.url, userAccessToken)
+
+    expect(res.body.total).to.equal(0)
+    expect(res.body.data).to.have.lengthOf(0)
+  })
+
+  it('Should clear my history', async function () {
+    await removeMyVideosHistory(server.url, server.accessToken, video3WatchedDate.toISOString())
+  })
+
+  it('Should have my history cleared', async function () {
+    const res = await listMyVideosHistory(server.url, server.accessToken)
+
+    expect(res.body.total).to.equal(1)
+
+    const videos: Video[] = res.body.data
+    expect(videos[0].name).to.equal('video 3')
+  })
+
+  it('Should disable videos history', async function () {
+    await updateMyUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      videosHistoryEnabled: false
+    })
+
+    await userWatchVideo(server.url, server.accessToken, video2UUID, 8, 409)
+  })
+
+  it('Should re-enable videos history', async function () {
+    await updateMyUser({
+      url: server.url,
+      accessToken: server.accessToken,
+      videosHistoryEnabled: true
+    })
+
+    await userWatchVideo(server.url, server.accessToken, video1UUID, 8)
+
+    const res = await listMyVideosHistory(server.url, server.accessToken)
+
+    expect(res.body.total).to.equal(2)
+
+    const videos: Video[] = res.body.data
+    expect(videos[0].name).to.equal('video 1')
+    expect(videos[1].name).to.equal('video 3')
+  })
+
   after(async function () {
     killallServers([ server ])
 
index 7d1f29c92ee8a6933840b0223b0f9bd1e39a7fc2..7221bcae653c678bd3103adb4055a252a1c1c180 100644 (file)
@@ -2,8 +2,8 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../utils'
-import { getVideosOverview } from '../../utils/overviews/overviews'
+import { flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/utils'
+import { getVideosOverview } from '../../../../shared/utils/overviews/overviews'
 import { VideosOverview } from '../../../../shared/models/overviews'
 
 const expect = chai.expect
index 13bcfd209836f636e831fe487c0d74ddcafec67f..4acda47b1c2f89dc16d6dc7d53f3ddc43ffc593b 100644 (file)
@@ -15,8 +15,8 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
-} from '../utils'
-import { waitJobs } from '../utils/server/jobs'
+} from '../../../shared/utils'
+import { waitJobs } from '../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index c2e3840c59b360e6d6bb28cb7e2efba9a6509bf4..50be5fa19f09b989f4b36206811bed2c889191f9 100644 (file)
@@ -15,8 +15,8 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo, wait
-} from '../utils'
-import { waitJobs } from '../utils/server/jobs'
+} from '../../../shared/utils'
+import { waitJobs } from '../../../shared/utils/server/jobs'
 
 const expect = chai.expect
 
index 6201314ce65721ea76cb22e05a7b79e8b2a72b9f..c6b7ec0789dff08d2ea7753196d303925b580c86 100644 (file)
@@ -1,6 +1,7 @@
 // Order of the tests we want to execute
 import './create-import-video-file-job'
 import './create-transcoding-job'
+import './optimize-old-videos'
 import './peertube'
 import './reset-password'
 import './update-host'
index 66dd39cce7ffa4d4a8d3342b148b82207bb3328a..6f6bc25a635393cfd87a2c78bdb470234ff45640 100644 (file)
@@ -15,8 +15,8 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo, viewVideo, wait
-} from '../utils'
-import { waitJobs } from '../utils/server/jobs'
+} from '../../../shared/utils'
+import { waitJobs } from '../../../shared/utils/server/jobs'
 import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffmpeg-utils'
 import { VIDEO_TRANSCODING_FPS } from '../../initializers'
 import { join } from 'path'
index 7a712bc4e9466493f80eeda48dc5a4089dc091a9..e2836d0c37fb05d50658d7dc2a3cd35377b0b964 100644 (file)
@@ -11,7 +11,7 @@ import {
   runServer,
   ServerInfo,
   setAccessTokensToServers
-} from '../utils'
+} from '../../../shared/utils'
 
 describe('Test CLI wrapper', function () {
   let server: ServerInfo
index bf937d1c0c022ed054a911cbe83dc97a30f6a310..1b65f7e39b133181422d9b7f3a0786bbe0d6cb6e 100644 (file)
@@ -10,7 +10,7 @@ import {
   runServer,
   ServerInfo,
   setAccessTokensToServers
-} from '../utils'
+} from '../../../shared/utils'
 
 describe('Test reset password scripts', function () {
   let server: ServerInfo
index b89e72ab76c68d51c60625da6c488ed73a0db7e7..811ea6a9f44f71871cb17fbeb466f535e593144b 100644 (file)
@@ -3,8 +3,8 @@
 import 'mocha'
 import * as chai from 'chai'
 import { VideoDetails } from '../../../shared/models/videos'
-import { waitJobs } from '../utils/server/jobs'
-import { addVideoCommentThread } from '../utils/videos/video-comments'
+import { waitJobs } from '../../../shared/utils/server/jobs'
+import { addVideoCommentThread } from '../../../shared/utils/videos/video-comments'
 import {
   addVideoChannel,
   createUser,
@@ -21,8 +21,8 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
-} from '../utils'
-import { getAccountsList } from '../utils/users/accounts'
+} from '../../../shared/utils'
+import { getAccountsList } from '../../../shared/utils/users/accounts'
 
 const expect = chai.expect
 
index b33a653b13e2079f0bc6ef3980085fcb8fd347a5..06b4a9c5a1c404599e3398b333e42d4125cf21a9 100644 (file)
@@ -15,7 +15,7 @@ import {
   updateCustomConfig,
   updateCustomSubConfig,
   uploadVideo
-} from './utils'
+} from '../../shared/utils'
 
 const expect = chai.expect
 
index 28fe3493b08be8b8b740f35904824867ec0fbcbb..a771474bc1487cc701226f53c4c245a7f61be1cf 100644 (file)
@@ -13,10 +13,10 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo, userLogin
-} from '../utils'
+} from '../../../shared/utils'
 import * as libxmljs from 'libxmljs'
-import { addVideoCommentThread } from '../utils/videos/video-comments'
-import { waitJobs } from '../utils/server/jobs'
+import { addVideoCommentThread } from '../../../shared/utils/videos/video-comments'
+import { waitJobs } from '../../../shared/utils/server/jobs'
 import { User } from '../../../shared/models/users'
 
 chai.use(require('chai-xml'))
diff --git a/server/tests/fixtures/video_short.avi b/server/tests/fixtures/video_short.avi
new file mode 100644 (file)
index 0000000..88979ca
Binary files /dev/null and b/server/tests/fixtures/video_short.avi differ
diff --git a/server/tests/fixtures/video_short.mkv b/server/tests/fixtures/video_short.mkv
new file mode 100644 (file)
index 0000000..a67f4f8
Binary files /dev/null and b/server/tests/fixtures/video_short.mkv differ
diff --git a/server/tests/fixtures/video_short_240p.mp4 b/server/tests/fixtures/video_short_240p.mp4
new file mode 100644 (file)
index 0000000..db07494
Binary files /dev/null and b/server/tests/fixtures/video_short_240p.mp4 differ
diff --git a/server/tests/helpers/comment-model.ts b/server/tests/helpers/comment-model.ts
new file mode 100644 (file)
index 0000000..76bb0f2
--- /dev/null
@@ -0,0 +1,25 @@
+/* tslint:disable:no-unused-expression */
+
+import * as chai from 'chai'
+import 'mocha'
+import { VideoCommentModel } from '../../models/video/video-comment'
+
+const expect = chai.expect
+
+class CommentMock {
+  text: string
+
+  extractMentions = VideoCommentModel.prototype.extractMentions
+}
+
+describe('Comment model', function () {
+  it('Should correctly extract mentions', async function () {
+    const comment = new CommentMock()
+
+    comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' +
+      'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end'
+    const result = comment.extractMentions().sort()
+
+    expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ])
+  })
+})
index a6d829a9f4b0f1bd5d05a21d5ff8e902932609fa..e604cf7e325b1581cdef4bcc94d439d9be468709 100644 (file)
@@ -2,13 +2,16 @@
 
 import * as chai from 'chai'
 import 'mocha'
+import { snakeCase, isNumber } from 'lodash'
 import {
-  parseBytes
+  parseBytes, objectConverter
 } from '../../helpers/core-utils'
+import { isNumeric } from 'validator'
 
 const expect = chai.expect
 
 describe('Parse Bytes', function () {
+
   it('Should pass when given valid value', async function () {
     // just return it
     expect(parseBytes(1024)).to.be.eq(1024)
@@ -45,4 +48,51 @@ describe('Parse Bytes', function () {
   it('Should be invalid when given invalid value', async function () {
     expect(parseBytes('6GB 1GB')).to.be.eq(6)
   })
+
+  it('Should convert an object', async function () {
+    function keyConverter (k: string) {
+      return snakeCase(k)
+    }
+
+    function valueConverter (v: any) {
+      if (isNumeric(v + '')) return parseInt('' + v, 10)
+
+      return v
+    }
+
+    const obj = {
+      mySuperKey: 'hello',
+      mySuper2Key: '45',
+      mySuper3Key: {
+        mySuperSubKey: '15',
+        mySuperSub2Key: 'hello',
+        mySuperSub3Key: [ '1', 'hello', 2 ],
+        mySuperSub4Key: 4
+      },
+      mySuper4Key: 45,
+      toto: {
+        super_key: '15',
+        superKey2: 'hello'
+      },
+      super_key: {
+        superKey4: 15
+      }
+    }
+
+    const res = objectConverter(obj, keyConverter, valueConverter)
+
+    expect(res.my_super_key).to.equal('hello')
+    expect(res.my_super_2_key).to.equal(45)
+    expect(res.my_super_3_key.my_super_sub_key).to.equal(15)
+    expect(res.my_super_3_key.my_super_sub_2_key).to.equal('hello')
+    expect(res.my_super_3_key.my_super_sub_3_key).to.deep.equal([ 1, 'hello', 2 ])
+    expect(res.my_super_3_key.my_super_sub_4_key).to.equal(4)
+    expect(res.toto.super_key).to.equal(15)
+    expect(res.toto.super_key_2).to.equal('hello')
+    expect(res.super_key.super_key_4).to.equal(15)
+
+    // Immutable
+    expect(res.mySuperKey).to.be.undefined
+    expect(obj['my_super_key']).to.be.undefined
+  })
 })
index 40c7dc70e29878d98a33a6ba650be70c365321fc..55120824516e067ec92b372c62b60f762e4f9847 100644 (file)
@@ -1 +1,2 @@
 import './core-utils'
+import './comment-model'
index 8fab20971594702235a82091de848a70a8c763ed..5f82719dac6e18aee41714ab0fe4f67b3c156f74 100644 (file)
@@ -2,7 +2,18 @@
 
 import 'mocha'
 import * as chai from 'chai'
-import { flushTests, killallServers, makeGetRequest, runServer, ServerInfo } from './utils'
+import {
+  addVideoChannel,
+  createUser,
+  flushTests,
+  killallServers,
+  makeGetRequest,
+  runServer,
+  ServerInfo,
+  setAccessTokensToServers,
+  uploadVideo
+} from '../../shared/utils'
+import { VideoPrivacy } from '../../shared/models/videos'
 
 const expect = chai.expect
 
@@ -15,6 +26,7 @@ describe('Test misc endpoints', function () {
     await flushTests()
 
     server = await runServer(1)
+    await setAccessTokensToServers([ server ])
   })
 
   describe('Test a well known endpoints', function () {
@@ -60,6 +72,16 @@ describe('Test misc endpoints', function () {
 
       expect(res.body.tracking).to.equal('N')
     })
+
+    it('Should get change-password location', async function () {
+      const res = await makeGetRequest({
+        url: server.url,
+        path: '/.well-known/change-password',
+        statusCodeExpected: 302
+      })
+
+      expect(res.header.location).to.equal('/my-account/settings')
+    })
   })
 
   describe('Test classic static endpoints', function () {
@@ -93,6 +115,64 @@ describe('Test misc endpoints', function () {
     })
   })
 
+  describe('Test bots endpoints', function () {
+
+    it('Should get the empty sitemap', async function () {
+      const res = await makeGetRequest({
+        url: server.url,
+        path: '/sitemap.xml',
+        statusCodeExpected: 200
+      })
+
+      expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/about/instance</loc></url>')
+    })
+
+    it('Should get the empty cached sitemap', async function () {
+      const res = await makeGetRequest({
+        url: server.url,
+        path: '/sitemap.xml',
+        statusCodeExpected: 200
+      })
+
+      expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/about/instance</loc></url>')
+    })
+
+    it('Should add videos, channel and accounts and get sitemap', async function () {
+      this.timeout(35000)
+
+      await uploadVideo(server.url, server.accessToken, { name: 'video 1', nsfw: false })
+      await uploadVideo(server.url, server.accessToken, { name: 'video 2', nsfw: false })
+      await uploadVideo(server.url, server.accessToken, { name: 'video 3', privacy: VideoPrivacy.PRIVATE })
+
+      await addVideoChannel(server.url, server.accessToken, { name: 'channel1', displayName: 'channel 1' })
+      await addVideoChannel(server.url, server.accessToken, { name: 'channel2', displayName: 'channel 2' })
+
+      await createUser(server.url, server.accessToken, 'user1', 'password')
+      await createUser(server.url, server.accessToken, 'user2', 'password')
+
+      const res = await makeGetRequest({
+        url: server.url,
+        path: '/sitemap.xml?t=1', // avoid using cache
+        statusCodeExpected: 200
+      })
+
+      expect(res.text).to.contain('xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"')
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/about/instance</loc></url>')
+
+      expect(res.text).to.contain('<video:title><![CDATA[video 1]]></video:title>')
+      expect(res.text).to.contain('<video:title><![CDATA[video 2]]></video:title>')
+      expect(res.text).to.not.contain('<video:title><![CDATA[video 3]]></video:title>')
+
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/video-channels/channel1</loc></url>')
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/video-channels/channel2</loc></url>')
+
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/accounts/user1</loc></url>')
+      expect(res.text).to.contain('<url><loc>http://localhost:9001/accounts/user2</loc></url>')
+    })
+  })
+
   after(async function () {
     killallServers([ server ])
   })
index a7fdbd1dc8fccaa8cd2255413c6ab4244ac461e1..01650349884efef0708472c60a0563974bd59c00 100644 (file)
@@ -10,7 +10,7 @@ import {
   ServerInfo,
   setAccessTokensToServers,
   uploadVideo
-} from '../utils'
+} from '../../../shared/utils'
 import * as Bluebird from 'bluebird'
 
 start()
index a96469b11181be6782ebdf872e370401b06a9f69..ac3baaf9a1a697067aa2394f8d5fa8577085f352 100644 (file)
@@ -16,8 +16,8 @@ import {
   updateVideo,
   uploadVideo, viewVideo,
   wait
-} from '../utils'
-import { getJobsListPaginationAndSort } from '../utils/server/jobs'
+} from '../../../shared/utils'
+import { getJobsListPaginationAndSort } from '../../../shared/utils/server/jobs'
 
 interface ServerInfo extends DefaultServerInfo {
   requestsNumber: number
diff --git a/server/tests/utils/miscs/email.ts b/server/tests/utils/miscs/email.ts
deleted file mode 100644 (file)
index 21accd0..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-import * as MailDev from 'maildev'
-
-function mockSmtpServer (emailsCollection: object[]) {
-  const maildev = new MailDev({
-    ip: '127.0.0.1',
-    smtp: 1025,
-    disableWeb: true,
-    silent: true
-  })
-  maildev.on('new', email => emailsCollection.push(email))
-
-  return new Promise((res, rej) => {
-    maildev.listen(err => {
-      if (err) return rej(err)
-
-      return res()
-    })
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-export {
-  mockSmtpServer
-}
diff --git a/server/tests/utils/videos/video-history.ts b/server/tests/utils/videos/video-history.ts
deleted file mode 100644 (file)
index 7635478..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-import { makePutBodyRequest } from '../requests/requests'
-
-function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number) {
-  const path = '/api/v1/videos/' + videoId + '/watching'
-  const fields = { currentTime }
-
-  return makePutBodyRequest({ url, path, token, fields, statusCodeExpected: 204 })
-}
-
-// ---------------------------------------------------------------------------
-
-export {
-  userWatchVideo
-}
index eb2571a03f6865744945b520d43eec09f1ba5c12..a68665f5bb7ff6bcfe6888a60201869a1495fc60 100644 (file)
@@ -6,7 +6,7 @@ import {
   Server,
   Client,
   User
-} from '../tests/utils/index'
+} from '../../shared/utils'
 
 program
   .option('-u, --url <url>', 'Server url')
index 21505b79db972a6d4cc1dcb2698003a67501b402..f50aafc355ecdd1d1347653e53ed3a49fc6a4922 100644 (file)
@@ -6,7 +6,7 @@ import { join } from 'path'
 import { VideoPrivacy } from '../../shared/models/videos'
 import { doRequestAndSaveToFile } from '../helpers/requests'
 import { CONSTRAINTS_FIELDS } from '../initializers'
-import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../tests/utils'
+import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/utils/index'
 import { truncate } from 'lodash'
 import * as prompt from 'prompt'
 import { remove } from 'fs-extra'
@@ -58,6 +58,7 @@ getSettings()
         settings.remotes[settings.default] :
         settings.remotes[0]
     }
+
     if (!program['username']) program['username'] = netrc.machines[program['url']].login
     if (!program['password']) program['password'] = netrc.machines[program['url']].password
   }
@@ -69,12 +70,19 @@ getSettings()
     process.exit(-1)
   }
 
+  removeEndSlashes(program['url'])
+  removeEndSlashes(program['targetUrl'])
+
   const user = {
     username: program['username'],
     password: program['password']
   }
 
-  run(user, program['url']).catch(err => console.error(err))
+  run(user, program['url'])
+    .catch(err => {
+      console.error(err)
+      process.exit(-1)
+    })
 })
 
 async function promptPassword () {
@@ -108,8 +116,12 @@ async function run (user, url: string) {
     secret: res.body.client_secret
   }
 
-  const res2 = await login(url, client, user)
-  accessToken = res2.body.access_token
+  try {
+    const res = await login(program[ 'url' ], client, user)
+    accessToken = res.body.access_token
+  } catch (err) {
+    throw new Error('Cannot authenticate. Please check your username/password.')
+  }
 
   const youtubeDL = await safeGetYoutubeDL()
 
@@ -321,3 +333,9 @@ function isNSFW (info: any) {
 
   return false
 }
+
+function removeEndSlashes (url: string) {
+  while (url.endsWith('/')) {
+    url.slice(0, -1)
+  }
+}
index 6248fb47d648f6cc1519f9892080991582e8b363..cc7bd9b4c7453706d4beabc4fa9dd6ea5eba0b2f 100644 (file)
@@ -1,8 +1,8 @@
 import * as program from 'commander'
 import { access, constants } from 'fs-extra'
 import { isAbsolute } from 'path'
-import { getClient, login } from '../tests/utils'
-import { uploadVideo } from '../tests/utils/index'
+import { getClient, login } from '../../shared/utils'
+import { uploadVideo } from '../../shared/utils/'
 import { VideoPrivacy } from '../../shared/models/videos'
 import { netrc, getSettings } from './cli'
 
index 44cb99efb9da885254ba4300b084df782233829d..89994f6650f2c272976272e7d4fcb1e3799c9f8c 100644 (file)
@@ -5,12 +5,14 @@ import { DislikeObject } from './objects/dislike-object'
 import { VideoAbuseObject } from './objects/video-abuse-object'
 import { VideoCommentObject } from './objects/video-comment-object'
 import { ViewObject } from './objects/view-object'
+import { APObject } from './objects/object.model'
 
 export type Activity = ActivityCreate | ActivityUpdate |
   ActivityDelete | ActivityFollow | ActivityAccept | ActivityAnnounce |
-  ActivityUndo | ActivityLike | ActivityReject
+  ActivityUndo | ActivityLike | ActivityReject | ActivityView | ActivityDislike | ActivityFlag
 
-export type ActivityType = 'Create' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like' | 'Reject'
+export type ActivityType = 'Create' | 'Update' | 'Delete' | 'Follow' | 'Accept' | 'Announce' | 'Undo' | 'Like' | 'Reject' |
+  'View' | 'Dislike' | 'Flag'
 
 export interface ActivityAudience {
   to: string[]
@@ -59,15 +61,34 @@ export interface ActivityReject extends BaseActivity {
 
 export interface ActivityAnnounce extends BaseActivity {
   type: 'Announce'
-  object: string | { id: string }
+  object: APObject
 }
 
 export interface ActivityUndo extends BaseActivity {
   type: 'Undo',
-  object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce
+  object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce
 }
 
 export interface ActivityLike extends BaseActivity {
   type: 'Like',
-  object: string
+  object: APObject
+}
+
+export interface ActivityView extends BaseActivity {
+  type: 'View',
+  actor: string
+  object: APObject
+}
+
+export interface ActivityDislike extends BaseActivity {
+  id: string
+  type: 'Dislike'
+  actor: string
+  object: APObject
+}
+
+export interface ActivityFlag extends BaseActivity {
+  type: 'Flag',
+  content: string,
+  object: APObject
 }
diff --git a/shared/models/activitypub/objects/object.model.ts b/shared/models/activitypub/objects/object.model.ts
new file mode 100644 (file)
index 0000000..3fd3380
--- /dev/null
@@ -0,0 +1 @@
+export type APObject = string | { id: string }
index 6b3b1b47c936730ae2b5a9e40ee56c4e4cde4451..a3953874d0bcf381482cd35f9b6754c4808172dd 100644 (file)
@@ -10,5 +10,5 @@ export interface Actor {
   followersCount: number
   createdAt: Date | string
   updatedAt: Date | string
-  avatar: Avatar
+  avatar?: Avatar
 }
index 5c3249452e1a20b7ba0ca8ff163c33a4d456f191..d7164b73f7bc11030e97d65cf55b02fc9e209816 100644 (file)
@@ -8,12 +8,14 @@ export const I18N_LOCALES = {
   'cs-CZ': 'Čeština',
   'eo': 'Esperanto',
   'de-DE': 'Deutsch',
+  'it-IT': 'Italiano',
   'es-ES': 'Español',
   'oc': 'Occitan',
   'zh-Hant-TW': '繁體中文(台灣)',
   'pt-BR': 'Português (Brasil)',
   'sv-SE': 'svenska',
-  // 'pl-PL': 'Polski'
+  'pl-PL': 'Polski',
+  'ru-RU': 'русский',
   'zh-Hans-CN': '简体中文(中国)'
 }
 
@@ -26,8 +28,9 @@ const I18N_LOCALE_ALIAS = {
   'de': 'de-DE',
   'es': 'es-ES',
   'pt': 'pt-BR',
-  'sv': 'sv-SE'
-  // 'pl': 'pl-PL'
+  'sv': 'sv-SE',
+  'pl': 'pl-PL',
+  'ru': 'ru-RU'
 }
 
 export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES)
diff --git a/shared/models/server/contact-form.model.ts b/shared/models/server/contact-form.model.ts
new file mode 100644 (file)
index 0000000..0696be8
--- /dev/null
@@ -0,0 +1,5 @@
+export interface ContactForm {
+  fromEmail: string
+  fromName: string
+  body: string
+}
index 3afd36fcdba282dc35e6af1fde29b563e22b0504..7a3eaa33f417a05529c406add51d6d378f04995e 100644 (file)
@@ -41,6 +41,10 @@ export interface CustomConfig {
     email: string
   }
 
+  contactForm: {
+    enabled: boolean
+  }
+
   user: {
     videoQuota: number
     videoQuotaDaily: number
@@ -48,6 +52,7 @@ export interface CustomConfig {
 
   transcoding: {
     enabled: boolean
+    allowAdditionalExtensions: boolean
     threads: number
     resolutions: {
       '240p': boolean
diff --git a/shared/models/server/index.ts b/shared/models/server/index.ts
new file mode 100644 (file)
index 0000000..c42f6f6
--- /dev/null
@@ -0,0 +1,6 @@
+export * from './about.model'
+export * from './contact-form.model'
+export * from './custom-config.model'
+export * from './job.model'
+export * from './server-config.model'
+export * from './server-stats.model'
index 91196c1eb818adcba88a4ef959e65443bf7d94c1..f4245ed4db308f44351e0d4f647ccaeb67f51b00 100644 (file)
@@ -15,6 +15,14 @@ export interface ServerConfig {
     }
   }
 
+  email: {
+    enabled: boolean
+  }
+
+  contactForm: {
+    enabled: boolean
+  }
+
   signup: {
     allowed: boolean,
     allowedForCurrentIP: boolean,
@@ -70,4 +78,10 @@ export interface ServerConfig {
     videoQuota: number
     videoQuotaDaily: number
   }
+
+  trending: {
+    videos: {
+      intervalDays: number
+    }
+  }
 }
index a6bd2d4d35ee2756ba05c2e535f83233bbc629ce..74f3de5d3094724e29d7de55efc6c12e10c6f70d 100644 (file)
@@ -5,6 +5,7 @@ export interface ServerStats {
   totalLocalVideos: number
   totalLocalVideoViews: number
   totalLocalVideoComments: number
+  totalLocalVideoFilesSize: number
 
   totalVideos: number
   totalVideoComments: number
index 7114741e00ae8bd114ca58c5e12f0c684581089a..cd07cf320021bda49f747f5f18821a96d0d689b4 100644 (file)
@@ -1,6 +1,8 @@
 export * from './user.model'
 export * from './user-create.model'
 export * from './user-login.model'
+export * from './user-notification.model'
+export * from './user-notification-setting.model'
 export * from './user-refresh-token.model'
 export * from './user-update.model'
 export * from './user-update-me.model'
diff --git a/shared/models/users/user-notification-setting.model.ts b/shared/models/users/user-notification-setting.model.ts
new file mode 100644 (file)
index 0000000..531e12b
--- /dev/null
@@ -0,0 +1,17 @@
+export enum UserNotificationSettingValue {
+  NONE = 0,
+  WEB = 1 << 0,
+  EMAIL = 1 << 1
+}
+
+export interface UserNotificationSetting {
+  newVideoFromSubscription: UserNotificationSettingValue
+  newCommentOnMyVideo: UserNotificationSettingValue
+  videoAbuseAsModerator: UserNotificationSettingValue
+  blacklistOnMyVideo: UserNotificationSettingValue
+  myVideoPublished: UserNotificationSettingValue
+  myVideoImportFinished: UserNotificationSettingValue
+  newUserRegistration: UserNotificationSettingValue
+  newFollow: UserNotificationSettingValue
+  commentMention: UserNotificationSettingValue
+}
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts
new file mode 100644 (file)
index 0000000..186b626
--- /dev/null
@@ -0,0 +1,83 @@
+export enum UserNotificationType {
+  NEW_VIDEO_FROM_SUBSCRIPTION = 1,
+  NEW_COMMENT_ON_MY_VIDEO = 2,
+  NEW_VIDEO_ABUSE_FOR_MODERATORS = 3,
+
+  BLACKLIST_ON_MY_VIDEO = 4,
+  UNBLACKLIST_ON_MY_VIDEO = 5,
+
+  MY_VIDEO_PUBLISHED = 6,
+
+  MY_VIDEO_IMPORT_SUCCESS = 7,
+  MY_VIDEO_IMPORT_ERROR = 8,
+
+  NEW_USER_REGISTRATION = 9,
+  NEW_FOLLOW = 10,
+  COMMENT_MENTION = 11
+}
+
+export interface VideoInfo {
+  id: number
+  uuid: string
+  name: string
+}
+
+export interface ActorInfo {
+  id: number
+  displayName: string
+  name: string
+  host: string
+  avatar?: {
+    path: string
+  }
+}
+
+export interface UserNotification {
+  id: number
+  type: UserNotificationType
+  read: boolean
+
+  video?: VideoInfo & {
+    channel: ActorInfo
+  }
+
+  videoImport?: {
+    id: number
+    video?: VideoInfo
+    torrentName?: string
+    magnetUri?: string
+    targetUrl?: string
+  }
+
+  comment?: {
+    id: number
+    threadId: number
+    account: ActorInfo
+    video: VideoInfo
+  }
+
+  videoAbuse?: {
+    id: number
+    video: VideoInfo
+  }
+
+  videoBlacklist?: {
+    id: number
+    video: VideoInfo
+  }
+
+  account?: ActorInfo
+
+  actorFollow?: {
+    id: number
+    follower: ActorInfo
+    following: {
+      type: 'account' | 'channel'
+      name: string
+      displayName: string
+    }
+  }
+
+  createdAt: string
+  updatedAt: string
+}
index 51c59d20ad0e7ef3bef72548c558f1e26b77284c..090256bca0dd066fe41d9b019a1e7940b7f7ffa1 100644 (file)
@@ -2,10 +2,15 @@ export enum UserRight {
   ALL,
 
   MANAGE_USERS,
+
   MANAGE_SERVER_FOLLOW,
+
   MANAGE_SERVER_REDUNDANCY,
+
   MANAGE_VIDEO_ABUSES,
+
   MANAGE_JOBS,
+
   MANAGE_CONFIGURATION,
 
   MANAGE_ACCOUNTS_BLOCKLIST,
index adef8fd955893ada64d26b604955062ba2c21baf..59c2ba10656a63a896ca8a429c437ff0e367fe69 100644 (file)
@@ -29,7 +29,8 @@ const userRoleRights: { [ id: number ]: UserRight[] } = {
     UserRight.UPDATE_ANY_VIDEO,
     UserRight.SEE_ALL_VIDEOS,
     UserRight.MANAGE_ACCOUNTS_BLOCKLIST,
-    UserRight.MANAGE_SERVERS_BLOCKLIST
+    UserRight.MANAGE_SERVERS_BLOCKLIST,
+    UserRight.MANAGE_USERS
   ],
 
   [UserRole.USER]: []
index 10edeee2e657cfe55710a8f87539095448034bb7..e24afab940848d92350963d1303b16d34d759765 100644 (file)
@@ -3,9 +3,12 @@ import { NSFWPolicyType } from '../videos/nsfw-policy.type'
 export interface UserUpdateMe {
   displayName?: string
   description?: string
-  nsfwPolicy?: NSFWPolicyType,
-  webTorrentEnabled?: boolean,
+  nsfwPolicy?: NSFWPolicyType
+
+  webTorrentEnabled?: boolean
   autoPlayVideo?: boolean
+  videosHistoryEnabled?: boolean
+
   email?: string
   currentPassword?: string
   password?: string
index 82af175160a787cf28df67059924b5f3ba057c35..af783d38907e085cce43989e11caf7ac06065ae9 100644 (file)
@@ -2,6 +2,7 @@ import { Account } from '../actors'
 import { VideoChannel } from '../videos/channel/video-channel.model'
 import { UserRole } from './user-role'
 import { NSFWPolicyType } from '../videos/nsfw-policy.type'
+import { UserNotificationSetting } from './user-notification-setting.model'
 
 export interface User {
   id: number
@@ -9,12 +10,17 @@ export interface User {
   email: string
   emailVerified: boolean
   nsfwPolicy: NSFWPolicyType
+
   autoPlayVideo: boolean
+  webTorrentEnabled: boolean
+  videosHistoryEnabled: boolean
+
   role: UserRole
   videoQuota: number
   videoQuotaDaily: number
   createdAt: Date
   account: Account
+  notificationSettings?: UserNotificationSetting
   videoChannels?: VideoChannel[]
 
   blocked: boolean
index 89c69cb562c81517b283a068b20ba2fa4bfa8efe..6e7d36421d4bcae753f3d276e83a9659e80fb995 100644 (file)
@@ -1,3 +1,4 @@
 export interface VideoBlacklistCreate {
   reason?: string
+  unfederate?: boolean
 }
index ef4e5e3a23cea522b67400696aa64c089e325177..4bd976190107e2b8e89f3725896fa926b370feb5 100644 (file)
@@ -2,6 +2,7 @@ export interface VideoBlacklist {
   id: number
   createdAt: Date
   updatedAt: Date
+  unfederated: boolean
   reason?: string
 
   video: {
index 4a9fa58b109792a7b4b57958a0a2eb04d3d538c8..022876a0bffe83c66af02018fbc8c2e84378f83c 100644 (file)
@@ -24,7 +24,7 @@ export interface VideoChannelAttribute {
   displayName: string
   url: string
   host: string
-  avatar: Avatar
+  avatar?: Avatar
 }
 
 export interface AccountAttribute {
@@ -34,7 +34,7 @@ export interface AccountAttribute {
   displayName: string
   url: string
   host: string
-  avatar: Avatar
+  avatar?: Avatar
 }
 
 export interface Video {
similarity index 82%
rename from server/tests/utils/index.ts
rename to shared/utils/index.ts
index 8349631c96a9eb0badf7cdef161de84974f9ae40..e08bbfd2a98955c63e0eeaa8e7f84c9eb7ccc194 100644 (file)
@@ -2,11 +2,15 @@ export * from './server/activitypub'
 export * from './cli/cli'
 export * from './server/clients'
 export * from './server/config'
+export * from './server/jobs'
 export * from './users/login'
 export * from './miscs/miscs'
 export * from './miscs/stubs'
+export * from './miscs/sql'
 export * from './server/follows'
+export * from './requests/activitypub'
 export * from './requests/requests'
+export * from './requests/check-api-params'
 export * from './server/servers'
 export * from './videos/services'
 export * from './users/users'
diff --git a/shared/utils/miscs/email-child-process.js b/shared/utils/miscs/email-child-process.js
new file mode 100644 (file)
index 0000000..40ae37d
--- /dev/null
@@ -0,0 +1,27 @@
+const MailDev = require('maildev')
+
+// must run maildev as forked ChildProcess
+// failed instantiation stops main process with exit code 0
+process.on('message', (msg) => {
+  if (msg.start) {
+    const maildev = new MailDev({
+      ip: '127.0.0.1',
+      smtp: 1025,
+      disableWeb: true,
+      silent: true
+    })
+
+    maildev.on('new', email => {
+      process.send({ email })
+    })
+
+    maildev.listen(err => {
+      if (err) {
+        // cannot send as Error object
+        return process.send({ err: err.message })
+      }
+
+      return process.send({ err: null })
+    })
+  }
+})
diff --git a/shared/utils/miscs/email.ts b/shared/utils/miscs/email.ts
new file mode 100644 (file)
index 0000000..f9f1bd9
--- /dev/null
@@ -0,0 +1,64 @@
+import { fork, ChildProcess } from 'child_process'
+
+class MockSmtpServer {
+
+  private static instance: MockSmtpServer
+  private started = false
+  private emailChildProcess: ChildProcess
+  private emails: object[]
+
+  private constructor () {
+    this.emailChildProcess = fork(`${__dirname}/email-child-process`, [])
+
+    this.emailChildProcess.on('message', (msg) => {
+      if (msg.email) {
+        return this.emails.push(msg.email)
+      }
+    })
+
+    process.on('exit', () => this.kill())
+  }
+
+  collectEmails (emailsCollection: object[]) {
+    return new Promise((res, rej) => {
+      if (this.started) {
+        this.emails = emailsCollection
+        return res()
+      }
+
+      // ensure maildev isn't started until
+      // unexpected exit can be reported to test runner
+      this.emailChildProcess.send({ start: true })
+      this.emailChildProcess.on('exit', () => {
+        return rej(new Error('maildev exited unexpectedly, confirm port not in use'))
+      })
+      this.emailChildProcess.on('message', (msg) => {
+        if (msg.err) {
+          return rej(new Error(msg.err))
+        }
+        this.started = true
+        this.emails = emailsCollection
+        return res()
+      })
+    })
+  }
+
+  kill () {
+    if (!this.emailChildProcess) return
+
+    process.kill(this.emailChildProcess.pid)
+
+    this.emailChildProcess = null
+    MockSmtpServer.instance = null
+  }
+
+  static get Instance () {
+    return this.instance || (this.instance = new this())
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  MockSmtpServer
+}
similarity index 92%
rename from server/tests/utils/miscs/miscs.ts
rename to shared/utils/miscs/miscs.ts
index 589daa420d54c561867d6ddda827ee14828f3829..91a93b631f77673dd846f0082e86eabfc0f5441d 100644 (file)
@@ -33,8 +33,8 @@ function webtorrentAdd (torrent: string, refreshWebTorrent = false) {
 }
 
 function root () {
-  // We are in server/tests/utils/miscs
-  return join(__dirname, '..', '..', '..', '..')
+  // We are in /shared/utils/miscs
+  return join(__dirname, '..', '..', '..')
 }
 
 async function testImage (url: string, imageName: string, imagePath: string, extension = '.jpg') {
@@ -44,7 +44,7 @@ async function testImage (url: string, imageName: string, imagePath: string, ext
 
   const body = res.body
 
-  const data = await readFile(join(__dirname, '..', '..', 'fixtures', imageName + extension))
+  const data = await readFile(join(root(), 'server', 'tests', 'fixtures', imageName + extension))
   const minLength = body.length - ((20 * body.length) / 100)
   const maxLength = body.length + ((20 * body.length) / 100)
 
@@ -59,7 +59,7 @@ function buildAbsoluteFixturePath (path: string, customTravisPath = false) {
 
   if (customTravisPath && process.env.TRAVIS) return join(process.env.HOME, 'fixtures', path)
 
-  return join(__dirname, '..', '..', 'fixtures', path)
+  return join(root(), 'server', 'tests', 'fixtures', path)
 }
 
 async function generateHighBitrateVideo () {
similarity index 73%
rename from server/tests/utils/requests/activitypub.ts
rename to shared/utils/requests/activitypub.ts
index 96fee60a87211c958865cf74c98d386f8fc547a6..e2348ace0df50ef2b54a5a38ab948ab05c5229a2 100644 (file)
@@ -1,7 +1,7 @@
-import { doRequest } from '../../../helpers/requests'
-import { HTTP_SIGNATURE } from '../../../initializers'
-import { buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils'
-import { activityPubContextify } from '../../../helpers/activitypub'
+import { doRequest } from '../../../server/helpers/requests'
+import { HTTP_SIGNATURE } from '../../../server/initializers'
+import { buildGlobalHeaders } from '../../../server/lib/job-queue/handlers/utils/activitypub-http-utils'
+import { activityPubContextify } from '../../../server/helpers/activitypub'
 
 function makePOSTAPRequest (url: string, body: any, httpSignature: any, headers: any) {
   const options = {
similarity index 96%
rename from server/tests/utils/requests/requests.ts
rename to shared/utils/requests/requests.ts
index 5796540f7c3e45b4d54d44fee95c099cd30ecdff..77e9f61645fd79630afde3533b295e6081fff6e7 100644 (file)
@@ -1,5 +1,5 @@
 import * as request from 'supertest'
-import { buildAbsoluteFixturePath } from '../miscs/miscs'
+import { buildAbsoluteFixturePath, root } from '../miscs/miscs'
 import { isAbsolute, join } from 'path'
 
 function makeGetRequest (options: {
@@ -142,7 +142,7 @@ function updateAvatarRequest (options: {
   if (isAbsolute(options.fixture)) {
     filePath = options.fixture
   } else {
-    filePath = join(__dirname, '..', '..', 'fixtures', options.fixture)
+    filePath = join(root(), 'server', 'tests', 'fixtures', options.fixture)
   }
 
   return makeUploadRequest({
similarity index 96%
rename from server/tests/utils/search/videos.ts
rename to shared/utils/search/videos.ts
index 8c0037ccc1eaed20740ee728daa5ae28ae22b5f4..ba46270175f45edc395deb80a450b38c9f83897a 100644 (file)
@@ -1,7 +1,7 @@
 /* tslint:disable:no-unused-expression */
 
 import * as request from 'supertest'
-import { VideosSearchQuery } from '../../../../shared/models/search'
+import { VideosSearchQuery } from '../../models/search'
 import { immutableAssign } from '../miscs/miscs'
 
 function searchVideo (url: string, search: string) {
similarity index 94%
rename from server/tests/utils/server/config.ts
rename to shared/utils/server/config.ts
index aa3100d3439962735477487f567233170d7a78ca..0c5512bab5edbabc2aaf7d3ec248908d54620082 100644 (file)
@@ -1,5 +1,5 @@
 import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests'
-import { CustomConfig } from '../../../../shared/models/server/custom-config.model'
+import { CustomConfig } from '../../models/server/custom-config.model'
 
 function getConfig (url: string) {
   const path = '/api/v1/config'
@@ -80,12 +80,16 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) {
     admin: {
       email: 'superadmin1@example.com'
     },
+    contactForm: {
+      enabled: true
+    },
     user: {
       videoQuota: 5242881,
       videoQuotaDaily: 318742
     },
     transcoding: {
       enabled: true,
+      allowAdditionalExtensions: true,
       threads: 1,
       resolutions: {
         '240p': false,
diff --git a/shared/utils/server/contact-form.ts b/shared/utils/server/contact-form.ts
new file mode 100644 (file)
index 0000000..80394cf
--- /dev/null
@@ -0,0 +1,28 @@
+import * as request from 'supertest'
+import { ContactForm } from '../../models/server'
+
+function sendContactForm (options: {
+  url: string,
+  fromEmail: string,
+  fromName: string,
+  body: string,
+  expectedStatus?: number
+}) {
+  const path = '/api/v1/server/contact'
+
+  const body: ContactForm = {
+    fromEmail: options.fromEmail,
+    fromName: options.fromName,
+    body: options.body
+  }
+  return request(options.url)
+    .post(path)
+    .send(body)
+    .expect(options.expectedStatus || 204)
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  sendContactForm
+}
similarity index 84%
rename from server/tests/utils/server/jobs.ts
rename to shared/utils/server/jobs.ts
index 26180ec725cb2de12f27991d5e3d85aed32a56cb..692b5e24d4b6f8eb01c3a7627cb1970321b58bb6 100644 (file)
@@ -1,7 +1,7 @@
 import * as request from 'supertest'
-import { Job, JobState } from '../../../../shared/models'
-import { ServerInfo } from './servers'
+import { Job, JobState } from '../../models'
 import { wait } from '../miscs/miscs'
+import { ServerInfo } from './servers'
 
 function getJobsList (url: string, accessToken: string, state: JobState) {
   const path = '/api/v1/jobs/' + state
@@ -29,16 +29,17 @@ function getJobsListPaginationAndSort (url: string, accessToken: string, state:
 }
 
 async function waitJobs (serversArg: ServerInfo[] | ServerInfo) {
+  const pendingJobWait = process.env.NODE_PENDING_JOB_WAIT ? parseInt(process.env.NODE_PENDING_JOB_WAIT, 10) : 2000
   let servers: ServerInfo[]
 
   if (Array.isArray(serversArg) === false) servers = [ serversArg as ServerInfo ]
   else servers = serversArg as ServerInfo[]
 
   const states: JobState[] = [ 'waiting', 'active', 'delayed' ]
-  const tasks: Promise<any>[] = []
-  let pendingRequests: boolean
+  let pendingRequests = false
 
-  do {
+  function tasksBuilder () {
+    const tasks: Promise<any>[] = []
     pendingRequests = false
 
     // Check if each server has pending request
@@ -54,13 +55,16 @@ async function waitJobs (serversArg: ServerInfo[] | ServerInfo) {
       }
     }
 
-    await Promise.all(tasks)
+    return tasks
+  }
+
+  do {
+    await Promise.all(tasksBuilder())
 
     // Retry, in case of new jobs were created
     if (pendingRequests === false) {
-      await wait(1000)
-
-      await Promise.all(tasks)
+      await wait(pendingJobWait)
+      await Promise.all(tasksBuilder())
     }
 
     if (pendingRequests) {
similarity index 86%
rename from server/tests/utils/server/servers.ts
rename to shared/utils/server/servers.ts
index f358a21f13f37dddb2d2a5244fc76de7c8a09e20..cb57e0a69668c04794a374fda53452cb75625352 100644 (file)
@@ -1,7 +1,11 @@
+/* tslint:disable:no-unused-expression */
+
 import { ChildProcess, exec, fork } from 'child_process'
 import { join } from 'path'
 import { root, wait } from '../miscs/miscs'
-import { readFile } from 'fs-extra'
+import { readdir, readFile } from 'fs-extra'
+import { existsSync } from 'fs'
+import { expect } from 'chai'
 
 interface ServerInfo {
   app: ChildProcess,
@@ -115,7 +119,7 @@ function runServer (serverNumber: number, configOverride?: Object, args = []) {
   }
 
   return new Promise<ServerInfo>(res => {
-    server.app = fork(join(__dirname, '..', '..', '..', '..', 'dist', 'server.js'), args, options)
+    server.app = fork(join(root(), 'dist', 'server.js'), args, options)
     server.app.stdout.on('data', function onStdout (data) {
       let dontContinue = false
 
@@ -141,8 +145,16 @@ function runServer (serverNumber: number, configOverride?: Object, args = []) {
       if (dontContinue === true) return
 
       server.app.stdout.removeListener('data', onStdout)
+
+      process.on('exit', () => {
+        try {
+          process.kill(server.app.pid)
+        } catch { /* empty */ }
+      })
+
       res(server)
     })
+
   })
 }
 
@@ -153,6 +165,18 @@ async function reRunServer (server: ServerInfo, configOverride?: any) {
   return server
 }
 
+async function checkTmpIsEmpty (server: ServerInfo) {
+  const testDirectory = 'test' + server.serverNumber
+
+  const directoryPath = join(root(), testDirectory, 'tmp')
+
+  const directoryExists = existsSync(directoryPath)
+  expect(directoryExists).to.be.true
+
+  const files = await readdir(directoryPath)
+  expect(files).to.have.lengthOf(0)
+}
+
 function killallServers (servers: ServerInfo[]) {
   for (const server of servers) {
     process.kill(-server.app.pid)
@@ -175,6 +199,7 @@ async function waitUntilLog (server: ServerInfo, str: string, count = 1) {
 // ---------------------------------------------------------------------------
 
 export {
+  checkTmpIsEmpty,
   ServerInfo,
   flushAndRunMultipleServers,
   flushTests,
diff --git a/shared/utils/socket/socket-io.ts b/shared/utils/socket/socket-io.ts
new file mode 100644 (file)
index 0000000..854ab71
--- /dev/null
@@ -0,0 +1,13 @@
+import * as io from 'socket.io-client'
+
+function getUserNotificationSocket (serverUrl: string, accessToken: string) {
+  return io(serverUrl + '/user-notifications', {
+    query: { accessToken }
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  getUserNotificationSocket
+}
similarity index 96%
rename from server/tests/utils/users/accounts.ts
rename to shared/utils/users/accounts.ts
index 257fa5b2750ad593fa81b7e6797464d68c06dd3d..388eb6973c79a7ca3c1c3d54161f9857ebe1e72a 100644 (file)
@@ -3,7 +3,7 @@
 import { expect } from 'chai'
 import { existsSync, readdir } from 'fs-extra'
 import { join } from 'path'
-import { Account } from '../../../../shared/models/actors'
+import { Account } from '../../models/actors'
 import { root } from '../miscs/miscs'
 import { makeGetRequest } from '../requests/requests'
 
diff --git a/shared/utils/users/user-notifications.ts b/shared/utils/users/user-notifications.ts
new file mode 100644 (file)
index 0000000..c8ed7df
--- /dev/null
@@ -0,0 +1,437 @@
+/* tslint:disable:no-unused-expression */
+
+import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
+import { UserNotification, UserNotificationSetting, UserNotificationType } from '../../models/users'
+import { ServerInfo } from '..'
+import { expect } from 'chai'
+import { inspect } from 'util'
+
+function updateMyNotificationSettings (url: string, token: string, settings: UserNotificationSetting, statusCodeExpected = 204) {
+  const path = '/api/v1/users/me/notification-settings'
+
+  return makePutBodyRequest({
+    url,
+    path,
+    token,
+    fields: settings,
+    statusCodeExpected
+  })
+}
+
+function getUserNotifications (
+  url: string,
+  token: string,
+  start: number,
+  count: number,
+  unread?: boolean,
+  sort = '-createdAt',
+  statusCodeExpected = 200
+) {
+  const path = '/api/v1/users/me/notifications'
+
+  return makeGetRequest({
+    url,
+    path,
+    token,
+    query: {
+      start,
+      count,
+      sort,
+      unread
+    },
+    statusCodeExpected
+  })
+}
+
+function markAsReadNotifications (url: string, token: string, ids: number[], statusCodeExpected = 204) {
+  const path = '/api/v1/users/me/notifications/read'
+
+  return makePostBodyRequest({
+    url,
+    path,
+    token,
+    fields: { ids },
+    statusCodeExpected
+  })
+}
+function markAsReadAllNotifications (url: string, token: string, statusCodeExpected = 204) {
+  const path = '/api/v1/users/me/notifications/read-all'
+
+  return makePostBodyRequest({
+    url,
+    path,
+    token,
+    statusCodeExpected
+  })
+}
+
+async function getLastNotification (serverUrl: string, accessToken: string) {
+  const res = await getUserNotifications(serverUrl, accessToken, 0, 1, undefined, '-createdAt')
+
+  if (res.body.total === 0) return undefined
+
+  return res.body.data[0] as UserNotification
+}
+
+type CheckerBaseParams = {
+  server: ServerInfo
+  emails: object[]
+  socketNotifications: UserNotification[]
+  token: string,
+  check?: { web: boolean, mail: boolean }
+}
+
+type CheckerType = 'presence' | 'absence'
+
+async function checkNotification (
+  base: CheckerBaseParams,
+  notificationChecker: (notification: UserNotification, type: CheckerType) => void,
+  emailNotificationFinder: (email: object) => boolean,
+  checkType: CheckerType
+) {
+  const check = base.check || { web: true, mail: true }
+
+  if (check.web) {
+    const notification = await getLastNotification(base.server.url, base.token)
+
+    if (notification || checkType !== 'absence') {
+      notificationChecker(notification, checkType)
+    }
+
+    const socketNotification = base.socketNotifications.find(n => {
+      try {
+        notificationChecker(n, 'presence')
+        return true
+      } catch {
+        return false
+      }
+    })
+
+    if (checkType === 'presence') {
+      const obj = inspect(base.socketNotifications, { depth: 5 })
+      expect(socketNotification, 'The socket notification is absent. ' + obj).to.not.be.undefined
+    } else {
+      const obj = inspect(socketNotification, { depth: 5 })
+      expect(socketNotification, 'The socket notification is present. ' + obj).to.be.undefined
+    }
+  }
+
+  if (check.mail) {
+    // Last email
+    const email = base.emails
+                      .slice()
+                      .reverse()
+                      .find(e => emailNotificationFinder(e))
+
+    if (checkType === 'presence') {
+      expect(email, 'The email is absent. ' + inspect(base.emails)).to.not.be.undefined
+    } else {
+      expect(email, 'The email is present. ' + inspect(email)).to.be.undefined
+    }
+  }
+}
+
+function checkVideo (video: any, videoName?: string, videoUUID?: string) {
+  expect(video.name).to.be.a('string')
+  expect(video.name).to.not.be.empty
+  if (videoName) expect(video.name).to.equal(videoName)
+
+  expect(video.uuid).to.be.a('string')
+  expect(video.uuid).to.not.be.empty
+  if (videoUUID) expect(video.uuid).to.equal(videoUUID)
+
+  expect(video.id).to.be.a('number')
+}
+
+function checkActor (actor: any) {
+  expect(actor.displayName).to.be.a('string')
+  expect(actor.displayName).to.not.be.empty
+  expect(actor.host).to.not.be.undefined
+}
+
+function checkComment (comment: any, commentId: number, threadId: number) {
+  expect(comment.id).to.equal(commentId)
+  expect(comment.threadId).to.equal(threadId)
+}
+
+async function checkNewVideoFromSubscription (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
+  const notificationType = UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkVideo(notification.video, videoName, videoUUID)
+      checkActor(notification.video.channel)
+    } else {
+      expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
+    }
+  }
+
+  function emailFinder (email: object) {
+    return email[ 'text' ].indexOf(videoUUID) !== -1
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkVideoIsPublished (base: CheckerBaseParams, videoName: string, videoUUID: string, type: CheckerType) {
+  const notificationType = UserNotificationType.MY_VIDEO_PUBLISHED
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkVideo(notification.video, videoName, videoUUID)
+      checkActor(notification.video.channel)
+    } else {
+      expect(notification.video).to.satisfy(v => v === undefined || v.name !== videoName)
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text: string = email[ 'text' ]
+    return text.includes(videoUUID) && text.includes('Your video')
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkMyVideoImportIsFinished (
+  base: CheckerBaseParams,
+  videoName: string,
+  videoUUID: string,
+  url: string,
+  success: boolean,
+  type: CheckerType
+) {
+  const notificationType = success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      expect(notification.videoImport.targetUrl).to.equal(url)
+
+      if (success) checkVideo(notification.videoImport.video, videoName, videoUUID)
+    } else {
+      expect(notification.videoImport).to.satisfy(i => i === undefined || i.targetUrl !== url)
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text: string = email[ 'text' ]
+    const toFind = success ? ' finished' : ' error'
+
+    return text.includes(url) && text.includes(toFind)
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkUserRegistered (base: CheckerBaseParams, username: string, type: CheckerType) {
+  const notificationType = UserNotificationType.NEW_USER_REGISTRATION
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkActor(notification.account)
+      expect(notification.account.name).to.equal(username)
+    } else {
+      expect(notification).to.satisfy(n => n.type !== notificationType || n.account.name !== username)
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text: string = email[ 'text' ]
+
+    return text.includes(' registered ') && text.includes(username)
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkNewActorFollow (
+  base: CheckerBaseParams,
+  followType: 'channel' | 'account',
+  followerName: string,
+  followerDisplayName: string,
+  followingDisplayName: string,
+  type: CheckerType
+) {
+  const notificationType = UserNotificationType.NEW_FOLLOW
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkActor(notification.actorFollow.follower)
+      expect(notification.actorFollow.follower.displayName).to.equal(followerDisplayName)
+      expect(notification.actorFollow.follower.name).to.equal(followerName)
+      expect(notification.actorFollow.follower.host).to.not.be.undefined
+
+      expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName)
+      expect(notification.actorFollow.following.type).to.equal(followType)
+    } else {
+      expect(notification).to.satisfy(n => {
+        return n.type !== notificationType ||
+          (n.actorFollow.follower.name !== followerName && n.actorFollow.following !== followingDisplayName)
+      })
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text: string = email[ 'text' ]
+
+    return text.includes('Your ' + followType) && text.includes(followingDisplayName) && text.includes(followerDisplayName)
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkCommentMention (
+  base: CheckerBaseParams,
+  uuid: string,
+  commentId: number,
+  threadId: number,
+  byAccountDisplayName: string,
+  type: CheckerType
+) {
+  const notificationType = UserNotificationType.COMMENT_MENTION
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkComment(notification.comment, commentId, threadId)
+      checkActor(notification.comment.account)
+      expect(notification.comment.account.displayName).to.equal(byAccountDisplayName)
+
+      checkVideo(notification.comment.video, undefined, uuid)
+    } else {
+      expect(notification).to.satisfy(n => n.type !== notificationType || n.comment.id !== commentId)
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text: string = email[ 'text' ]
+
+    return text.includes(' mentioned ') && text.includes(uuid) && text.includes(byAccountDisplayName)
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+let lastEmailCount = 0
+async function checkNewCommentOnMyVideo (base: CheckerBaseParams, uuid: string, commentId: number, threadId: number, type: CheckerType) {
+  const notificationType = UserNotificationType.NEW_COMMENT_ON_MY_VIDEO
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      checkComment(notification.comment, commentId, threadId)
+      checkActor(notification.comment.account)
+      checkVideo(notification.comment.video, undefined, uuid)
+    } else {
+      expect(notification).to.satisfy((n: UserNotification) => {
+        return n === undefined || n.comment === undefined || n.comment.id !== commentId
+      })
+    }
+  }
+
+  const commentUrl = `http://localhost:9001/videos/watch/${uuid};threadId=${threadId}`
+  function emailFinder (email: object) {
+    return email[ 'text' ].indexOf(commentUrl) !== -1
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+
+  if (type === 'presence') {
+    // We cannot detect email duplicates, so check we received another email
+    expect(base.emails).to.have.length.above(lastEmailCount)
+    lastEmailCount = base.emails.length
+  }
+}
+
+async function checkNewVideoAbuseForModerators (base: CheckerBaseParams, videoUUID: string, videoName: string, type: CheckerType) {
+  const notificationType = UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS
+
+  function notificationChecker (notification: UserNotification, type: CheckerType) {
+    if (type === 'presence') {
+      expect(notification).to.not.be.undefined
+      expect(notification.type).to.equal(notificationType)
+
+      expect(notification.videoAbuse.id).to.be.a('number')
+      checkVideo(notification.videoAbuse.video, videoName, videoUUID)
+    } else {
+      expect(notification).to.satisfy((n: UserNotification) => {
+        return n === undefined || n.videoAbuse === undefined || n.videoAbuse.video.uuid !== videoUUID
+      })
+    }
+  }
+
+  function emailFinder (email: object) {
+    const text = email[ 'text' ]
+    return text.indexOf(videoUUID) !== -1 && text.indexOf('abuse') !== -1
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, type)
+}
+
+async function checkNewBlacklistOnMyVideo (
+  base: CheckerBaseParams,
+  videoUUID: string,
+  videoName: string,
+  blacklistType: 'blacklist' | 'unblacklist'
+) {
+  const notificationType = blacklistType === 'blacklist'
+    ? UserNotificationType.BLACKLIST_ON_MY_VIDEO
+    : UserNotificationType.UNBLACKLIST_ON_MY_VIDEO
+
+  function notificationChecker (notification: UserNotification) {
+    expect(notification).to.not.be.undefined
+    expect(notification.type).to.equal(notificationType)
+
+    const video = blacklistType === 'blacklist' ? notification.videoBlacklist.video : notification.video
+
+    checkVideo(video, videoName, videoUUID)
+  }
+
+  function emailFinder (email: object) {
+    const text = email[ 'text' ]
+    return text.indexOf(videoUUID) !== -1 && text.indexOf(' ' + blacklistType) !== -1
+  }
+
+  await checkNotification(base, notificationChecker, emailFinder, 'presence')
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  CheckerBaseParams,
+  CheckerType,
+  checkNotification,
+  markAsReadAllNotifications,
+  checkMyVideoImportIsFinished,
+  checkUserRegistered,
+  checkVideoIsPublished,
+  checkNewVideoFromSubscription,
+  checkNewActorFollow,
+  checkNewCommentOnMyVideo,
+  checkNewBlacklistOnMyVideo,
+  checkCommentMention,
+  updateMyNotificationSettings,
+  checkNewVideoAbuseForModerators,
+  getUserNotifications,
+  markAsReadNotifications,
+  getLastNotification
+}
similarity index 94%
rename from server/tests/utils/users/users.ts
rename to shared/utils/users/users.ts
index f129923157102b19f090478c5318181e7ee94679..61a7e375722e0aba6f3cc0748e85415eb9e2627d 100644 (file)
@@ -1,8 +1,8 @@
 import * as request from 'supertest'
 import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests'
 
-import { UserRole } from '../../../../shared/index'
-import { NSFWPolicyType } from '../../../../shared/models/videos/nsfw-policy.type'
+import { UserRole } from '../../index'
+import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type'
 
 function createUser (
   url: string,
@@ -162,14 +162,15 @@ function unblockUser (url: string, userId: number | string, accessToken: string,
 
 function updateMyUser (options: {
   url: string
-  accessToken: string,
-  currentPassword?: string,
-  newPassword?: string,
-  nsfwPolicy?: NSFWPolicyType,
-  email?: string,
+  accessToken: string
+  currentPassword?: string
+  newPassword?: string
+  nsfwPolicy?: NSFWPolicyType
+  email?: string
   autoPlayVideo?: boolean
-  displayName?: string,
+  displayName?: string
   description?: string
+  videosHistoryEnabled?: boolean
 }) {
   const path = '/api/v1/users/me'
 
@@ -181,6 +182,9 @@ function updateMyUser (options: {
   if (options.email !== undefined && options.email !== null) toSend['email'] = options.email
   if (options.description !== undefined && options.description !== null) toSend['description'] = options.description
   if (options.displayName !== undefined && options.displayName !== null) toSend['displayName'] = options.displayName
+  if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) {
+    toSend['videosHistoryEnabled'] = options.videosHistoryEnabled
+  }
 
   return makePutBodyRequest({
     url: options.url,
similarity index 94%
rename from server/tests/utils/videos/video-abuses.ts
rename to shared/utils/videos/video-abuses.ts
index 4ad82ad8cb7dcec2cc1691e00d462144834daf81..7f011ec0ff0acd4baa7754d7809471882fe5ba1d 100644 (file)
@@ -1,5 +1,5 @@
 import * as request from 'supertest'
-import { VideoAbuseUpdate } from '../../../../shared/models/videos/abuse/video-abuse-update.model'
+import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model'
 import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests'
 
 function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) {
similarity index 90%
rename from server/tests/utils/videos/video-blacklist.ts
rename to shared/utils/videos/video-blacklist.ts
index 2c176fde064c79126525c5b944dab43d41ae12dc..f2ae0ed26918ff5094c2b1d29f66373db4720bdf 100644 (file)
@@ -1,11 +1,18 @@
 import * as request from 'supertest'
 
-function addVideoToBlacklist (url: string, token: string, videoId: number | string, reason?: string, specialStatus = 204) {
+function addVideoToBlacklist (
+  url: string,
+  token: string,
+  videoId: number | string,
+  reason?: string,
+  unfederate?: boolean,
+  specialStatus = 204
+) {
   const path = '/api/v1/videos/' + videoId + '/blacklist'
 
   return request(url)
           .post(path)
-          .send({ reason })
+          .send({ reason, unfederate })
           .set('Accept', 'application/json')
           .set('Authorization', 'Bearer ' + token)
           .expect(specialStatus)
similarity index 97%
rename from server/tests/utils/videos/video-channels.ts
rename to shared/utils/videos/video-channels.ts
index 70e8d1a6b7e4905a9e17ee0e978771960c2f43e6..3935c261e9803a4d047af6f99c993e3369dd2cea 100644 (file)
@@ -1,5 +1,5 @@
 import * as request from 'supertest'
-import { VideoChannelCreate, VideoChannelUpdate } from '../../../../shared/models/videos'
+import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
 import { updateAvatarRequest } from '../requests/requests'
 
 function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
diff --git a/shared/utils/videos/video-history.ts b/shared/utils/videos/video-history.ts
new file mode 100644 (file)
index 0000000..dc7095b
--- /dev/null
@@ -0,0 +1,39 @@
+import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
+
+function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number, statusCodeExpected = 204) {
+  const path = '/api/v1/videos/' + videoId + '/watching'
+  const fields = { currentTime }
+
+  return makePutBodyRequest({ url, path, token, fields, statusCodeExpected })
+}
+
+function listMyVideosHistory (url: string, token: string) {
+  const path = '/api/v1/users/me/history/videos'
+
+  return makeGetRequest({
+    url,
+    path,
+    token,
+    statusCodeExpected: 200
+  })
+}
+
+function removeMyVideosHistory (url: string, token: string, beforeDate?: string) {
+  const path = '/api/v1/users/me/history/videos/remove'
+
+  return makePostBodyRequest({
+    url,
+    path,
+    token,
+    fields: beforeDate ? { beforeDate } : {},
+    statusCodeExpected: 204
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+  userWatchVideo,
+  listMyVideosHistory,
+  removeMyVideosHistory
+}
similarity index 89%
rename from server/tests/utils/videos/video-imports.ts
rename to shared/utils/videos/video-imports.ts
index eb985a5b16ddf2dccf4c6eb8a96a53025ebb3215..ec77cdcda6503c770b2a3c6120f197368aa3b3e3 100644 (file)
@@ -1,4 +1,5 @@
-import { VideoImportCreate } from '../../../../shared/models/videos'
+
+import { VideoImportCreate } from '../../models/videos'
 import { makeGetRequest, makeUploadRequest } from '../requests/requests'
 
 function getYoutubeVideoUrl () {
@@ -10,6 +11,10 @@ function getMagnetURI () {
   return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4'
 }
 
+function getBadVideoUrl () {
+  return 'https://download.cpy.re/peertube/bad_video.mp4'
+}
+
 function importVideo (url: string, token: string, attributes: VideoImportCreate) {
   const path = '/api/v1/videos/imports'
 
@@ -44,6 +49,7 @@ function getMyVideoImports (url: string, token: string, sort?: string) {
 // ---------------------------------------------------------------------------
 
 export {
+  getBadVideoUrl,
   getYoutubeVideoUrl,
   importVideo,
   getMagnetURI,
similarity index 98%
rename from server/tests/utils/videos/videos.ts
rename to shared/utils/videos/videos.ts
index d6c3e5dac06b0ce5de86906053a6404b05257316..0cf6e7c4f93d2f04398d929d7cc70ae23a8c2151 100644 (file)
@@ -16,8 +16,9 @@ import {
   ServerInfo,
   testImage
 } from '../'
-import { VideoDetails, VideoPrivacy } from '../../../../shared/models/videos'
-import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
+
+import { VideoDetails, VideoPrivacy } from '../../models/videos'
+import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
 import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
 
 type VideoAttributes = {
@@ -270,7 +271,7 @@ function removeVideo (url: string, token: string, id: number | string, expectedS
 async function checkVideoFilesWereRemoved (
   videoUUID: string,
   serverNumber: number,
-  directories = [ 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]
+  directories = [ 'redundancy', 'videos', 'thumbnails', 'torrents', 'previews', 'captions' ]
 ) {
   const testDirectory = 'test' + serverNumber
 
@@ -416,7 +417,7 @@ function rateVideo (url: string, accessToken: string, id: number, rating: string
 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
   return new Promise<any>((res, rej) => {
     const torrentName = videoUUID + '-' + resolution + '.torrent'
-    const torrentPath = join(__dirname, '..', '..', '..', '..', 'test' + server.serverNumber, 'torrents', torrentName)
+    const torrentPath = join(root(), 'test' + server.serverNumber, 'torrents', torrentName)
     readFile(torrentPath, (err, data) => {
       if (err) return rej(err)
 
index 9848c93ee1ffcb99ca320665d5901b1892bef803..f2bb945f92c5b148b437736edcd98a6e40a0f4ce 100644 (file)
@@ -1,7 +1,7 @@
 openapi: 3.0.0
 info:
   title: PeerTube
-  version: 1.1.0
+  version: 1.2.0
   contact:
     name: PeerTube Community
     url: 'https://joinpeertube.org'
@@ -23,7 +23,7 @@ info:
 
     # Authentication
     When you sign up for an account, you are given the possibility to generate
-    sessions, and authenticate using this session token. One session token can 
+    sessions, and authenticate using this session token. One session token can
     currently be used at a time.
 
     # Errors
@@ -61,7 +61,7 @@ tags:
     description: >
       Managing servers which the instance interacts with is crucial to the
       concept of federation in PeerTube and external video indexation. The PeerTube
-      server then deals with inter-server ActivityPub operations and propagates 
+      server then deals with inter-server ActivityPub operations and propagates
       information across its social graph by posting activities to actors' inbox
       endpoints.
   - name: Video Abuse
@@ -492,7 +492,8 @@ paths:
     get:
       summary: Get current user information
       security:
-        - OAuth2: []
+        - OAuth2:
+          - user
       tags:
         - User
       responses:
@@ -507,7 +508,8 @@ paths:
     put:
       summary: Update current user information
       security:
-        - OAuth2: []
+        - OAuth2:
+          - user
       tags:
         - User
       responses:
@@ -523,7 +525,8 @@ paths:
     get:
       summary: Get current user used quota
       security:
-        - OAuth2: []
+        - OAuth2:
+          - user
       tags:
         - User
       responses:
@@ -558,7 +561,71 @@ paths:
     get:
       summary: Get videos of the current user
       security:
-        - OAuth2: []
+        - OAuth2:
+          - user
+      tags:
+        - User
+      parameters:
+        - $ref: '#/components/parameters/start'
+        - $ref: '#/components/parameters/count'
+        - $ref: '#/components/parameters/sort'
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/Video'
+  /users/me/subscriptions:
+    get:
+      summary: Get subscriptions of the current user
+      security:
+        - OAuth2:
+            - user
+      tags:
+        - User
+      parameters:
+        - $ref: '#/components/parameters/start'
+        - $ref: '#/components/parameters/count'
+        - $ref: '#/components/parameters/sort'
+      responses:
+        '200':
+          description: successful operation
+    post:
+      summary: Add subscription to the current user
+      security:
+        - OAuth2:
+            - user
+      tags:
+        - User
+      responses:
+        '200':
+          description: successful operation
+  /users/me/subscriptions/exist:
+    get:
+      summary: Get if subscriptions exist for the current user
+      security:
+        - OAuth2:
+            - user
+      tags:
+        - User
+      parameters:
+        - $ref: '#/components/parameters/subscriptionsUris'
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                type: object
+  /users/me/subscriptions/videos:
+    get:
+      summary: Get videos of subscriptions of the current user
+      security:
+        - OAuth2:
+          - user
       tags:
         - User
       parameters:
@@ -574,6 +641,31 @@ paths:
                 type: array
                 items:
                   $ref: '#/components/schemas/Video'
+  '/users/me/subscriptions/{uri}':
+    get:
+      summary: Get subscription of the current user for a given uri
+      security:
+        - OAuth2:
+            - user
+      tags:
+        - User
+      responses:
+        '200':
+          description: successful operation
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/VideoChannel'
+    delete:
+      summary: Delete subscription of the current user for a given uri
+      security:
+        - OAuth2:
+            - user
+      tags:
+        - User
+      responses:
+        '200':
+          description: successful operation
   /users/register:
     post:
       summary: Register a user
@@ -751,7 +843,9 @@ paths:
                   type: string
                 tags:
                   description: Video tags
-                  type: string
+                  type: array
+                  items:
+                    type: string
                 commentsEnabled:
                   description: Enable or disable comments for this video
                   type: string
@@ -820,7 +914,7 @@ paths:
           $ref: '#/paths/~1users~1me/put/responses/204'
   '/videos/{id}/watching':
     put:
-      summary: Indicate progress of in watching the video by its id for a user
+      summary: Set watching progress of a video by its id for a user
       tags:
         - Video
       security:
@@ -958,7 +1052,9 @@ paths:
                   type: string
                 tags:
                   description: Video tags
-                  type: string
+                  type: array
+                  items:
+                    type: string
                 commentsEnabled:
                   description: Enable or disable comments for this video
                   type: string
@@ -1434,6 +1530,8 @@ components:
         - type: array
           items:
             type: number
+      style: form
+      explode: false
     tagsOneOf:
       name: tagsOneOf
       in: query
@@ -1445,6 +1543,8 @@ components:
         - type: array
           items:
             type: string
+      style: form
+      explode: false
     tagsAllOf:
       name: tagsAllOf
       in: query
@@ -1456,6 +1556,8 @@ components:
         - type: array
           items:
             type: string
+      style: form
+      explode: false
     languageOneOf:
       name: languageOneOf
       in: query
@@ -1463,10 +1565,12 @@ components:
       description: language id of the video
       schema:
         oneOf:
-        - type: number
+        - type: string
         - type: array
           items:
-            type: number
+            type: string
+      style: form
+      explode: false
     licenceOneOf:
       name: licenceOneOf
       in: query
@@ -1478,6 +1582,8 @@ components:
         - type: array
           items:
             type: number
+      style: form
+      explode: false
     nsfw:
       name: nsfw
       in: query
@@ -1501,6 +1607,15 @@ components:
         enum:
         - local
         - all-local
+    subscriptionsUris:
+      name: uris
+      in: query
+      required: true
+      description: list of uris to check if each is part of the user subscriptions
+      schema:
+        type: array
+        items:
+          type: string
   requestBodies:
     VideoChannelInput:
       content:
index 1c77395258c7fddd367da618c8479c8c4dc51b26..1a9ba7d2b8a884b8a2f3ee5135874de48732d37a 100644 (file)
@@ -4,13 +4,13 @@
 <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
 **Table of Contents**  
 
-- [CLI wrapper](#cli-wrapper)
 - [Remote Tools](#remote-tools)
   - [Dependencies](#dependencies)
   - [Installation](#installation)
-  - [peertube-import-videos.js](#peertube-import-videosjs)
-  - [peertube-upload.js](#peertube-uploadjs)
-  - [peertube-watch.js](#peertube-watchjs)
+  - [CLI wrapper](#cli-wrapper)
+    - [peertube-import-videos.js](#peertube-import-videosjs)
+    - [peertube-upload.js](#peertube-uploadjs)
+    - [peertube-watch.js](#peertube-watchjs)
 - [Server tools](#server-tools)
   - [parse-log](#parse-log)
   - [create-transcoding-job.js](#create-transcoding-jobjs)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
-## CLI wrapper
+## Remote Tools
+
+You need at least 512MB RAM to run the script.
+Scripts can be launched directly from a PeerTube server, or from a separate server, even a desktop PC.
+You need to follow all the following steps even if you are on a PeerTube server (including cloning the git repository in a different directory than your production installation because the scripts utilize non-production dependencies).
+
+### Dependencies
+
+Install the [PeerTube dependencies](dependencies.md).
+
+### Installation
+
+Clone the PeerTube repo to get the latest version (even if you are on your PeerTube server):
+
+```
+$ git clone https://github.com/Chocobozzz/PeerTube.git
+$ CLONE="$(pwd)/PeerTube"
+```
+
+Run ``yarn install --pure-lockfile``
+```
+$ cd ${CLONE}
+$ yarn install --pure-lockfile
+```
+
+Build server tools:
+```
+$ cd ${CLONE}
+$ npm run build:server
+```
+
+### CLI wrapper
 
-The wrapper provides a convenient interface to most scripts, and requires the [same dependencies](#dependencies). You can access it as `peertube` via an alias in your `.bashrc` like `alias peertube="node ${PEERTUBE_PATH}/dist/server/tools/peertube.js"`:
+The wrapper provides a convenient interface to the following scripts.
+You can access it as `peertube` via an alias in your `.bashrc` like `alias peertube="cd /your/peertube/directory/ && node ./dist/server/tools/peertube.js"` (you have to keep the `cd` command):
 
 ```
   Usage: peertube [command] [options]
@@ -51,12 +83,12 @@ The wrapper provides a convenient interface to most scripts, and requires the [s
 The wrapper can keep track of instances you have an account on. We limit to one account per instance for now.
 
 ```bash
-$ peertube auth add -u "PEERTUBE_URL" -U "PEERTUBE_USER" --password "PEERTUBE_PASSWORD"
+$ peertube auth add -u 'PEERTUBE_URL' -U 'PEERTUBE_USER' --password 'PEERTUBE_PASSWORD'
 $ peertube auth list
 ┌──────────────────────────────┬──────────────────────────────┐
 │ instance                     │ login                        │
 ├──────────────────────────────┼──────────────────────────────┤
-│ "PEERTUBE_URL"               │ "PEERTUBE_USER"              │
+│ 'PEERTUBE_URL'               │ 'PEERTUBE_USER'              │
 └──────────────────────────────┴──────────────────────────────┘
 ```
 
@@ -72,53 +104,22 @@ And now that your video is online, you can watch it from the confort of your ter
 $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10
 ```
 
-## Remote Tools
-
-You need at least 512MB RAM to run the script.
-Scripts can be launched directly from a PeerTube server, or from a separate server, even a desktop PC.
-You need to follow all the following steps even if you are on a PeerTube server (including cloning the git repository in a different directory than your production installation because the scripts utilize non-production dependencies).
-
-### Dependencies
-
-Install the [PeerTube dependencies](dependencies.md).
-
-### Installation
-
-Clone the PeerTube repo to get the latest version (even if you are on your PeerTube server):
-
-```
-$ git clone https://github.com/Chocobozzz/PeerTube.git
-$ CLONE="$(pwd)/PeerTube"
-```
-
-Run ``yarn install``
-```
-$ cd ${CLONE}
-$ yarn install
-```
-
-Build server tools:
-```
-$ cd ${CLONE}
-$ npm run build:server
-```
-
-### peertube-import-videos.js
+#### peertube-import-videos.js
 
 You can use this script to import videos from all [supported sites of youtube-dl](https://rg3.github.io/youtube-dl/supportedsites.html) into PeerTube.  
 Be sure you own the videos or have the author's authorization to do so.
 
 ```sh
 $ node dist/server/tools/peertube-import-videos.js \
-    -u "PEERTUBE_URL" \
-    -U "PEERTUBE_USER" \
-    --password "PEERTUBE_PASSWORD" \
-    -t "TARGET_URL"
+    -u 'PEERTUBE_URL' \
+    -U 'PEERTUBE_USER' \
+    --password 'PEERTUBE_PASSWORD' \
+    -t 'TARGET_URL'
 ```
 
 * `PEERTUBE_URL` : the full URL of your PeerTube server where you want to import, eg: https://peertube.cpy.re
 * `PEERTUBE_USER` : your PeerTube account where videos will be uploaded
-* `PEERTUBE_PASSWORD` : password of your PeerTube account (if omitted, you will be prompted for it)
+* `PEERTUBE_PASSWORD` : password of your PeerTube account (if `PEERTUBE_PASSWORD` is omitted, you will be prompted for it)
 * `TARGET_URL` : the target url you want to import. Examples:
   * YouTube:
     * Channel: https://www.youtube.com/channel/ChannelId
@@ -133,7 +134,7 @@ Already downloaded videos will not be uploaded twice, so you can run and re-run
 Videos will be publicly available after transcoding (you can see them before that in your account on the web interface).
 
 
-### peertube-upload.js
+#### peertube-upload.js
 
 You can use this script to import videos directly from the CLI.
 
@@ -144,7 +145,7 @@ $ cd ${CLONE}
 $ node dist/server/tools/peertube-upload.js --help
 ```
 
-### peertube-watch.js
+#### peertube-watch.js
 
 You can use this script to play videos directly from the CLI.
 
@@ -198,10 +199,10 @@ $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production
 ### prune-storage.js
 
 Some transcoded videos or shutdown at a bad time can leave some unused files on your storage.
-To delete them (a confirmation will be demanded first):
+Stop PeerTube and delete these files (a confirmation will be demanded first):
 
 ```
-$ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run prune-storage
+$ sudo systemctl stop peertube && sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run prune-storage
 ```
 
 ### optimize-old-videos.js
index f27def3b457eb3785ae405ab8230dd8cade254e5..802d6b2cad6ed0d0d4b87d325ef5e753633ef29a 100644 (file)
@@ -18,3 +18,4 @@ PEERTUBE_ADMIN_EMAIL=admin@domain.tld
 # /!\ Prefer to use the PeerTube admin interface to set the following configurations /!\
 #PEERTUBE_SIGNUP_ENABLED=true
 #PEERTUBE_TRANSCODING_ENABLED=true
+#PEERTUBE_CONTACT_FORM_ENABLED=true
index cfc30632cbc2020abeef37239123aa77790ba90a..8604939aa2dcd00a42791393862a81e47f7ca975 100644 (file)
@@ -50,6 +50,11 @@ user:
 admin:
   email: "PEERTUBE_ADMIN_EMAIL"
 
+contact_form:
+  enabled:
+    __name: "PEERTUBE_CONTACT_FORM_ENABLED"
+    __format: "json"
+
 signup:
   enabled:
     __name: "PEERTUBE_SIGNUP_ENABLED"
@@ -101,9 +106,11 @@ transcoding:
     1080:
       __name: "PEERTUBE_TRANSCODING_1080P"
       __format: "json"
-    
 
 instance:
   name: "PEERTUBE_INSTANCE_NAME"
   description: "PEERTUBE_INSTANCE_DESCRIPTION"
   terms: "PEERTUBE_INSTANCE_TERMS"
+
+services:
+  csp-logger: "PEERTUBE_SERVICES_CSPLOGGER"
index 4970bbccab70497b769c73563d8383e12afc1b12..846c838e85c6d964147e45065cfaf6f52d036aba 100644 (file)
@@ -32,8 +32,10 @@ redis:
 
 # From the project root directory
 storage:
+  tmp: '../data/tmp/'
   avatars: '../data/avatars/'
   videos: '../data/videos/'
+  redundancy: '../data/redundancy/'
   logs: '../data/logs/'
   previews: '../data/previews/'
   thumbnails: '../data/thumbnails/'
index 6dbbfddf6438fb93b87d325cba65222f4a56bf79..7dd626b9f02ae06d74cdf474ba90afedc1542d0a 100755 (executable)
@@ -9,7 +9,7 @@ fi
 # Always copy default and custom env configuration file, in cases where new keys were added
 cp /app/config/default.yaml /config
 cp /app/support/docker/production/config/custom-environment-variables.yaml /config
-chown -R peertube:peertube /config
+find /config ! -user peertube -exec chown peertube:peertube {} \;
 
 # first arg is `-f` or `--some-option`
 # or first arg is `something.conf`
@@ -19,7 +19,7 @@ fi
 
 # allow the container to be started with `--user`
 if [ "$1" = 'npm' -a "$(id -u)" = '0' ]; then
-    chown -R peertube:peertube /data
+    find /data ! -user peertube -exec  chown peertube:peertube {} \;
     exec gosu peertube "$0" "$@"
 fi
 
index 78fdf5848ad68b17e8c42fb75fd808271f9754a2..5d14c58ae79b2aa7d808aca123b8c3cf388bdac7 100755 (executable)
@@ -16,6 +16,7 @@ load_rc_config $name
 
 : ${peertube_enable:=NO}
 
+sig_stop=-KILL
 peertube_chdir="/var/www/peertube/peertube-latest"
 peertube_env="HOME=/var/www/peertube \
 NODE_ENV=production \
@@ -23,7 +24,7 @@ NODE_CONFIG_DIR=/var/www/peertube/config \
 USER=peertube"
 peertube_user=peertube
 
-command="/usr/local/bin/npm"
-command_args="start >> /var/log/peertube/${name}.log 2>&1 &"
+command="/usr/local/bin/node"
+command_args="dist/server >> /var/log/peertube/${name}.log 2>&1 &"
 
 run_rc_command "$1"
index b0003113371fb5601ee3783a32f5582b0dd33127..54ffdcc3298af304dffe6c1f4b9d37665759382f 100644 (file)
@@ -96,8 +96,18 @@ server {
     proxy_set_header Host $host;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
-    # Hard limit, PeerTube does not support videos > 8GB
+    # This is the maximum upload size, which roughly matches the maximum size of a video file
+    # you can send via the API or the web interface. By default this is 8GB, but administrators
+    # can increase or decrease the limit. Currently there's no way to communicate this limit
+    # to users automatically, so you may want to leave a note in your instance 'about' page if
+    # you change this.
+    #
+    # Note that temporary space is needed equal to the total size of all concurrent uploads.
+    # This data gets stored in /var/lib/nginx by default, so you may want to put this directory
+    # on a dedicated filesystem.
+    #
     client_max_body_size 8G;
+
     proxy_connect_timeout       600;
     proxy_send_timeout          600;
     proxy_read_timeout          600;
@@ -105,7 +115,7 @@ server {
   }
 
   # Bypass PeerTube for performance reasons. Could be removed
-  location /static/webseed {
+  location ~ ^/static/(webseed|redundancy)/ {
     # Clients usually have 4 simultaneous webseed connections, so the real limit is 3MB/s per client
     limit_rate 800k;
 
@@ -128,7 +138,12 @@ server {
       access_log off;
     }
 
-    alias /var/www/peertube/storage/videos;
+    root /var/www/peertube/storage;
+
+    rewrite ^/static/webseed/(.*)$ /videos/$1 break;
+    rewrite ^/static/redundancy/(.*)$ /redundancy/$1 break;
+
+    try_files $uri /;
   }
 
   # Websocket tracker
@@ -143,4 +158,16 @@ server {
     proxy_set_header Host $host;
     proxy_pass http://localhost:9000;
   }
+
+  location /socket.io {
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header Host $host;
+
+    proxy_pass http://localhost:9000;
+
+    # enable WebSockets
+    proxy_http_version 1.1;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+  }
 }
index 88856385c464e298d3b7f14e55546d8815a3c311..fba644788dc08edace817d455cb514d7485c8159 100644 (file)
@@ -15,5 +15,24 @@ StandardError=syslog
 SyslogIdentifier=peertube
 Restart=always
 
+; Some security directives.
+; Use private /tmp and /var/tmp folders inside a new file system namespace,
+; which are discarded after the process stops.
+PrivateTmp=true
+; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
+ProtectSystem=full
+; Sets up a new /dev mount for the process and only adds API pseudo devices
+; like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled
+; by default because it may not work on devices like the Raspberry Pi.
+PrivateDevices=false
+; Ensures that the service process and all its children can never gain new
+; privileges through execve().
+NoNewPrivileges=true
+; This makes /home, /root, and /run/user inaccessible and empty for processes invoked
+; by this unit. Make sure that you do not depend on data inside these folders.
+ProtectHome=true
+; Drops the sys admin capability from the daemon.
+CapabilityBoundingSet=~CAP_SYS_ADMIN
+
 [Install]
 WantedBy=multi-user.target
index b961fb5cc867e5d7d34c73916220b8005780dcf5..1e759af1b6a18587d9f12d91368217dc1c77de8c 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,20 @@
 # yarn lockfile v1
 
 
+"@iamstarkov/listr-update-renderer@0.4.1":
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/@iamstarkov/listr-update-renderer/-/listr-update-renderer-0.4.1.tgz#d7c48092a2dcf90fd672b6c8b458649cb350c77e"
+  integrity sha512-IJyxQWsYDEkf8C8QthBn5N8tIUR9V9je6j3sMIpAkonaadjbvxmRC6RAhpa3RKxndhNnU2M6iNbtJwd7usQYIA==
+  dependencies:
+    chalk "^1.1.3"
+    cli-truncate "^0.2.1"
+    elegant-spinner "^1.0.1"
+    figures "^1.7.0"
+    indent-string "^3.0.0"
+    log-symbols "^1.0.2"
+    log-update "^2.3.0"
+    strip-ansi "^3.0.1"
+
 "@samverschueren/stream-to-observable@^0.3.0":
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@@ -44,7 +58,7 @@
     "@types/connect" "*"
     "@types/node" "*"
 
-"@types/bull@^3.3.12":
+"@types/bull@3.4.0":
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/@types/bull/-/bull-3.4.0.tgz#18ffefefa4dd1cfbdbdc8ca7df56c934459f6b9d"
   integrity sha512-NVD2X+cUu1qNv6blsOfCr2fVsD3+O13U19dFuy9Du7PWfn1/gjFZEDk220uBuRSH5JyaP4nV6S8BLjsT5/bXUg==
     "@types/express" "*"
 
 "@types/node@*", "@types/node@^10.0.8":
-  version "10.12.8"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.8.tgz#d0a3ab5a6e61458c492304e2776ac136b81db927"
-  integrity sha512-INamyRZG4rW3lDCUmwVd5Xho/bXvQm/v1yP8V0UN1RuInU7RoWoaO570b+yLX4Ia/0szsx1wa8VzcsVlsvbWLA==
+  version "10.12.12"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47"
+  integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==
 
 "@types/node@6.0.41":
   version "6.0.41"
     "@types/node" "*"
 
 "@types/oauth2-server@^3.0.8":
-  version "3.0.9"
-  resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.9.tgz#e3f32011862f03f399635c5916d5a383bca26fe2"
-  integrity sha512-NixZjyKS4TCM1mMr6QViK0rxR8iMHiE1utYje+ZGne1SgJQzLT3OOAjCrnRp70G+L8W1BXnzIPPaIxj1kYJHNg==
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.10.tgz#ea671a6ad3d02062aac5f7c1ba1fb9c468314db0"
+  integrity sha512-1XYQdBrBuGimRhGLk9XavjGY2h5IYmT0rTi3pDAWzq6xRWZp+LCAwNm8YNYdDwQxBp//eogtZePe8mS7QPDiNg==
   dependencies:
     "@types/express" "*"
 
   integrity sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw==
 
 "@types/redis@^2.8.5":
-  version "2.8.7"
-  resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.7.tgz#e0825093fb1af9d5b4a7246c6d7d1163cc842c35"
-  integrity sha512-ZMW8M5LRxU0D4u2GhnCEqJ1/mUJKSudlCWxeP1FRxfZQqr0Pb4tonPLzDEyRpC50uvEfAP3xOLjDuUOWi0QHCQ==
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.8.tgz#70855e79a6020080cca3cb5f1f5ee7f11b49a979"
+  integrity sha512-o/1ufNVPA92uum9HFbEiXXIHBuLywSwHQtAZoACMc1FhPXS5YftybBC1EI0zjdbUb273VVWF0Ivll/bq4g+gyw==
   dependencies:
     "@types/node" "*"
 
   dependencies:
     "@types/node" "*"
 
+"@types/socket.io@^2.1.2":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.2.tgz#7165c2587cc3b86b44aa78e2a0060140551de211"
+  integrity sha512-Ind+4qMNfQ62llyB4IMs1D8znMEBsMKohZBPqfBUIXqLQ9bdtWIbNTBWwtdcBWJKnokMZGcmWOOKslatni5vtA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/superagent@*":
   version "3.8.4"
   resolved "https://registry.yarnpkg.com/@types/superagent/-/superagent-3.8.4.tgz#24a5973c7d1a9c024b4bbda742a79267c33fb86a"
     "@types/node" "*"
 
 "@types/supertest@^2.0.3":
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.6.tgz#a0665350c0e36315e1bccdf4785f2b76fcb71b6b"
-  integrity sha512-qRvPP8dO7IBqJz8LaQ7/Lw2oo/geiDUPAMx/L+CQCkR9sN622O30XCH7RSyUmilyCSyjxyhJ7cEtd3hmwPwvhw==
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.7.tgz#46ff6508075cd4519736be060f0d6331a5c8ca7b"
+  integrity sha512-GibTh4OTkal71btYe2fpZP/rVHIPnnUsYphEaoywVHo+mo2a/LhlOFkIm5wdN0H0DA0Hx8x+tKgCYMD9elHu5w==
   dependencies:
     "@types/superagent" "*"
 
@@ -409,7 +430,7 @@ accepts@~1.2.12:
     mime-types "~2.1.6"
     negotiator "0.5.3"
 
-accepts@~1.3.5:
+accepts@~1.3.4, accepts@~1.3.5:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2"
   integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I=
@@ -477,9 +498,9 @@ ajv@^4.7.0:
     json-stable-stringify "^1.0.1"
 
 ajv@^6.5.5:
-  version "6.5.5"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.5.tgz#cf97cdade71c6399a92c6d6c4177381291b781a1"
-  integrity sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==
+  version "6.6.1"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.6.1.tgz#6360f5ed0d80f232cc2b294c362d5dc2e538dd61"
+  integrity sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==
   dependencies:
     fast-deep-equal "^2.0.1"
     fast-json-stable-stringify "^2.0.0"
@@ -638,6 +659,11 @@ arraybuffer.slice@0.0.6:
   resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca"
   integrity sha1-8zshWfBTKj8xB6JywMz70a0peco=
 
+arraybuffer.slice@~0.0.7:
+  version "0.0.7"
+  resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675"
+  integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==
+
 arrify@^1.0.0, arrify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -844,9 +870,9 @@ binary-search@^1.3.4:
   integrity sha512-dPxU/vZLnH0tEVjVPgi015oSwqu6oLfCeHywuFRhBE0yM0mYocvleTl8qsdM1YFhRzTRhM1+VzS8XLDVrHPopg==
 
 bindings@^1.3.0, bindings@~1.3.0:
-  version "1.3.0"
-  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
-  integrity sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5"
+  integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==
 
 bindings@~1.2.1:
   version "1.2.1"
@@ -963,6 +989,11 @@ blob@0.0.4:
   resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
   integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=
 
+blob@0.0.5:
+  version "0.0.5"
+  resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
+  integrity sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==
+
 block-stream2@^1.0.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/block-stream2/-/block-stream2-1.1.0.tgz#c738e3a91ba977ebb5e1fef431e13ca11d8639e2"
@@ -1143,9 +1174,9 @@ builtins@^1.0.3:
   integrity sha1-y5T662HIaWRR2zZTThQi+U8K7og=
 
 bull@^3.4.2:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/bull/-/bull-3.5.1.tgz#b936a1306cb7e9dc1ac9c23a0dcaf41a1370effc"
-  integrity sha512-stbptND5+uRmzd6gIUJlC93fikXKyrJl53HGxzyqD0ahCMeyFRlaD5kN1i+PqfZSkcHKx/kK3HOJ8knum/Yi7A==
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/bull/-/bull-3.5.2.tgz#9c85f205b17686efab2ee28aaa4388887360de32"
+  integrity sha512-tuL4Uj0kUeaQ7Cow3POkca20fk+VSsR8AiTFeNkyMmuicBnE1ZMwvF1NRDY7vIH43pD9PiMCSEP4Li/934Pw1w==
   dependencies:
     bluebird "^3.5.3"
     cron-parser "^2.5.0"
@@ -1283,6 +1314,11 @@ camelcase@^4.0.0, camelcase@^4.1.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
   integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
 
+camelcase@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
+  integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
+
 camelize@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
@@ -1447,14 +1483,14 @@ cli-columns@^3.1.2:
     string-width "^2.0.0"
     strip-ansi "^3.0.1"
 
-cli-cursor@^1.0.1, cli-cursor@^1.0.2:
+cli-cursor@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
   integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=
   dependencies:
     restore-cursor "^1.0.1"
 
-cli-cursor@^2.0.0:
+cli-cursor@^2.0.0, cli-cursor@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
   integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
@@ -1701,15 +1737,15 @@ concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.5.2:
     typedarray "^0.0.6"
 
 concurrently@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.0.1.tgz#f6310fbadf2f476dd95df952edb5c0ab789f672c"
-  integrity sha512-D8UI+mlI/bfvrA57SeKOht6sEpb01dKk+8Yee4fbnkk1Ue8r3S+JXoEdFZIpzQlXJGtnxo47Wvvg/kG4ba3U6Q==
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-4.1.0.tgz#17fdf067da71210685d9ea554423ef239da30d33"
+  integrity sha512-pwzXCE7qtOB346LyO9eFWpkFJVO3JQZ/qU/feGeaAHiX1M3Rw3zgXKc5cZ8vSH5DGygkjzLFDzA/pwoQDkRNGg==
   dependencies:
     chalk "^2.4.1"
     date-fns "^1.23.0"
     lodash "^4.17.10"
     read-pkg "^4.0.1"
-    rxjs "6.2.2"
+    rxjs "^6.3.3"
     spawn-command "^0.0.2-1"
     supports-color "^4.5.0"
     tree-kill "^1.1.0"
@@ -1723,10 +1759,10 @@ config-chain@~1.1.11:
     ini "^1.3.4"
     proto-list "~1.2.1"
 
-config@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/config/-/config-2.0.1.tgz#995ccc8175460578d646ac0a2e4018ffa44ca046"
-  integrity sha512-aTaviJnC8ZjQYx8kQf4u6tWqIxWolyQQ3LqXgnCLAsIb78JrUshHG0YuzIarzTaVVe1Pazms3TXImfYra8UsyQ==
+config@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/config/-/config-3.0.0.tgz#a71cdbb22d225df9eff20b95178d65a63c452367"
+  integrity sha512-QMr3BCOcHdgXx8t8cLfBhWtHcIAAMikaxUc2XASuH2A93g9kOIRch7sXFQdSvdMxhQobnctWm2y68YJYRttJlw==
   dependencies:
     json5 "^1.0.1"
 
@@ -1830,7 +1866,16 @@ cors@^2.8.1:
     object-assign "^4"
     vary "^1"
 
-cosmiconfig@^5.0.2, cosmiconfig@^5.0.6:
+cosmiconfig@5.0.6:
+  version "5.0.6"
+  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39"
+  integrity sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==
+  dependencies:
+    is-directory "^0.3.1"
+    js-yaml "^3.9.0"
+    parse-json "^4.0.0"
+
+cosmiconfig@^5.0.6:
   version "5.0.7"
   resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04"
   integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==
@@ -1867,9 +1912,9 @@ create-torrent@^3.24.5, create-torrent@^3.33.0:
     simple-sha1 "^2.0.0"
 
 cron-parser@^2.5.0:
-  version "2.7.1"
-  resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.1.tgz#d08c00b1e220db564fd1cecb5019c8dd450f84d1"
-  integrity sha512-gupE4KsGEVtp5X4YbUlQx6NiFt3e+VOhREPI4ZXS9FT5JcOjfw2ey1EUv3J6XWrxHR1aKYrk4uJDmdRjG39bgA==
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.7.3.tgz#12603f89f5375af353a9357be2543d3172eac651"
+  integrity sha512-t9Kc7HWBWPndBzvbdQ1YG9rpPRB37Tb/tTviziUOh1qs3TARGh3b1p+tnkOHNe1K5iI3oheBPgLqwotMM7+lpg==
   dependencies:
     is-nan "^1.2.1"
     moment-timezone "^0.5.23"
@@ -1967,7 +2012,7 @@ debug@2.6.9, debug@^2.1.1, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.
   dependencies:
     ms "2.0.0"
 
-debug@3.1.0:
+debug@3.1.0, debug@~3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
   integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
@@ -1988,23 +2033,23 @@ debug@^4.0.1:
   dependencies:
     ms "^2.1.1"
 
+debug@~4.1.0:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+  dependencies:
+    ms "^2.1.1"
+
 debuglog@^1.0.0, debuglog@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
   integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=
 
-decamelize@^1.1.1:
+decamelize@^1.1.1, decamelize@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
   integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
 
-decamelize@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-2.0.0.tgz#656d7bbc8094c4c788ea53c5840908c9c7d063c7"
-  integrity sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==
-  dependencies:
-    xregexp "4.0.0"
-
 decode-uri-component@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
@@ -2346,6 +2391,23 @@ engine.io-client@1.8.3:
     xmlhttprequest-ssl "1.5.3"
     yeast "0.1.2"
 
+engine.io-client@~3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.3.1.tgz#afedb4a07b2ea48b7190c3136bfea98fdd4f0f03"
+  integrity sha512-q66JBFuQcy7CSlfAz9L3jH+v7DTT3i6ZEadYcVj2pOs8/0uJHLxKX3WBkGTvULJMdz0tUCyJag0aKT/dpXL9BQ==
+  dependencies:
+    component-emitter "1.2.1"
+    component-inherit "0.0.3"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.1"
+    has-cors "1.1.0"
+    indexof "0.0.1"
+    parseqs "0.0.5"
+    parseuri "0.0.5"
+    ws "~6.1.0"
+    xmlhttprequest-ssl "~1.5.4"
+    yeast "0.1.2"
+
 engine.io-parser@1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a"
@@ -2358,6 +2420,17 @@ engine.io-parser@1.3.2:
     has-binary "0.1.7"
     wtf-8 "1.0.0"
 
+engine.io-parser@~2.1.0, engine.io-parser@~2.1.1:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.3.tgz#757ab970fbf2dfb32c7b74b033216d5739ef79a6"
+  integrity sha512-6HXPre2O4Houl7c4g7Ic/XzPnHBvaEmN90vtRO9uLmwtRqQmTOw0QMevL1TOfL2Cpu1VzsaTmMotQgMdkzGkVA==
+  dependencies:
+    after "0.8.2"
+    arraybuffer.slice "~0.0.7"
+    base64-arraybuffer "0.1.5"
+    blob "0.0.5"
+    has-binary2 "~1.0.2"
+
 engine.io@1.8.3:
   version "1.8.3"
   resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4"
@@ -2370,6 +2443,18 @@ engine.io@1.8.3:
     engine.io-parser "1.3.2"
     ws "1.1.2"
 
+engine.io@~3.3.1:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-3.3.2.tgz#18cbc8b6f36e9461c5c0f81df2b830de16058a59"
+  integrity sha512-AsaA9KG7cWPXWHp5FvHdDWY3AMWeZ8x+2pUVLcn71qE5AtAzgGbxuclOytygskw8XGmiQafTmnI9Bix3uihu2w==
+  dependencies:
+    accepts "~1.3.4"
+    base64id "1.0.0"
+    cookie "0.3.1"
+    debug "~3.1.0"
+    engine.io-parser "~2.1.0"
+    ws "~6.1.0"
+
 env-variable@0.0.x:
   version "0.0.5"
   resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88"
@@ -2644,10 +2729,10 @@ expand-brackets@^2.1.4:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
-expand-template@^1.0.2:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.1.tgz#981f188c0c3a87d2e28f559bc541426ff94f21dd"
-  integrity sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==
+expand-template@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
+  integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
 
 expect-ct@0.1.1:
   version "0.1.1"
@@ -2837,6 +2922,13 @@ figures@^1.3.5, figures@^1.7.0:
     escape-string-regexp "^1.0.5"
     object-assign "^4.1.0"
 
+figures@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
+  integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
+  dependencies:
+    escape-string-regexp "^1.0.5"
+
 file-entry-cache@^1.1.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8"
@@ -3361,6 +3453,13 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
+has-binary2@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d"
+  integrity sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==
+  dependencies:
+    isarray "2.0.1"
+
 has-binary@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c"
@@ -3420,9 +3519,9 @@ has-values@^1.0.0:
     kind-of "^4.0.0"
 
 hash.js@^1.0.0:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812"
-  integrity sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==
+  version "1.1.7"
+  resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
+  integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
   dependencies:
     inherits "^2.0.3"
     minimalistic-assert "^1.0.1"
@@ -3557,9 +3656,9 @@ humanize-ms@^1.2.1:
     ms "^2.0.0"
 
 husky@^1.0.0-rc.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/husky/-/husky-1.1.4.tgz#92f61383527d2571e9586234e5864356bfaceaa9"
-  integrity sha512-cZjGpS7qsaBSo3fOMUuR7erQloX3l5XzL1v/RkIqU6zrQImDdU70z5Re9fGDp7+kbYlM2EtS4aYMlahBeiCUGw==
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/husky/-/husky-1.2.0.tgz#d631dda1e4a9ee8ba69a10b0c51a0e2c66e711e5"
+  integrity sha512-/ib3+iycykXC0tYIxsyqierikVa9DA2DrT32UEirqNEFVqOj1bFMTgP3jAz8HM7FgC/C8pc/BTUa9MV2GEkZaA==
   dependencies:
     cosmiconfig "^5.0.6"
     execa "^1.0.0"
@@ -4103,6 +4202,11 @@ isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
   integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
 
+isarray@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+  integrity sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=
+
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -4483,13 +4587,14 @@ libxmljs@0.19.5:
     node-pre-gyp "~0.11.0"
 
 lint-staged@^8.0.4:
-  version "8.0.4"
-  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.0.4.tgz#d3c909fcf7897152cdce2d6e42500cd9b5b41a0d"
-  integrity sha512-Rs0VxXoyFqHMrPQgKAMy+O907+m5Po71UVPhBi7BUBwU7ZZ2aoc+mZmpOX3DVPCoTcy6+hqJa9yIZfacNpJHdg==
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-8.1.0.tgz#dbc3ae2565366d8f20efb9f9799d076da64863f2"
+  integrity sha512-yfSkyJy7EuVsaoxtUSEhrD81spdJOe/gMTGea3XaV7HyoRhTb9Gdlp6/JppRZERvKSEYXP9bjcmq6CA5oL2lYQ==
   dependencies:
+    "@iamstarkov/listr-update-renderer" "0.4.1"
     chalk "^2.3.1"
     commander "^2.14.1"
-    cosmiconfig "^5.0.2"
+    cosmiconfig "5.0.6"
     debug "^3.1.0"
     dedent "^0.7.0"
     del "^3.0.0"
@@ -4500,7 +4605,6 @@ lint-staged@^8.0.4:
     is-windows "^1.0.2"
     jest-validate "^23.5.0"
     listr "^0.14.2"
-    listr-update-renderer "https://github.com/okonet/listr-update-renderer/tarball/upgrade-log-update"
     lodash "^4.17.5"
     log-symbols "^2.2.0"
     micromatch "^3.1.8"
@@ -4518,9 +4622,10 @@ listr-silent-renderer@^1.1.1:
   resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e"
   integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=
 
-listr-update-renderer@^0.4.0, "listr-update-renderer@https://github.com/okonet/listr-update-renderer/tarball/upgrade-log-update":
-  version "0.4.0"
-  resolved "https://github.com/okonet/listr-update-renderer/tarball/upgrade-log-update#06073fa93166277607a7814f4e1f83960081414c"
+listr-update-renderer@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2"
+  integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==
   dependencies:
     chalk "^1.1.3"
     cli-truncate "^0.2.1"
@@ -4531,30 +4636,30 @@ listr-update-renderer@^0.4.0, "listr-update-renderer@https://github.com/okonet/l
     log-update "^2.3.0"
     strip-ansi "^3.0.1"
 
-listr-verbose-renderer@^0.4.0:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35"
-  integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=
+listr-verbose-renderer@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db"
+  integrity sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==
   dependencies:
-    chalk "^1.1.3"
-    cli-cursor "^1.0.2"
+    chalk "^2.4.1"
+    cli-cursor "^2.1.0"
     date-fns "^1.27.2"
-    figures "^1.7.0"
+    figures "^2.0.0"
 
 listr@^0.14.2:
-  version "0.14.2"
-  resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.2.tgz#cbe44b021100a15376addfc2d79349ee430bfe14"
-  integrity sha512-vmaNJ1KlGuGWShHI35X/F8r9xxS0VTHh9GejVXwSN20fG5xpq3Jh4bJbnumoT6q5EDM/8/YP1z3YMtQbFmhuXw==
+  version "0.14.3"
+  resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586"
+  integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==
   dependencies:
     "@samverschueren/stream-to-observable" "^0.3.0"
     is-observable "^1.1.0"
     is-promise "^2.1.0"
     is-stream "^1.1.0"
     listr-silent-renderer "^1.1.1"
-    listr-update-renderer "^0.4.0"
-    listr-verbose-renderer "^0.4.0"
-    p-map "^1.1.1"
-    rxjs "^6.1.0"
+    listr-update-renderer "^0.5.0"
+    listr-verbose-renderer "^0.5.0"
+    p-map "^2.0.0"
+    rxjs "^6.3.3"
 
 load-ip-set@^2.1.0:
   version "2.1.0"
@@ -4791,9 +4896,9 @@ lowercase-keys@^1.0.0:
   integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
 
 lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
-  integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+  integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
   dependencies:
     pseudomap "^1.0.2"
     yallist "^2.1.2"
@@ -5054,9 +5159,9 @@ mime@^1.3.4, mime@^1.4.1:
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
 mime@^2.2.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
-  integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.0.tgz#e051fd881358585f3279df333fe694da0bcffdd6"
+  integrity sha512-ikBcWwyqXQSHKtciCcctu9YfPbFYZ4+gbHEmE0Q8jzcTYQg5dHCr3g2wwAZjPoJfQVXZq6KXAjpXOTf5/cjT7w==
 
 mimelib@^0.3.0:
   version "0.3.1"
@@ -5386,9 +5491,9 @@ node-abi@^2.2.0:
     semver "^5.4.1"
 
 node-addon-api@^1.6.0:
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.1.tgz#a9881c8dbc6400bac6ddedcb96eccf8051678536"
-  integrity sha512-GcLOYrG5/enbqH4SMsqXt6GQUQGGnDnE3FLDZzXYkCgQHuZV5UDFR+EboeY8kpG0avroyOjpFQ2qLEBosFcRIA==
+  version "1.6.2"
+  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217"
+  integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA==
 
 node-fetch-npm@^2.0.2:
   version "2.0.2"
@@ -5484,20 +5589,20 @@ nodemailer-shared@^1.1.0:
     nodemailer-fetch "1.6.0"
 
 nodemailer@^4.4.2:
-  version "4.6.8"
-  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.8.tgz#f82fb407828bf2e76d92acc34b823d83e774f89c"
-  integrity sha512-A3s7EM/426OBIZbLHXq2KkgvmKbn2Xga4m4G+ZUA4IaZvG8PcZXrFh+2E4VaS2o+emhuUVRnzKN2YmpkXQ9qwA==
+  version "4.7.0"
+  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.7.0.tgz#4420e06abfffd77d0618f184ea49047db84f4ad8"
+  integrity sha512-IludxDypFpYw4xpzKdMAozBSkzKHmNBvGanUREjJItgJ2NYcK/s8+PggVhj7c2yGFQykKsnnmv1+Aqo0ZfjHmw==
 
 nodemon@^1.18.6:
-  version "1.18.6"
-  resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.6.tgz#89b1136634d6c0afc7de24cc932a760e999e2c76"
-  integrity sha512-4pHQNYEZun+IkIC2jCaXEhkZnfA7rQe73i8RkdRyDJls/K+WxR7IpI5uNUsAvQ0zWvYcCDNGD+XVtw2ZG86/uQ==
+  version "1.18.7"
+  resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.18.7.tgz#716b66bf3e89ac4fcfb38a9e61887a03fc82efbb"
+  integrity sha512-xuC1V0F5EcEyKQ1VhHYD13owznQbUw29JKvZ8bVH7TmuvVNHvvbp9pLgE4PjTMRJVe0pJ8fGRvwR2nMiosIsPQ==
   dependencies:
     chokidar "^2.0.4"
     debug "^3.1.0"
     ignore-by-default "^1.0.1"
     minimatch "^3.0.4"
-    pstree.remy "^1.1.0"
+    pstree.remy "^1.1.2"
     semver "^5.5.0"
     supports-color "^5.2.0"
     touch "^3.1.0"
@@ -6051,6 +6156,11 @@ p-map@^1.1.1:
   resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
   integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
 
+p-map@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.0.0.tgz#be18c5a5adeb8e156460651421aceca56c213a50"
+  integrity sha512-GO107XdrSUmtHxVoi60qc9tUl/KkNKm+X2CF4P9amalpGxv5YqVPJNfSb0wcA+syCopkZvYYIzW8OVTQW59x/w==
+
 p-try@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@@ -6263,14 +6373,7 @@ pg-connection-string@0.1.3:
   resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-0.1.3.tgz#da1847b20940e42ee1492beaf65d49d91b245df7"
   integrity sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=
 
-pg-hstore@^2.3.2:
-  version "2.3.2"
-  resolved "https://registry.yarnpkg.com/pg-hstore/-/pg-hstore-2.3.2.tgz#f7ef053e7b9b892ae986af2f7cbe86432dfcf24f"
-  integrity sha1-9+8FPnubiSrphq8vfL6GQy388k8=
-  dependencies:
-    underscore "^1.7.0"
-
-pg-pool@~2.0.3:
+pg-pool@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.4.tgz#05ad0f2d9437d89c94ccc4f4d0a44ac65ade865b"
   integrity sha512-Mi2AsmlFkVMpI28NreaDkz5DkfxLOG16C/HNwi091LDlOiDiQACtAroLxSd1vIS2imBqxdjjO9cQZg2CwsOPbw==
@@ -6286,14 +6389,14 @@ pg-types@~1.12.1:
     postgres-interval "^1.1.0"
 
 pg@^7.4.1:
-  version "7.6.1"
-  resolved "https://registry.yarnpkg.com/pg/-/pg-7.6.1.tgz#42c68aed37bf38b813616e3d21f4338f350c1b79"
-  integrity sha512-rAItIkYrRaNGinZN/Hs8F9R5mQjQSPlnzxPF+eCimSl92qnuNGR42gkpOQKP1bnvTwkSjRTBL+VNC5EcFhtCuQ==
+  version "7.7.1"
+  resolved "https://registry.yarnpkg.com/pg/-/pg-7.7.1.tgz#546b192ff484322b69689391f885de3ba91a30d4"
+  integrity sha512-p3I0mXOmUvCoVlCMFW6iYSrnguPol6q8He15NGgSIdM3sPGjFc+8JGCeKclw8ZR4ETd+Jxy2KNiaPUcocHZeMw==
   dependencies:
     buffer-writer "2.0.0"
     packet-reader "0.3.1"
     pg-connection-string "0.1.3"
-    pg-pool "~2.0.3"
+    pg-pool "^2.0.4"
     pg-types "~1.12.1"
     pgpass "1.x"
     semver "4.3.2"
@@ -6396,12 +6499,12 @@ postgres-interval@^1.1.0:
     xtend "^4.0.0"
 
 prebuild-install@^5.2.0:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.2.1.tgz#87ba8cf17c65360a75eefeb3519e87973bf9791d"
-  integrity sha512-9DAccsInWHB48TBQi2eJkLPE049JuAI6FjIH0oIrij4bpDVEbX6JvlWRAcAAlUqBHhjgq0jNqA3m3bBXWm9v6w==
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.2.2.tgz#237888f21bfda441d0ee5f5612484390bccd4046"
+  integrity sha512-4e8VJnP3zJdZv/uP0eNWmr2r9urp4NECw7Mt1OSAi3rcLrbBRxGiAkfUFtre2MhQ5wfREAjRV+K1gubvs/GPsA==
   dependencies:
     detect-libc "^1.0.3"
-    expand-template "^1.0.2"
+    expand-template "^2.0.3"
     github-from-package "0.0.0"
     minimist "^1.2.0"
     mkdirp "^0.5.1"
@@ -6529,7 +6632,7 @@ psl@^1.1.24:
   resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67"
   integrity sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==
 
-pstree.remy@^1.1.0:
+pstree.remy@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.2.tgz#4448bbeb4b2af1fed242afc8dc7416a6f504951a"
   integrity sha512-vL6NLxNHzkNTjGJUpMm5PLC+94/0tTlC1vkP9bdU0pOHih+EujMjgMTwfZopZvHWRFbqJ5Y73OMoau50PewDDA==
@@ -6587,11 +6690,16 @@ qs@4.0.0:
   resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607"
   integrity sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc=
 
-qs@6.5.2, qs@^6.5.1, qs@~6.5.2:
+qs@6.5.2, qs@~6.5.2:
   version "6.5.2"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
   integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
 
+qs@^6.5.1:
+  version "6.6.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2"
+  integrity sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA==
+
 query-string@^6.1.0:
   version "6.2.0"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.2.0.tgz#468edeb542b7e0538f9f9b1aeb26f034f19c86e1"
@@ -6845,7 +6953,7 @@ referrer-policy@1.1.0:
   resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.1.0.tgz#35774eb735bf50fb6c078e83334b472350207d79"
   integrity sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk=
 
-reflect-metadata@^0.1.10:
+reflect-metadata@^0.1.12:
   version "0.1.12"
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2"
   integrity sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==
@@ -7077,14 +7185,7 @@ rx-lite@^3.1.2:
   resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
   integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=
 
-rxjs@6.2.2:
-  version "6.2.2"
-  resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.2.2.tgz#eb75fa3c186ff5289907d06483a77884586e1cf9"
-  integrity sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==
-  dependencies:
-    tslib "^1.9.0"
-
-rxjs@^6.1.0:
+rxjs@^6.3.3:
   version "6.3.3"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.3.3.tgz#3c6a7fa420e844a81390fb1158a9ec614f4bad55"
   integrity sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==
@@ -7425,6 +7526,15 @@ simple-websocket@^7.0.1:
     readable-stream "^2.0.5"
     ws "^6.0.0"
 
+sitemap@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/sitemap/-/sitemap-2.1.0.tgz#1633cb88c196d755ad94becfb1c1bcacc6d3425a"
+  integrity sha512-AkfA7RDVCITQo+j5CpXsMJlZ/8ENO2NtgMHYIh+YMvex2Hao/oe3MQgNa03p0aWY6srCfUA1Q02OgiWCAiuccA==
+  dependencies:
+    lodash "^4.17.10"
+    url-join "^4.0.0"
+    xmlbuilder "^10.0.0"
+
 slash@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
@@ -7508,6 +7618,11 @@ socket.io-adapter@0.5.0:
     debug "2.3.3"
     socket.io-parser "2.3.1"
 
+socket.io-adapter@~1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz#2a805e8a14d6372124dd9159ad4502f8cb07f06b"
+  integrity sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=
+
 socket.io-client@1.7.3:
   version "1.7.3"
   resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377"
@@ -7525,6 +7640,26 @@ socket.io-client@1.7.3:
     socket.io-parser "2.3.1"
     to-array "0.1.4"
 
+socket.io-client@2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.2.0.tgz#84e73ee3c43d5020ccc1a258faeeb9aec2723af7"
+  integrity sha512-56ZrkTDbdTLmBIyfFYesgOxsjcLnwAKoN4CiPyTVkMQj3zTUh0QAx3GbvIvLpFEOvQWu92yyWICxB0u7wkVbYA==
+  dependencies:
+    backo2 "1.0.2"
+    base64-arraybuffer "0.1.5"
+    component-bind "1.0.0"
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    engine.io-client "~3.3.1"
+    has-binary2 "~1.0.2"
+    has-cors "1.1.0"
+    indexof "0.0.1"
+    object-component "0.0.3"
+    parseqs "0.0.5"
+    parseuri "0.0.5"
+    socket.io-parser "~3.3.0"
+    to-array "0.1.4"
+
 socket.io-parser@2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0"
@@ -7535,6 +7670,15 @@ socket.io-parser@2.3.1:
     isarray "0.0.1"
     json3 "3.3.2"
 
+socket.io-parser@~3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.3.0.tgz#2b52a96a509fdf31440ba40fed6094c7d4f1262f"
+  integrity sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==
+  dependencies:
+    component-emitter "1.2.1"
+    debug "~3.1.0"
+    isarray "2.0.1"
+
 socket.io@1.7.3:
   version "1.7.3"
   resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b"
@@ -7548,6 +7692,18 @@ socket.io@1.7.3:
     socket.io-client "1.7.3"
     socket.io-parser "2.3.1"
 
+socket.io@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-2.2.0.tgz#f0f633161ef6712c972b307598ecd08c9b1b4d5b"
+  integrity sha512-wxXrIuZ8AILcn+f1B4ez4hJTPG24iNgxBBDaJfT6MsyOhVYiTXWexGoPkd87ktJG8kQEcL/NBvRi64+9k4Kc0w==
+  dependencies:
+    debug "~4.1.0"
+    engine.io "~3.3.1"
+    has-binary2 "~1.0.2"
+    socket.io-adapter "~1.1.0"
+    socket.io-client "2.2.0"
+    socket.io-parser "~3.3.0"
+
 socks-proxy-agent@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz#2eae7cf8e2a82d34565761539a7f9718c5617659"
@@ -7869,10 +8025,10 @@ string2compact@^1.1.1, string2compact@^1.2.5:
     addr-to-ip-port "^1.0.1"
     ipaddr.js "^1.0.1"
 
-string_decoder@^1.1.1, string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+string_decoder@^1.1.1:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
+  integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
   dependencies:
     safe-buffer "~5.1.0"
 
@@ -7881,6 +8037,13 @@ string_decoder@~0.10.x:
   resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
   integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
 
+string_decoder@~1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+  dependencies:
+    safe-buffer "~5.1.0"
+
 stringify-object@^3.2.2:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629"
@@ -8342,9 +8505,9 @@ tsutils@^2.27.2:
     tslib "^1.8.1"
 
 tsutils@^3.0.0:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.5.0.tgz#42602f7df241e753a2105cc3627a664abf11f745"
-  integrity sha512-/FZ+pEJQixWruFejFxNPRSwg+iF6aw7PYZVRqUscJ7EnzV3zieI8byfZziUR7QjCuJFulq8SEe9JcGflO4ze4Q==
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.5.2.tgz#6fd3c2d5a731e83bb21b070a173ec0faf3a8f6d3"
+  integrity sha512-qIlklNuI/1Dzfm+G+kJV5gg3gimZIX5haYtIVQe7qGyKd7eu8T1t1DY6pz4Sc2CGXAj9s1izycctm9Zfl9sRuQ==
   dependencies:
     tslib "^1.8.1"
 
@@ -8411,9 +8574,9 @@ typedarray@^0.0.6:
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
 typescript@^3.1.6:
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68"
-  integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.1.tgz#0b7a04b8cf3868188de914d9568bd030f0c56192"
+  integrity sha512-jw7P2z/h6aPT4AENXDGjcfHTu5CSqzsbZc6YlUIebTyBAq8XaKp78x7VcSh30xwSCcsu5irZkYZUSFP1MrAMbg==
 
 uid-number@0.0.6:
   version "0.0.6"
@@ -8451,7 +8614,7 @@ underscore-keypath@~0.0.22:
   dependencies:
     underscore "*"
 
-underscore@*, underscore@^1.7.0:
+underscore@*:
   version "1.9.1"
   resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
   integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
@@ -8553,6 +8716,11 @@ urix@^0.1.0:
   resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
   integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
 
+url-join@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
+  integrity sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=
+
 url-parse-lax@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
@@ -8720,9 +8888,9 @@ wcwidth@^1.0.0:
     defaults "^1.0.3"
 
 webfinger.js@^2.6.6:
-  version "2.6.6"
-  resolved "https://registry.yarnpkg.com/webfinger.js/-/webfinger.js-2.6.6.tgz#52ebdc85da8c8fb6beb690e8e32594c99d2ff4ae"
-  integrity sha512-dQpuL01XtluQ9Ndgu62o3pEmIe/ssDoIE0CQsOyavGl04xyHal+Ge4gFerw5V0BFoLTQpD8ZZqaDzb43hG9atw==
+  version "2.7.0"
+  resolved "https://registry.yarnpkg.com/webfinger.js/-/webfinger.js-2.7.0.tgz#403354a14a65aeeba64c1408c18a387487cea106"
+  integrity sha512-l+UtsuV4zrBKyVAj9VCtwWgscTgadCsdGgL1OvbV102cvydWwJCGXlFIXauzWLzfheIDHfPNRWfgMuwyC6ZfIA==
   dependencies:
     xhr2 "^0.1.4"
 
@@ -8908,10 +9076,10 @@ ws@1.1.2:
     options ">=0.0.5"
     ultron "1.0.x"
 
-ws@^6.0.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.0.tgz#119a9dbf92c54e190ec18d10e871d55c95cf9373"
-  integrity sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==
+ws@^6.0.0, ws@~6.1.0:
+  version "6.1.2"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.2.tgz#3cc7462e98792f0ac679424148903ded3b9c3ad8"
+  integrity sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==
   dependencies:
     async-limiter "~1.0.0"
 
@@ -8936,16 +9104,16 @@ xhr2@^0.1.4:
   integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8=
 
 xliff@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/xliff/-/xliff-4.1.0.tgz#32ea268a6442c122e132e6abf874539b1fc9c6b3"
-  integrity sha512-BlqCVTd16GLNx4TAll1Ebs1Gswh6g/Mx/9z6cXmbNTVqy7iqXAAwZjmhE2G1fX+++xoXy0IufPp+DOv8tJC/pA==
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/xliff/-/xliff-4.1.2.tgz#eb6fae21346d82653febd44d478f5748ad79fbd2"
+  integrity sha512-ru+ya+rz2cb+D3Or9sf5xrj0MCL+q+vZmWOJlqZehIWlG3hqeIXhbfLMDAW9A5BsnRfL+BdMBHaogaTUGHyMyA==
   dependencies:
-    xml-js "1.6.7"
+    xml-js "1.6.8"
 
-xml-js@1.6.7:
-  version "1.6.7"
-  resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.7.tgz#a99b40c18a16d3e06537b3ae026a27bd60ffe8ab"
-  integrity sha512-1hn0xwwfMcWywnJxqiOXiv+pZaOJyf/YWcUeqJICF0BFb+IOkRFSkKyeA0V62WqTHXNdBxNuCFHhS/w2DtYpoA==
+xml-js@1.6.8:
+  version "1.6.8"
+  resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.8.tgz#e06419c54235f18f4c2cdda824cbd65a782330de"
+  integrity sha512-kUv/geyN80d+s1T68uBfjoz+PjNUjwwf5AWWRwKRqqQaGozpMVsFsKYnenPsxlbN/VL7f0ia8NfLLPCDwX+95Q==
   dependencies:
     sax "^1.2.4"
 
@@ -8962,6 +9130,11 @@ xml@^1.0.1:
   resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
   integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=
 
+xmlbuilder@^10.0.0:
+  version "10.1.1"
+  resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0"
+  integrity sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==
+
 xmlbuilder@~9.0.1:
   version "9.0.7"
   resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
@@ -8977,10 +9150,10 @@ xmlhttprequest-ssl@1.5.3:
   resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d"
   integrity sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=
 
-xregexp@4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"
-  integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==
+xmlhttprequest-ssl@~1.5.4:
+  version "1.5.5"
+  resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"
+  integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=
 
 "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
   version "4.0.1"
@@ -9003,16 +9176,17 @@ yallist@^2.1.2:
   integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
 
 yallist@^3.0.0, yallist@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9"
-  integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9"
+  integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==
 
-yargs-parser@^10.1.0:
-  version "10.1.0"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-10.1.0.tgz#7202265b89f7e9e9f2e5765e0fe735a905edbaa8"
-  integrity sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==
+yargs-parser@^11.1.1:
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4"
+  integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==
   dependencies:
-    camelcase "^4.1.0"
+    camelcase "^5.0.0"
+    decamelize "^1.2.0"
 
 yargs-parser@^8.0.0:
   version "8.1.0"
@@ -9047,12 +9221,12 @@ yargs@^11.0.0:
     yargs-parser "^9.0.2"
 
 yargs@^12.0.1, yargs@^12.0.2:
-  version "12.0.2"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
-  integrity sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==
+  version "12.0.5"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
+  integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==
   dependencies:
     cliui "^4.0.0"
-    decamelize "^2.0.0"
+    decamelize "^1.2.0"
     find-up "^3.0.0"
     get-caller-file "^1.0.1"
     os-locale "^3.0.0"
@@ -9062,7 +9236,7 @@ yargs@^12.0.1, yargs@^12.0.2:
     string-width "^2.0.0"
     which-module "^2.0.0"
     y18n "^3.2.1 || ^4.0.0"
-    yargs-parser "^10.1.0"
+    yargs-parser "^11.1.1"
 
 yeast@0.1.2:
   version "0.1.2"
@@ -9085,9 +9259,9 @@ youtube-dl@^1.12.2:
     streamify "^0.2.9"
 
 z-schema@^3.24.1:
-  version "3.24.1"
-  resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.24.1.tgz#07a3643c8e061ec1af32e823c9f9e5e5e56e3c8d"
-  integrity sha512-2eR8eq/v1coNqyBc5HzswEcoLbw+S33RMnR326uiuOIr97ve5vwPNMDrKS1IRCB12bZ3a8BrfGxrRwuSXUyPvw==
+  version "3.24.2"
+  resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-3.24.2.tgz#193560e718812d98fdc190c38871b634b92f2386"
+  integrity sha512-Zb2YLJ9g72MexBXKPRzoypd4OZfVkFghdy10eVbcMNLl9YQsPXtyMpiK7a3sG7IIERg1lEDjEMrG9Km9DPbWLw==
   dependencies:
     core-js "^2.5.7"
     lodash.get "^4.0.0"