From 59797ee233db88db047773294225eb8c8701adb7 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Wed, 17 Jan 2018 23:00:23 +0100 Subject: [PATCH 1/5] Weblate translations (#6284) * Translated using Weblate (French) Currently translated at 100.0% (56 of 56 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fr/ * Translated using Weblate (Catalan) Currently translated at 100.0% (56 of 56 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ca/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (260 of 260 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (45 of 45 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pt_BR/ * Translated using Weblate (Dutch) Currently translated at 100.0% (529 of 529 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Translated using Weblate (Dutch) Currently translated at 100.0% (45 of 45 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/nl/ * Translated using Weblate (Galician) Currently translated at 100.0% (529 of 529 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/ * Translated using Weblate (Galician) Currently translated at 100.0% (45 of 45 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/gl/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.6% (527 of 529 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (528 of 529 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (45 of 45 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pt_BR/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (260 of 260 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/ * Translated using Weblate (Slovak) Currently translated at 100.0% (56 of 56 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/ * Translated using Weblate (Slovak) Currently translated at 37.2% (197 of 529 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Slovak) Currently translated at 100.0% (260 of 260 strings) Translation: Mastodon/React Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/ * Translated using Weblate (Russian) Currently translated at 99.0% (526 of 531 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ru/ * Translated using Weblate (Catalan) Currently translated at 100.0% (45 of 45 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ca/ * Translated using Weblate (Catalan) Currently translated at 99.8% (530 of 531 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/ * Translated using Weblate (Japanese) Currently translated at 92.8% (52 of 56 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ja/ * Translated using Weblate (Japanese) Currently translated at 75.8% (47 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ja/ * Translated using Weblate (Polish) Currently translated at 77.4% (48 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pl/ * Translated using Weblate (Slovak) Currently translated at 38.3% (204 of 532 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/ * Translated using Weblate (Japanese) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ja/ * Translated using Weblate (Catalan) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ca/ * Translated using Weblate (Polish) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pl/ * Translated using Weblate (Polish) Currently translated at 100.0% (75 of 75 strings) Translation: Mastodon/Doorkeeper Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/pl/ * Translated using Weblate (Polish) Currently translated at 100.0% (62 of 62 strings) Translation: Mastodon/Devise Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pl/ * Translated using Weblate (Polish) Currently translated at 100.0% (56 of 56 strings) Translation: Mastodon/Preferences Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/pl/ * Translated using Weblate (Russian) Currently translated at 96.8% (525 of 542 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ru/ * Translated using Weblate (Japanese) Currently translated at 99.0% (537 of 542 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Polish) Currently translated at 99.8% (541 of 542 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/ * Translated using Weblate (Japanese) Currently translated at 99.0% (538 of 543 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/ * Translated using Weblate (Dutch) Currently translated at 97.4% (529 of 543 strings) Translation: Mastodon/Backend Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/ * Normalize translations Ran i18n-tasks normalize && yarn manage:translations --- app/javascript/mastodon/locales/pt-BR.json | 4 +- app/javascript/mastodon/locales/sk.json | 4 +- config/locales/ca.yml | 12 ++- config/locales/devise.ca.yml | 21 +++++ config/locales/devise.gl.yml | 4 + config/locales/devise.ja.yml | 17 ++++ config/locales/devise.nl.yml | 4 + config/locales/devise.pl.yml | 19 ++++- config/locales/devise.pt-BR.yml | 4 + config/locales/doorkeeper.pl.yml | 2 +- config/locales/gl.yml | 7 ++ config/locales/ja.yml | 16 +++- config/locales/nl.yml | 12 ++- config/locales/pl.yml | 27 +++++-- config/locales/pt-BR.yml | 12 ++- config/locales/ru.yml | 8 +- config/locales/simple_form.ca.yml | 2 +- config/locales/simple_form.fr.yml | 2 +- config/locales/simple_form.ja.yml | 2 +- config/locales/simple_form.pl.yml | 10 +-- config/locales/simple_form.sk.yml | 4 + config/locales/sk.yml | 94 +++++++++++++++++++++- 22 files changed, 254 insertions(+), 33 deletions(-) diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 947c6fb2b91..381b80639b5 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -50,7 +50,7 @@ "column_header.unpin": "Desafixar", "column_subheading.navigation": "Navegação", "column_subheading.settings": "Configurações", - "compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.", + "compose_form.hashtag_warning": "Esse toot não será listado em nenhuma hashtag por ser não listado. Somente toots públicos podem ser pesquisados por hashtag.", "compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.", "compose_form.lock_disclaimer.lock": "trancada", "compose_form.placeholder": "No que você está pensando?", @@ -223,7 +223,7 @@ "status.media_hidden": "Mídia escondida", "status.mention": "Mencionar @{name}", "status.more": "Mais", - "status.mute": "Mute @{name}", + "status.mute": "Silenciar @{name}", "status.mute_conversation": "Silenciar conversa", "status.open": "Expandir", "status.pin": "Fixar no perfil", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 41ebfbd0e04..c38bfd3c6fc 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -195,8 +195,8 @@ "privacy.private.short": "Iba sledujúci", "privacy.public.long": "Pošli všetkým", "privacy.public.short": "Verejne", - "privacy.unlisted.long": "Neposielať verejne", - "privacy.unlisted.short": "Nie je v zozname", + "privacy.unlisted.long": "Neposielať do verejných časových osí", + "privacy.unlisted.short": "Verejne mimo osí", "relative_time.days": "{number}d", "relative_time.hours": "{number}h", "relative_time.just_now": "now", diff --git a/config/locales/ca.yml b/config/locales/ca.yml index ac79fcee2f1..57e07cbd8a6 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -265,12 +265,18 @@ ca: unresolved: No resolt view: Visualització settings: + activity_api_enabled: + desc_html: Compte d'estatus publicats localment, usuaris actius i registres nous en cubs setmanals + title: Publica estadístiques agregades sobre l'activitat de l'usuari bootstrap_timeline_accounts: desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desbloquejats. El valor predeterminat quan està buit és tots els administradors locals.. title: El seguiment per defecte per als nous usuaris contact_information: email: Introdueix una adreça de correu electrònic pùblica username: Introdueix un nom d'usuari + peers_api_enabled: + desc_html: Els noms de domini que ha trobat aquesta instància al fediverse + title: Publica la llista d'instàncies descobertes registrations: closed_message: desc_html: Apareix en la primera pàgina quan es tanquen els registres
Pots utilitzar etiquetes HTML @@ -345,7 +351,7 @@ ca: warning: Aneu amb compte amb aquestes dades. No ho compartiu mai amb ningú! your_token: El token d'accés auth: - agreement_html: En inscriure't, acceptes els nostres termes del servei i la nostra política de privadesa. + agreement_html: En inscriure't, acceptes seguir els nostres termes del servei i la nostra política de privadesa. change_password: Canvia la contrasenya delete_account: Esborra el compte delete_account_html: Si vols esborrar el teu compte pots fer-ho aquí. Se't demanarà confirmació. @@ -546,12 +552,14 @@ ca: blackberry: Blackberry chrome: Chrome edge: Microsoft Edge + electron: Electron firefox: Firefox generic: Navegador desconegut ie: Internet Explorer micro_messenger: MicroMessenger nokia: Nokia S40 Ovi Browser opera: Opera + otter: Altre phantom_js: PhantomJS qq: QQ Browser safari: Safari @@ -596,7 +604,7 @@ ca: open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat pin_errors: - limit: S'han fixat massa toots + limit: Ja has fixat el màxim nombre de toots ownership: El toot d'algú altre no es pot fixar private: No es pot fixar el toot no públic reblog: No es pot fixar un impuls diff --git a/config/locales/devise.ca.yml b/config/locales/devise.ca.yml index fe95c402d61..d88db17cac1 100644 --- a/config/locales/devise.ca.yml +++ b/config/locales/devise.ca.yml @@ -17,11 +17,32 @@ ca: unconfirmed: Has de confirmar l'adreça de correu electrònic abans de continuar. mailer: confirmation_instructions: + action: Verificar l'adreça de correu + explanation: Has creat un compte a %{host} amb aquesta adreça de correu electrònic. Estàs a un sol clic de l'activació. Si no fos així, ignora aquest correu electrònic. + extra_html: Si us plau consulta també les regles de la instància i les nostres condicions de servei. subject: 'Mastodon: Instruccions de confirmació' + title: Verifica l'adreça de correu + email_changed: + explanation: 'L''adreça de correu del teu compte s''està canviant a:' + extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de l'instància si no pots accedir al teu compte. + subject: 'Mastodon: s''ha canviat l''adreça electrònica' + title: Nova adreça de correu electrònic password_change: + explanation: S'ha canviat la contrasenya del teu compte. + extra: Si no has canviat el teu correu electrònic, és probable que algú hagi accedit al teu compte. Si us plau, canvia la contrasenya immediatament o posa't en contacte amb l'administrador de l'instància si no pots accedir al teu compte. subject: 'Mastodon: Contrasenya canviada' + title: Contrasenya canviada + reconfirmation_instructions: + explanation: Confirma la nova adreça per canviar el teu correu electrònic. + extra: Si no has iniciat aquest canvi, ignora aquest correu electrònic. L'adreça electrònica del compte de Mastodon no canviarà fins que accedeixis a l'enllaç de dalt. + subject: 'Mastodon: Confirma el correu electrònic per a %{instance}' + title: Verifica l'adreça de correu electrònic reset_password_instructions: + action: Canviar contrasenya + explanation: Has sol·licitat una contrasenya nova per al teu compte. + extra: Si no ho has sol·licitat, ignora aquest correu electrònic. La teva contrasenya no canviarà fins que accedeixis a l'enllaç de dalt i creis un de nou. subject: 'Mastodon: Instruccions per a reiniciar contrassenya' + title: Contrasenya restablerta unlock_instructions: subject: 'Mastodon: Instruccions per a desblocar' omniauth_callbacks: diff --git a/config/locales/devise.gl.yml b/config/locales/devise.gl.yml index 8a2b5d5638c..c0fc53834a7 100644 --- a/config/locales/devise.gl.yml +++ b/config/locales/devise.gl.yml @@ -18,8 +18,12 @@ gl: mailer: confirmation_instructions: subject: 'Mastodon: Instruccións de confirmación para %{instance}' + email_changed: + subject: 'Mastodon: email cambiado' password_change: subject: 'Mastodon: contrasinal cambiado' + reconfirmation_instructions: + subject: 'Mastodon: Confirme email para %{instance}' reset_password_instructions: subject: 'Mastodon: Instruccións para restablecer o contrasinal' unlock_instructions: diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml index 118186877be..1f6395479ac 100644 --- a/config/locales/devise.ja.yml +++ b/config/locales/devise.ja.yml @@ -17,15 +17,32 @@ ja: unconfirmed: 続行するにはメールアドレスを確認する必要があります。 mailer: confirmation_instructions: + action: メールアドレスの確認 + explanation: このメールアドレスで%{host}にアカウントを作成しました。有効にするまであと一歩です。もし心当たりがない場合、申し訳ありませんがこのメールを無視してください。 + extra_html: また インスタンスのルール利用規約 もお読みください。 subject: 'Mastodon: メールアドレスの確認' + title: メールアドレスの確認 email_changed: + explanation: 'アカウントのメールアドレスは以下のように変更されます:' + extra: メールアドレスの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はインスタンス管理者に連絡してください。 subject: 'Mastodon: メールアドレスの変更' + title: 新しいメールアドレス password_change: + explanation: パスワードが変更されました。 + extra: パスワードの変更を行っていない場合、他の誰かがあなたのアカウントにアクセスした可能性があります。すぐにパスワードを変更するか、アカウントがロックされている場合はインスタンス管理者に連絡してください。 subject: 'Mastodon: パスワードが変更されました' + title: パスワードの変更 reconfirmation_instructions: + explanation: メールアドレスを変更するため新しいアドレスを確認してください。 + extra: この変更に心当たりがない場合、このメールを無視してください。上記リンク先にアクセスするまでアカウントのメールアドレスは変更されません。 subject: 'Mastodon: %{instance}のメールを確認する' + title: メールアドレスの確認 reset_password_instructions: + action: パスワードの変更 + explanation: あなたのアカウントに対しパスワードの再発行が要求されました。 + extra: この要求に心当たりがない場合、このメールを無視してください。上記リンク先にアクセスし新しいものを作成するまでパスワードは変更されません。 subject: 'Mastodon: パスワード再発行' + title: パスワード再発行 unlock_instructions: subject: 'Mastodon: アカウントのロックの解除' omniauth_callbacks: diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml index 8c2c7b6a110..57e85a7b5d5 100644 --- a/config/locales/devise.nl.yml +++ b/config/locales/devise.nl.yml @@ -19,8 +19,12 @@ nl: mailer: confirmation_instructions: subject: 'Mastodon: E-mail bevestigen voor %{instance}' + email_changed: + subject: 'Mastodon: E-mailadres is veranderd' password_change: subject: 'Mastodon: Wachtwoord veranderd' + reconfirmation_instructions: + subject: 'Mastodon: Bevestig het e-mailadres voor %{instance}' reset_password_instructions: subject: 'Mastodon: Wachtwoord opnieuw instellen' unlock_instructions: diff --git a/config/locales/devise.pl.yml b/config/locales/devise.pl.yml index 6a296046304..53a4f4552ed 100644 --- a/config/locales/devise.pl.yml +++ b/config/locales/devise.pl.yml @@ -17,15 +17,32 @@ pl: unconfirmed: Zweryfikuj adres e-mail, aby kontynuować. mailer: confirmation_instructions: + action: Zweryfikuj adres e-mail + explanation: Utworzyłeś konto na %{host} podając ten adres e-mail. Jedno kliknięcie dzieli Cię od aktywacji tego konta. Jeżeli to nie Ty, zignoruj ten e-mail. + extra_html: Przeczytaj też regulamin instancji i nasze zasady użytkowania. subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail' + title: Zweryfikuj adres e-mail email_changed: + explanation: 'Adres e-mail dla Twojego konta zostanie zmieniony na:' + extra: Jeżeli nie próbowałeś zmienić adresu e-mail, prawdopodobnie ktoś uzyskał dostęp do Twojego konta. Zmień natychmiastowo hasło lub skontaktuj się z administratorem isntancji, jeżeli nie masz dostępu do konta. subject: 'Mastodon: Zmieniono adres e-mail' + title: Nowy adres e-mail password_change: + explanation: Hasło do Twojego konta zostało zmienione. + extra: Jeżeli nie zmieniałeś hasła, prawdopodobnie ktoś uzyskał dostęp do Twojego konta. Zmień hasło natychmiastowo lub skontaktuj się z administratorem instancji, jeżeli nie masz dostępu do konta. subject: 'Mastodon: Zmieniono hasło' + title: Zmieniono hasło reconfirmation_instructions: + explanation: Potwierdź nowy adres aby zmienić e-mail. + extra: Jeżeli nie próbowałeś zmienić e-maila, zignoruj tą wiadomość. Adres e-mail przypisany do konta Mastodona nie ulegnie zmianie, jeżeli nie użyjesz powyższego odnośniku. subject: 'Mastodon: Potwierdź adres e-mail na &{instance}' + title: Zweryfikuj adres e-mail reset_password_instructions: + action: Zmień hasło + explanation: Próbowałeś uzyskać nowe hasło do swojego konta. + extra: Jeżeli to nie Ty, zignoruj tą wiadomość. Twoje hasło nie ulegnie zmianie, jeżeli nie wykorzystasz powyższego odnośnika i nie utworzysz nowego hasła. subject: 'Mastodon: Instrukcje ustawienia nowego hasła' + title: Przywracanie hasła unlock_instructions: subject: 'Mastodon: Instrukcje odblokowania konta' omniauth_callbacks: @@ -38,7 +55,7 @@ pl: updated: Twoje hasło zostało zmienione. Jesteś zalogowany/a. updated_not_active: Twoje hasło zostało zmienione. registrations: - destroyed: Twoje konto zostało anulowane. Mamy jednak nadzieję, że do nas wrócisz. Do zobaczenia! + destroyed: Twoje konto zostało zawieszone. Mamy jednak nadzieję, że do nas wrócisz. Do zobaczenia! signed_up: Twoje konto zostało utworzone. Witamy! signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane. signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane. diff --git a/config/locales/devise.pt-BR.yml b/config/locales/devise.pt-BR.yml index 13736b3a350..eab86c80290 100644 --- a/config/locales/devise.pt-BR.yml +++ b/config/locales/devise.pt-BR.yml @@ -18,8 +18,12 @@ pt-BR: mailer: confirmation_instructions: subject: 'Mastodon: Instruções de confirmação' + email_changed: + subject: 'Mastodon: Email alterado' password_change: subject: 'Mastodon: Senha modificada' + reconfirmation_instructions: + subject: 'Mastodon: Confirmar emai para %{instance}' reset_password_instructions: subject: 'Mastodon: Instruções para mudança de senha' unlock_instructions: diff --git a/config/locales/doorkeeper.pl.yml b/config/locales/doorkeeper.pl.yml index 33f133c06fd..6c127b73be6 100644 --- a/config/locales/doorkeeper.pl.yml +++ b/config/locales/doorkeeper.pl.yml @@ -83,7 +83,7 @@ pl: invalid_grant: Grant uwierzytelnienia jest niepoprawny, przeterminowany, unieważniony, nie pasuje do URI przekierowwania użytego w żądaniu uwierzytelnienia, lub został wystawiony przez innego klienta. invalid_redirect_uri: URI przekierowania jest nieprawidłowy. invalid_request: 'Żądanie jest nieprawidłowe: brakujący parametr, niewspierana wartość parametru, lub inny błąd.' - invalid_resource_owner: Dostarczone dane uwierzytelniające właściciela zasobu są niepoprawne, lub właściciel zasobu nie może zostać znaleziony. + invalid_resource_owner: Dostarczone dane uwierzytelniające właściciela zasobu są niepoprawne, lub właściciel zasobu nie może zostać znaleziony invalid_scope: Zakres żądania jest niepoprawny, nieznany, lub błędnie zbudowany. invalid_token: expired: Token dostępowy wygasł diff --git a/config/locales/gl.yml b/config/locales/gl.yml index ca72bf18346..55f71724966 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -265,12 +265,18 @@ gl: unresolved: Non resolto view: Vista settings: + activity_api_enabled: + desc_html: Conta de estados publicados localmente, usuarias activas, e novos rexistros por semana + title: Publicar estatísticas agregadas sobre a actividade da usuaria bootstrap_timeline_accounts: desc_html: Separar múltiples nomes de usuaria con vírgulas. Só funcionarán as contas locais non bloqueadas. Si baldeiro, por omisión son todos os local admin. title: Seguimentos por omisión para novas usuarias contact_information: email: e-mail de traballo username: Nome de usuaria de contacto + peers_api_enabled: + desc_html: Nome de dominio que esta instancia atopou no fediverso + title: Publicar lista de instancias descubertas registrations: closed_message: desc_html: Mostrado na páxina de portada cando o rexistro está pechado. Pode utilizar etiquetas HTML @@ -509,6 +515,7 @@ gl: quadrillion: Q thousand: K trillion: T + unit: " " pagination: next: Seguinte prev: Previo diff --git a/config/locales/ja.yml b/config/locales/ja.yml index c5b8ff4e031..75c13329299 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -338,10 +338,13 @@ ja: body: "%{reporter} が %{target} を通報しました" subject: "%{instance} の新しい通報 (#%{id})" application_mailer: + notification_preferences: メール設定の変更 salutation: "%{name} さん" settings: 'メール設定の変更: %{link}' signature: Mastodon %{instance} インスタンスからの通知 view: 'リンク:' + view_profile: プロフィールを表示 + view_status: トゥートを表示 applications: created: アプリが作成されました destroyed: アプリが削除されました @@ -482,29 +485,38 @@ ja: title: モデレーション notification_mailer: digest: + action: 全ての通知を表示 body: "%{instance} での最後のログインからの出来事:" mention: "%{name} さんがあなたに返信しました:" new_followers_summary: - one: 新たなフォロワーを獲得しました! - other: "%{count} 人の新たなフォロワーを獲得しました!" + one: また、離れている間に新たなフォロワーを獲得しました! + other: また、離れている間に%{count} 人の新たなフォロワーを獲得しました! subject: one: "新しい1件の通知 \U0001F418" other: "新しい%{count}件の通知 \U0001F418" + title: 不在の間に… favourite: body: "%{name} さんにお気に入り登録された、あなたのトゥートがあります:" subject: "%{name} さんにお気に入りに登録されました" + title: 新たなお気に入り登録 follow: body: "%{name} さんにフォローされています!" subject: "%{name} さんにフォローされています" + title: 新たなフォロワー follow_request: + action: フォローリクエストの管理 body: "%{name} さんがあなたにフォローをリクエストしました" subject: "%{name} さんからのフォローリクエスト" + title: 新たなフォローリクエスト mention: + action: 返信 body: "%{name} さんから返信がありました:" subject: "%{name} さんに返信されました" + title: 新たな返信 reblog: body: "%{name} さんにブーストされた、あなたのトゥートがあります:" subject: "%{name} さんにブーストされました" + title: 新たなブースト number: human: decimal_units: diff --git a/config/locales/nl.yml b/config/locales/nl.yml index ce1ceabe8c0..4c2829f985f 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -265,12 +265,18 @@ nl: unresolved: Onopgelost view: Weergeven settings: + activity_api_enabled: + desc_html: Wekelijks overzicht van de hoeveelheid lokale toots, actieve gebruikers en nieuwe registraties + title: Statistieken over gebruikersactiviteit publiceren bootstrap_timeline_accounts: desc_html: Meerdere gebruikersnamen met komma's scheiden. Alleen lokale en niet opgeschorte accounts werken. Laat leeg voor alle lokale beheerders. title: Standaard te volgen accounts voor nieuwe gebruikers contact_information: email: Vul een openbaar gebruikt e-mailadres in username: Vul een gebruikersnaam in + peers_api_enabled: + desc_html: Domeinnamen die deze server in de fediverse is tegengekomen + title: Lijst van bekende servers publiceren registrations: closed_message: desc_html: Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld
En ook hier kan je HTML gebruiken @@ -476,11 +482,11 @@ nl: title: Moderatie notification_mailer: digest: - body: 'Hier is een korte samenvatting van wat je hebt gemist op %{instance} sinds jouw laatste bezoek op %{since}:' + body: Hier is een korte samenvatting van de berichten die je sinds jouw laatste bezoek op %{since} hebt gemist mention: "%{name} vermeldde jou in:" new_followers_summary: - one: Jij hebt een nieuwe volger! Hoera! - other: Jij hebt %{count} nieuwe volgers! Prachtig! + one: Je hebt trouwens sinds je weg was er ook een nieuwe volger bijgekregen! Hoera! + other: Je hebt trouwens sinds je weg was er ook %{count} nieuwe volgers bijgekregen! Fantastisch! subject: one: "1 nieuwe melding sinds jouw laatste bezoek \U0001F418" other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418" diff --git a/config/locales/pl.yml b/config/locales/pl.yml index c7682b22a46..5d77e0fdac7 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -46,7 +46,7 @@ pl: posts: Wpisy posts_with_replies: Wpisy z odpowiedziami remote_follow: Śledź zdalnie - reserved_username: Ta nazwa użytkownika jest zarezerwowana. + reserved_username: Ta nazwa użytkownika jest zarezerwowana roles: admin: Administrator moderator: Moderator @@ -183,7 +183,7 @@ pl: title: Niestandardowe emoji unlisted: Niewidoczne update_failed_msg: Nie udało się zaktualizować emoji - updated_msg: Pomyślnie zaktualizowano emoji + updated_msg: Pomyślnie zaktualizowano emoji! upload: Dodaj domain_blocks: add_new: Dodaj nową @@ -194,7 +194,7 @@ pl: create: Utwórz blokadę hint: Blokada domen nie zabroni tworzenia wpisów kont w bazie danych, ale pozwoli na automatyczną moderację kont do nich należących. severity: - desc_html: "Wyciszenie uczyni wpisy użytkownika widoczne tylko dla osób, które go śledzą. Zawieszenie spowoduje usunięcie całej zawartości dodanej przez użytkownika." + desc_html: "Wyciszenie uczyni wpisy użytkownika widoczne tylko dla osób, które go śledzą. Zawieszenie spowoduje usunięcie całej zawartości dodanej przez użytkownika. Użyj Żadne, jeżeli chcesz jedynie odrzucać zawartość multimedialną." noop: Nic nie rób silence: Wycisz suspend: Zawieś @@ -305,7 +305,7 @@ pl: title: Niestandardowe zasady użytkowania site_title: Nazwa instancji thumbnail: - desc_html: 'Używana w podglądzie przez OpenGraph i API. Zalecany rozmiar: 1200x630 pikseli.' + desc_html: 'Używana w podglądzie przez OpenGraph i API. Zalecany rozmiar: 1200x630 pikseli' title: Miniatura instancji timeline_preview: desc_html: Wyświetlaj publiczną oś czasu na stronie widocznej dla niezalogowanych @@ -339,10 +339,12 @@ pl: body: Użytkownik %{reporter} zgłosił %{target} subject: Nowe zgłoszenie na %{instance} (#%{id}) application_mailer: + notification_preferences: Zmień ustawienia e-maili salutation: "%{name}," settings: 'Zmień ustawienia powiadamiania: %{link}' signature: Powiadomienie Mastodona z instancji %{instance} view: 'Zobacz:' + view_status: Wyświetl wpis applications: created: Pomyślnie utworzono aplikację destroyed: Pomyślnie usunięto aplikację @@ -485,33 +487,42 @@ pl: title: Moderacja notification_mailer: digest: - body: 'Oto krótkie podsumowanie co Cię ominęło na %{instance} od Twojej ostatniej wizyty (%{since}):' + action: Wyświetl wszystkie powiadomienia + body: Oto krótkie podsumowanie wiadomości, które ominęły Cię od Twojej ostatniej wizyty (%{since}) mention: "%{name} wspomniał o Tobie w:" new_followers_summary: few: "(%{count}) nowe osoby śledzą Cię!" many: "(%{count}) nowych osób Cię śledzi! Wspaniale!" - one: Śledzi Cię nowa osoba! Gratulacje! - other: "(%{count}) nowych osób Cię śledzi! Wspaniale!" + one: Dodatkowo, w czasie nieobecności zaczęła śledzić Cię jedna osoba Gratulacje! + other: Dodatkowo, zaczęło Cię śledzić %{count} nowych osób! Wspaniale! subject: few: "%{count} nowe powiadomienia od Twojej ostatniej wizyty \U0001F418" many: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418" one: "1 nowe powiadomienie od Twojej ostatniej wizyty \U0001F418" other: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418" + title: W trakcie Twojej nieobecności… favourite: body: 'Twój wpis został polubiony przez %{name}:' subject: "%{name} lubi Twój wpis" + title: Nowe polubienie follow: body: "%{name} Cię śledzi!" subject: "%{name} Cię śledzi" + title: Nowy śledzący follow_request: + action: Zarządzaj prośbami o możliwość śledzenia body: "%{name} poprosił o możliwość śledzenia Cię" subject: 'Prośba o możliwość śledzenia: %{name}' + title: Nowa prośba o możliwość śledzenia mention: + action: Odpowiedz body: "%{name} wspomniał o Tobie w:" subject: "%{name} wspomniał o Tobie" + title: Nowe wspomnienie o Tobie reblog: body: 'Twój wpis został podbity przez %{name}:' subject: Twój wpis został podbity przez %{name} + title: Nowe podbicie number: human: decimal_units: @@ -559,12 +570,14 @@ pl: blackberry: Blackberry chrome: Chrome edge: Microsoft Edge + electron: Electron firefox: Firefox generic: nieznana przeglądarka ie: Internet Explorer micro_messenger: MicroMessenger nokia: Nokia S40 Ovi Browser opera: Opera + otter: Przeglądarka Otter phantom_js: PhantomJS qq: QQ Browser safari: Safari diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 13fbceabb5b..fc4db6758bc 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -265,12 +265,18 @@ pt-BR: unresolved: Não resolvido view: Visualizar settings: + activity_api_enabled: + desc_html: Contagem de status postados localmente, usuários ativos e novos cadastros filtrados semanalmente + title: Publicar estatísticas agregadas sobre atividade de usuários bootstrap_timeline_accounts: desc_html: Separe nomes de usuário através de vírgulas. Funciona apenas com contas locais e destrancadas. O padrão quando vazio são todos os administradores locais. title: Usuários a serem seguidos por padrão por novas contas contact_information: email: E-mail username: Contate usuário + peers_api_enabled: + desc_html: Nomes de domínio que essa instância encontrou no fediverso + title: Publicar lista de instâncias descobertas registrations: closed_message: desc_html: Exibido na página inicial quando cadastros estão fechados. Você pode usar tags HTML @@ -285,7 +291,7 @@ pt-BR: desc_html: Permitir que qualquer um crie uma conta title: Cadastro aberto show_staff_badge: - desc_html: Mostrar uma insígnia de equipe na página de usuário + desc_html: Mostrar uma insígnia de Equipe na página de usuário title: Mostrar insígnia de equipe site_description: desc_html: Parágrafo introdutório na página inicial e em meta tags. Você pode usar tags HTML, em especial <a> e <em>. @@ -345,7 +351,7 @@ pt-BR: warning: Tenha cuidado com estes dados. Nunca compartilhe com alguém! your_token: Seu token de acesso auth: - agreement_html: Cadastrando-se você concorda em seguir as regras da instância e os nossos termos de serviço. + agreement_html: Ao se cadastrar você concorda em seguir as regras da instância e os nossos termos de serviço. change_password: Segurança delete_account: Excluir conta delete_account_html: Se você deseja excluir a sua conta, você pode prosseguir para cá. Uma confirmação será requisitada. @@ -596,7 +602,7 @@ pt-BR: open_in_web: Abrir na web over_character_limit: limite de caracteres de %{max} excedido pin_errors: - limit: Você já fixou o máximo de toots possíveis + limit: Você já fixou a quantidade máxima de toots ownership: Toots de outras pessoas não podem ser fixados private: Toot não-público não pode ser fixado reblog: Um compartilhamento não pode ser fixado diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 697a1aa27ce..6e63aaddacc 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -49,6 +49,7 @@ ru: reserved_username: Имя пользователя зарезервировано roles: admin: Администратор + moderator: Мод unfollow: Отписаться admin: account_moderation_notes: @@ -71,6 +72,8 @@ ru: domain: Домен edit: Изменить email: E-mail + enable: Включить + enabled: Включен feed_url: URL фида followers: Подписчики followers_url: URL подписчиков @@ -336,10 +339,12 @@ ru: body: "%{reporter} подал(а) жалобу на %{target}" subject: Новая жалоба, узел %{instance} (#%{id}) application_mailer: + notification_preferences: Изменить настройки e-mail salutation: "%{name}," settings: 'Изменить настройки e-mail: %{link}' signature: Уведомления Mastodon от %{instance} view: 'Просмотр:' + view_status: Просмотреть статус applications: created: Приложение успешно создано destroyed: Приложение успешно удалено @@ -349,7 +354,7 @@ ru: warning: Будьте очень внимательны с этими данными. Не делитесь ими ни с кем! your_token: Ваш токен доступа auth: - agreement_html: Создавая аккаунт, вы соглашаетесь с нашими правилами поведения и политикой конфиденциальности. + agreement_html: Создавая аккаунт, вы соглашаетесь с правилами узла и нашими условиями обслуживания. change_password: Изменить пароль delete_account: Удалить аккаунт delete_account_html: Если Вы хотите удалить свой аккаунт, вы можете перейти сюда. У Вас будет запрошено подтверждение. @@ -554,6 +559,7 @@ ru: blackberry: Blackberry chrome: Chrome edge: Microsoft Edge + electron: Electron firefox: Firefox generic: Неизвестный браузер ie: Internet Explorer diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 2ff3348f30d..3d210b73cc9 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -4,7 +4,7 @@ ca: hints: defaults: avatar: PNG, GIF o JPG. Màxim 2MB. Serà escalat a 120x120px - digest: S'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència + digest: Només s'envia després d'un llarg període d'inactivitat amb un resum de les mencions que has rebut en la teva absència display_name: one: 1 càracter other: %{count} càracters diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 2397e5161c6..017aa4021c6 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -4,7 +4,7 @@ fr: hints: defaults: avatar: Au format PNG, GIF ou JPG. 2 Mo maximum. Sera réduit à 120x120px - digest: Envoyé après une longue période d’inactivité et contient un résumé des notifications que vous avez reçues pendant votre absence + digest: Uniquement envoyé après une longue période d’inactivité et uniquement si vous avez reçu des messages personnels pendant votre absence display_name: one: 1 caractère restant other: %{count} caractères restants diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 2e5f969573a..2837ef1c3ca 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -4,7 +4,7 @@ ja: hints: defaults: avatar: 2MBまでのPNGやGIF、JPGが利用可能です。120x120pxまで縮小されます - digest: 長期間ログインしなかった際、その期間に受け取った返信の要約を受け取ることができます + digest: 長期間使用していない場合と不在時に返信を受けた場合のみ送信されます display_name: あと%{count}文字入力できます。 header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます locked: フォロワーを手動で承認する必要があります diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml index 507e464690e..23eb8e83b2f 100644 --- a/config/locales/simple_form.pl.yml +++ b/config/locales/simple_form.pl.yml @@ -4,27 +4,27 @@ pl: hints: defaults: avatar: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 120x120px - digest: Wysyłane po długiej nieaktywności, zawiera podsumowanie wspomnień o Twoich profilu + digest: Wysyłane tylko po długiej nieaktywności, jeżeli w tym czasie otrzymaleś jakąś wiadomość bezpośrednią display_name: few: Pozostały %{count} znaki. many: Pozostało %{count} znaków - one: Pozostał 1 znak. + one: Pozostał 1 znak other: Pozostało %{count} znaków header: PNG, GIF lub JPG. Maksymalnie 2MB. Zostanie zmniejszony do 700x335px locked: Musisz akceptować prośby o śledzenie note: few: Pozostały %{count} znaki. many: Pozostało %{count} znaków - one: Pozostał 1 znak. + one: Pozostał 1 znak other: Pozostało %{count} znaków setting_noindex: Wpływa na widoczność strony profilu i Twoich wpisów setting_theme: Zmienia wygląd Mastodona po zalogowaniu z dowolnego urządzenia. imports: data: Plik CSV wyeksportowany z innej instancji Mastodona sessions: - otp: Wprowadź kod weryfikacji dwuetapowej z telefonu lub wykorzystaj jeden z kodów zapasowych + otp: Wprowadź kod weryfikacji dwuetapowej z telefonu lub wykorzystaj jeden z kodów zapasowych. user: - filtered_languages: Wpisy w wybranych językach nie będą wyświetlać się na publicznych osiach czasu. + filtered_languages: Wpisy w wybranych językach nie będą wyświetlać się na publicznych osiach czasu labels: defaults: avatar: Awatar diff --git a/config/locales/simple_form.sk.yml b/config/locales/simple_form.sk.yml index 25265c73a6c..45e4c295402 100644 --- a/config/locales/simple_form.sk.yml +++ b/config/locales/simple_form.sk.yml @@ -30,10 +30,12 @@ sk: data: Dáta display_name: Meno email: Emailová adresa + expires_in: Expirovať po filtered_languages: Filtrované jazyky header: Obrázok v hlavičke locale: Jazyk locked: Zamknúť účet + max_uses: Maximálny počet použití new_password: Nové heslo note: O vás otp_attempt: Dvoj-faktorový (2FA) kód @@ -44,6 +46,7 @@ sk: setting_default_sensitive: Označiť každý obrázok/video/súbor ako chúlostivý setting_delete_modal: Zobrazovať potvrdzovacie okno pred zmazaním toot-u setting_noindex: Nezaradzovať vaše príspevky do indexácie pre vyhľadávanie + setting_reduce_motion: Redukovať pohyb v animáciách setting_system_font_ui: Použiť štandardný systémový font setting_theme: Vzhľad setting_unfollow_modal: Zobrazovať potvrdzovacie okno pred skončením sledovania iného používateľa @@ -53,6 +56,7 @@ sk: interactions: must_be_follower: Blokovať notifikácie pod používateľov, ktorí vás nesledujú must_be_following: Blokovať notifikácie od ľudí ktorý vás nesledujú + must_be_following_dm: Blokovať priame správy od ľudí ktorých nesleduješ notification_emails: digest: Posielať súhrnné emaily favourite: Poslať email ak niekto označí váš príspevok ako obľúbený diff --git a/config/locales/sk.yml b/config/locales/sk.yml index d3ae96dcd3e..4cf7d6f3098 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -1,6 +1,7 @@ --- sk: about: + about_hashtag_html: Toto sú verejné tooty otagované #%{tagom}. Ak máš účet niekde vo fediverse, môžeš ich používať. about_mastodon_html: Mastodon je sociálna sieť založená na otvorených webových protokoloch. Jej zrojový kód je otvorený a je decentralizovaná podobne ako email. about_this: Info closed_registrations: Registrácie sú momentálne uzatvorené. Avšak, existujú ďalšie Mastodon inštancie kde si môžete založiť účet a získať prístup do tejto siete od nich. @@ -38,6 +39,7 @@ sk: followers: Sledujúci following: Sleduje media: Médiá + moved_html: "%{name} účet bol presunutý na %{new_profile_link}:" nothing_here: Nič tu nie je! people_followed_by: Ľudia, ktorých %{name} sleduje people_who_follow: Ľudia sledujúci %{name} @@ -47,17 +49,31 @@ sk: reserved_username: Prihlasovacie meno je rezervované roles: admin: Admin + moderator: Mod unfollow: Prestať sledovať admin: + account_moderation_notes: + account: Moderátor + create: Vytvoriť + created_at: Dátum + created_msg: Poznámka moderátora bola úspešne vytvorená! + delete: Zmazať + destroyed_msg: Poznámka moderátora bola úspešne zmazaná! accounts: are_you_sure: Ste si istý? + by_domain: Doména confirm: Potvrdiť confirmed: Potvrdený + demote: Degradovať + disable: Zablokovať disable_two_factor_authentication: Zakázať 2FA + disabled: Blokovaný display_name: Zobraziť meno domain: Doména edit: Upraviť email: Email + enable: Povoliť + enabled: Povolený feed_url: URL časovej osi followers: Sledujúci followers_url: URL sledujúcich @@ -69,12 +85,15 @@ sk: local: Lokálne remote: Federované title: Lokácia + login_status: Status prihlásenia media_attachments: Prílohy + memorialize: Zmeniť na "Navždy budeme spomínať" moderation: all: Všetko silenced: Umlčané suspended: Suspendované title: Moderácia + moderation_notes: Moderátorské poznámky most_recent_activity: Posledná aktivita most_recent_ip: Posledná IP not_subscribed: Nezaregistrované @@ -85,6 +104,7 @@ sk: outbox_url: URL poslaných perform_full_suspension: Suspendovať profile_url: URL profilu + promote: Povýšiť protocol: Protokol public: Verejná os push_subscription_expires: PuSH odoberanie expiruje @@ -92,6 +112,12 @@ sk: reset: Reset reset_password: Obnoviť heslo resubscribe: Znovu odoberať + role: Oprávnenia + roles: + admin: Administrátor + moderator: Moderátor + staff: Člen + user: Používateľ salmon_url: Salmon URL search: Hľadať shared_inbox_url: URL zdieľanej schránky @@ -108,17 +134,56 @@ sk: unsubscribe: Prestať odoberať username: Používateľske meno web: Web + action_logs: + actions: + confirm_user: "%{name} potvrdil e-mailovú adresu používateľa %{target}" + create_custom_emoji: "%{name} nahral nový emoji %{target}" + create_domain_block: "%{name} zablokoval doménu %{target}" + create_email_domain_block: "%{name} pridal e-mailovú doménu %{target} na zoznam zakázaných" + demote_user: "%{name} degradoval používateľa %{target}" + destroy_domain_block: "%{name} povolil doménu %{target}" + destroy_email_domain_block: "%{name} pridal e-mailovú doménu %{target} na zoznam povolených" + destroy_status: "%{name} zmazal status %{target}" + disable_2fa_user: "%{name} zakázal 2FA pre používateľa %{target}" + disable_custom_emoji: "%{name} zakázal emoji %{target}" + disable_user: "%{name} zakázal prihlásenie pre používateľa %{target}" + enable_custom_emoji: "%{name} povolil emoji %{target}" + enable_user: "%{name} povolil prihlásenie pre používateľa %{target}" + memorialize_account: '%{name} zmenil účet %{target} na stránku "Navždy budeme spomínať"' + promote_user: "%{name} povýšil používateľa %{target}" + reset_password_user: "%{name} resetoval heslo pre používateľa %{target}" + resolve_report: "%{name} zamietol nahlásenie %{target}" + silence_account: "%{name} stíšil účet %{target}" + suspend_account: "%{name} suspendoval účet používateľa %{target}" + unsilence_account: "%{name} zrušil stíšenie účtu používateľa %{target}" + unsuspend_account: "%{name} zrušil suspendáciu účtu používateľa %{target}" + update_custom_emoji: "%{name} aktualizoval emoji %{target}" + update_status: "%{name} aktualizoval status %{target}" + title: Audit log custom_emojis: + by_domain: Doména + copied_msg: Lokálna kópia emoji úspešne vytvorená + copy: Kopírovať + copy_failed_msg: Nebolo možné vytvoriť lokálnu kópiu tohto emoji created_msg: Emoji úspešne vytvorené! delete: Zmazať destroyed_msg: Emojo úspešne zničený! + disable: Zakázať + disabled_msg: Emoji bolo úspešne zakázané emoji: Emoji + enable: Povoliť + enabled_msg: Emoji bolo úspešne povolené image_hint: PNG do 50KB + listed: V zozname new: - title: Pridať vlastný emoji + title: Pridať nový vlastný emoji + overwrite: Prepísať shortcode: Skratka shortcode_hint: Aspoň 2 znaky, povolené sú alfanumerické alebo podčiarkovník title: Vlastné emoji + unlisted: Nie je na zozname + update_failed_msg: Nebolo možné aktualizovať toto emoji + updated_msg: Emoji bolo úspešne aktualizované! upload: Nahrať domain_blocks: add_new: Pridať nový @@ -129,16 +194,43 @@ sk: create: Blokovať doménu hint: Blokovanie domény stále dovolí vytvárať nové účty v databáze, ale tieto budú automaticky moderované. severity: + desc_html: "Stíšenie urobí všetky príspevky účtu neviditeľné pre všetkých ktorý nesledujú tento účet. Suspendácia zmaže všetky príspevky, médiá a profilové informácie. Použi Nič ak chceš iba neprijímať súbory médií." noop: Nič silence: Stíšiť suspend: Suspendovať title: Nové blokovanie domény reject_media: Odmietať súbory s obrázkami alebo videami + reject_media_hint: Zmaže lokálne uložené súbory médií a odmietne ich sťahovanie v budúcnosti. Irelevantné pre suspendáciu severities: noop: Nič silence: Stíšiť suspend: Suspendovať severity: Závažnosť + show: + affected_accounts: + one: Jeden účet v databáze ovplyvnený + other: "%{count} účtov v databáze ovplyvnených" + retroactive: + silence: Zrušiť stíšenie všetkých existujúcich účtov z tejto domény + suspend: Zrušiť suspendáciu všetkých existujúcich účtov z tejto domény + title: Zrušiť blokovanie domény pre %{domain} + undo: Vrátiť späť + title: Blokovanie domén + undo: Späť + email_domain_blocks: + add_new: Pridať nový + created_msg: Emailová doména bola úspešne pridaná do zoznamu zakázaných + delete: Zmazať + destroyed_msg: Emailová doména bola úspešne vymazaná zo zoznamu zakázaných + domain: Doména + new: + create: Pridať doménu + auth: + login: Prihlásenie settings: authorized_apps: Autorizované aplikácie back: Naspäť na stránku + users: + invalid_email: Emailová adresa je neplatná + invalid_otp_token: Neplatný 2FA kód + signed_in_as: 'Prihlásený ako:' From 7badad7797b487b411a2ab34e0f7413741974bb4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 17 Jan 2018 23:56:03 +0100 Subject: [PATCH 2/5] Fix home regeneration (#6251) * Fix regeneration marker not being removed after completion * Return HTTP 206 from /api/v1/timelines/home if regeneration in progress Prioritize RegenerationWorker by putting it into default queue * Display loading indicator and poll home timeline while it regenerates * Add graphic to regeneration message * Make "not found" indicator consistent with home regeneration --- .../api/v1/timelines/home_controller.rb | 10 +++- app/javascript/images/elephant-friend.png | Bin 24466 -> 0 bytes .../images/elephant_ui_disappointed.svg | 1 + app/javascript/images/elephant_ui_working.svg | 1 + app/javascript/images/mastodon-not-found.png | Bin 19560 -> 0 bytes app/javascript/mastodon/actions/timelines.js | 13 ++++-- .../mastodon/components/missing_indicator.js | 9 +++- .../mastodon/components/status_list.js | 21 ++++++++- .../mastodon/features/home_timeline/index.js | 37 ++++++++++++++- .../mastodon/features/list_timeline/index.js | 8 +++- .../ui/containers/status_list_container.js | 1 + app/javascript/mastodon/reducers/timelines.js | 5 +- .../styles/mastodon/components.scss | 43 ++++++++++++++++-- app/services/precompute_feed_service.rb | 1 + app/workers/regeneration_worker.rb | 2 +- .../concerns/user_tracking_concern_spec.rb | 40 ++++++++++++---- 16 files changed, 165 insertions(+), 27 deletions(-) delete mode 100644 app/javascript/images/elephant-friend.png create mode 100644 app/javascript/images/elephant_ui_disappointed.svg create mode 100644 app/javascript/images/elephant_ui_working.svg delete mode 100644 app/javascript/images/mastodon-not-found.png diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index db6cd8568b6..bbbcf7f9088 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -9,7 +9,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController def show @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + + render json: @statuses, + each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + status: regeneration_in_progress? ? 206 : 200 end private @@ -57,4 +61,8 @@ class Api::V1::Timelines::HomeController < Api::BaseController def pagination_since_id @statuses.first.id end + + def regeneration_in_progress? + Redis.current.exists("account:#{current_account.id}:regeneration") + end end diff --git a/app/javascript/images/elephant-friend.png b/app/javascript/images/elephant-friend.png deleted file mode 100644 index 3c5145ba987ff14621d0632cec74e02eb3886d8b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24466 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 \ No newline at end of file diff --git a/app/javascript/images/elephant_ui_working.svg b/app/javascript/images/elephant_ui_working.svg new file mode 100644 index 00000000000..8ba475db0a0 --- /dev/null +++ b/app/javascript/images/elephant_ui_working.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/images/mastodon-not-found.png b/app/javascript/images/mastodon-not-found.png deleted file mode 100644 index 76108d41f69272a51209faa631f6db3bc000a797..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19560 zcmd>G1y@^Lu*IF?PN6u(-K9{V#Y-VT(BjY_#odZqvEtT3u@KxHN^uG9lHwku5S*9q zy`S+`7VDCmb#G?QJ+o(@nSB!TLF*kM9xWaU3JRg>d!>)a<0T3TDmxAq^1o)c5-Q{Y z%ToQF63X*`uOFSIsmL?9U)}@VQBd&7{`*5k$4Ft(ikP?-h%~95U^GqOGYB7@&OZoqrmuKhv!WFd_;Ma{p z=Z7kS@7Bplg2QU(c_sIrQi*??^Nk)d02-OUHh$}mh;N6x(aY^0Y%^Hd245O6`B%pe z9H+((6J!w^3)=tIk2+c-ir^*2ZZ7m=oEv9DE9-8PtzJ=Z@GV8k^7KO{_V}R9g6`Dh!Dpdd7VE|`|myholvHm<>#}hnxpH$8@ zKrQuhuoJ-wSts=Ma#Hn7=Xd|N?ovFn6osI$gFf+P@C99QjS>KgeX3DbdKNYTiI(3w zCS=ZF_tRP&!4NPw*pPguofbqe0o1={TlHS83I0Lt9_TFv%{aK$Cl-trv|hRp4Q9vS zRAK0jOGZAlqy8|W;4e(Br|;q%&c#Q-Mj<8pEKuG-{g>VwY#wgTw6JU~aq7f{ zp=mkg+TPuP`h|zAIz8~`r#FijmhQ^r?%P+JvWYTYtYuG?!yb3KpxfHiuHMK^Z<&z&}9eQL>Firs3B)S%Afsw4&rh8AZ0r#PZW;amu0j><}Lhqq{ z+g=5Vh_)^b6_@*O`k!V&2W~16_{N z^NASb?X78UE_)KGt?rna*Y8mt_Iffxf*bvg|8%2ItbzejLC@`%tg3Qg@H3?9Q6Yp! zQ^JjP>*n*1g*jucCm-IgDoU*pTIF+A9Od~%dkUk&n={F#4Y`l)ryDC-0!AHr{8mOI%FGBd#kyr6OO};AQQhOutO8lGsMoLnS zc9KDxxqf1vAxaSSjjPqReei2&#=Vx5uWCLWge2etO=6{^Gn+b{@!Y2M$Cs!Km_fEk ztF&|FJTDwO+6yx`cu2fMw|b;Q9xEG-jFAIPUh{$n1(?FhezffY`N_pylKR?4M-AsE znc@xc*{ib{{aN$2<_D_zc+{V(O z_*9i99l2h7=J{QvRgdOq*Oka2Awq&^D;d0jb60$slo2b4{DukkBbqe?{8=-#&bL*T zu>z5Q;Gx#Mz&0wJB7(GA)9ex%S>Wv-@LON~UvaZ2vHD>;=t4m#J-@|R) zm1l40uVrEq>d3+OgN8SImNTt3orT@BR%-FJEq_vMU(c@xloa9MBotHaA0$AB7*U>- zQWpPqR;*cHz5nDO*t-mxv2x%#X2emCi!AP^1=U%fe5^GBJPn3UAwxo*Y%gw2&Q)rU zcbbs^yC)Gn*i7jK^Dle4a*B^62QfoTR%5uJlP+QzPt!1*2}ww!p`Gds`4LYqw$dMP zB-2?C?Dpx?&BN}%vdqxGZ{A)01@5Q8gzMfA(bd3Tqf_nN)7I6$z+g+EeA3^}E~YM| zlb^ecf!{^g3UI}x-0o5K_-5lvi4_gepKf;ti~{ExfoM7xAZ^pRW5<)4)>>7a??1lQMs}puw{$ET92m>ahV9n z_q{%TV>q`yXdFCpGa~)!f(a2PznEh-03e^#%&^aCy18aHo&(qWOT_h*QhD{Y;VGJ2 z!{n&cJ+`QN+;#fejfg0{Af(B6Uw7}HI8UE3Rj`+743Dz>bVc0@;V~9v4!RL_x~YC@1y7kP$ zp0TbzT05ZDhpUzch9V|T;=yD+iT{P~DiUYRqnph<>djrmjYC+#cXEOQF(K3rr3Plc zje91LM(P=yb;Gb)MH~jW?7RfTDF`b0?t?P=fWN$>=AH{G+1{$cq&)3cHJ#a9**)J| zN7(Ugw)#>2`7ehM9J4Y|_aMWDbIHhkNF&zKv8eOyuKp)0W1W8R#n|p$|Fg@2r)nsp zD(U^tu)aShj6PsXH9)$Ld9#kEg~-PVbI8&TqW8Vg8Xg3N$n}GP`YEvWV>iQDG<` zH>lkD#7$F&D{nQacmYpJGdxe{lhB+A0FKH4CyFc;6Q8#%HYC3USzRsSE-(^gghqwJ zQBTQO5aGlh0P&s7vqzY|RZYt%Oc?Qk5_`Md3mE+!dVoB1C~86Q(^9n)gkIv|fK_#! zTai&O`^Uv+m4v#!Sh!+3IIK6T7n>t6AeyP5E^tr#AXVqM1BaBXN<$nCkRg>!; z-RgFouT@fpRSKpG(MDkRo!4sz?(B55|4XEzqI%ZJ-+30%Oof)j_zz2Zll-h0`3e>@ zFQd@@@Dm(9wOH5W+~TvOqZi48YIjwVE)^c4e!_6%sMOLxlBtCuSb`t$bWwbn2zRUR zh63+u` zMKIp|3iNgp2{al8%7PnBN$#+pV&J>Iki-%PziLlgh4^UK5?SnSKc=uvlUPA4LN!5N zBf*NY&r$59c}Qmj1p+V5`5g-aZ~qm$+d&`JSrUU_scX)A&!-+z&y!O3!r(h7MD%%{ z`V!K}ETk9*y(SGgXP!+20*j~Xcs1Gi#X?0p$$DPgR2<@&MQHJHriw7*9WPf&;VjnQM zWuFi|z4ic;vjI(`wtD)JP#n?H!WVn%1vx*T$9uMc8K?evSv%oHY|Xx2VY+WXr6lIW zmTn=GFR)DWxAPr_-oeawi6A1)yvwJjPd-28shu62ArW@@L5cbdyI*rGFx}-?+DL?pY>X+<0q9qjrfCWgh|Aj$cd{*y0N;zBXy!O zbzKL2Pl4ZL$eQns*kfSy@dM1MG4S#_yZ|S6D_)gmF3DfN{YI^3-KUG)OXxNffdCMic;TpU(XTa_vIp$qyccK#7jb9kIap+A%=tn|&yGY|4>Oc~ zo;|=(2_&Xwb++EOrHzUl;^Yx_GJ;iAeZ-5O*G@94VlBI>lDZoyF8T&&?HuVG#Hy>K z=}M^78h|OgG>37;@)n@zH+uVf+Zo>xr@-k%u}tL(;1M-BR&) zm=wq(Ht&9K!Y+F_^yXfqCy2(SgCVz5pUNZo??*G zzXhDm{1NBjjcF7sTmra%2@CNnq&hB6Pxwl(;CpA)8E111TX`N+zUq}QgDci&&L|yYSCrzhmV5x_z)QePo3Z( zv+yWl$!KPqV#iZMh^FDb--kSTlijK&ih#;^_HS)3w5Fn_MQ+nd8J;&=1G%HPVE5$u zJ*wi>LqgX$Z4iQ@S|bukS~PQsz{71_q;(JkkK`x|pxDczS1tb}FvQkDcSnqtE=%i3 zPGFxXvS4l1a3z-Xo8|h;#wY$zdHZ-(NIkXpR>wInzR&gLm;Stbfd9^#7Rx42PTY>| zI8OZn1lT9}&Z_v4`HZCJA9kOe%ih3xOTbUF?ts4s#XD){B*~aoV}0PyM@BRWybk%( zN3%art*U2%bm$Iv#?p~>#EO%l2!+U~(|txOm-^S(eb5tUjTp-yf8!mi z9&@VXK1;gZ4%)bmcYl7m7Ta0DAx=hw(w%&H&ZXtUP2yDN<4R7D(A~YwPG~3*TGUd*Xx7kq!Y(yN| zg&hj_*ywdDr%_aEwY73=*yPP9;r7;^Yzu`Q5#QDo3Kyl*(0HsK9mPu=8pZq8~QIVZcv7% zmW>ar^PPi=CHjFi`mfcvZHn~b7t#n`w z@OS(|&BBU%^CO-E*Yl(@x??8#p^oS;<|o!O)5e7{>Oo8B)-i?m%2J7^yE|QcG625x z^MHJXlI5sOPK?*%wdx<9Yaag@faZk1U3@$Tlfhiy$ZPRa0B>RvY7 zvaty1qj&l5ioNr~>Sc;%P9}l&UxD=~b9Ap^N4U$2?9-Z7KQ(bKhuJvp!~VuKZ@E>Q zN*E*42bJEHgW#^T^1cth2A;*F0R$u7fjyNP9f-G8E0Dz%%M%x^p1Q3IFzqIw$M&s( z#Q%z^o1D9SVQ;nU0T8H@{f&E3^+SeGrfLKewC`7)sD-a7{gMU0lUqR|xnx#2yyyD8 zZ9nT;Q>Ha(|HC!8I|?3*g(v{L3Q`^sOnh}8 z+4WKc_z5y|fZLwbQd%}DI_2>L-r?{>ekxwL`Zdhwy<)0rkjC~BE{XB*;tM;57CI3? zba3S3xFS?7-+af}E^_#nt;m3HHFoHK*tosY3b=6M-W^X6MP~g}{mBiSWqi4zG&G`p z4`~m7)9GKY_ur>@r7zGOD|Un38ywKAm@vpG2rU>jS9YC@_Y+2`z`b4QiQKEak%&U( z8O6z0JbW0FO^hqF(-?~E0DmwJE~ zQONn&h10>q%$TF$x#Vx{+M8l$;I^*Hyx0VA6FP6@;ufh)0qKmnXe%rrEf%SnB1lP9 zz)|=?;#Rug6b{<<Rn3c+PME_L`DfFx=8ooSzIZ)xr>A}nj3VOy^@ zc!n$h)Y=iw3`5H*v9?K}NyRn7b@2lXcH#&bvBkHwwd zr!Z)AGF)LFlPL9fO*NyyG=X8wL~_f3)B19)UL!5&xIJFb91K2G?~x3JSIJ6lTGt-A zp2oJzO3NEGCg&wz+eeZFkodgD2%I!`56Z@8N3i{%8I%i_{DP?1a z3J&=&AIkO-@1qq+A#?H4-=ASQfZ{|fXiZgeKW5*l8}yH=uwF1-`Das<7rb!kv*CoR zxFXKo=HzH(1nXo21cUK!%DBArM-iPNjOW5si3uLisu%jkD&M(f=Tpl$d5-Sc7 zWz*H2sr~sJOE!5C>@I}NIV;RPrr?f6MgqoaP@o*tbP)UTgHEK0(muaY%C?#SkER7^ zML55#%nf6a8bW`Uhy2O_^2Okzxw!vHGD=q*4|Aqydj!IYG`zp{R4*nRs+)~YM2%P2m@e&XyKE9oj`q*33lJ?-};`#-- z%GOuvqGRQb-G$`ZF)Ux!s}uNw<4bUh@@PrwnVI9}vxAX6iTWPI-5Uc{L!-0BwNtO0 zDm*S*|8R_O33M=5BDFqAUcBK43^5=$T2_HV!D!X`J9t=?%n|;r;KbRsn zVvH{DbL%>{IMw-?FSKCgKkk4|^V413;C_jPmKA?UeW(aU`DgCb^6T6*Z<{Pu1S53p zFM7Elx1+PaO3|Binsmx)lR;LJ*x&W7mtx#5bj(yE8u|{yCKyo`3_o=~ZMqlx63+tGR?i$R2IigMU{M37l3=ZY)Z6YpYVJ2x zV4|7hB)>a-BTl6KhsepFI*>_Z41&K_r%*ODvY!UfqVd&QXZ^r4UWr`xxBJsE2)xI) z0X*`ZIe{UwjcLvtNyTl0QpVh`^u?O=h_`R1G``Vm4cFQ78R~Flh|Ac}7@E|$d#_}c zIQzQ`f1u1n@h-Y+L}E+gJHZVa#nWkxm4}&uOtb8g@g)g$1P-uzSvsI`((Adk4O~s* zBaSaYF|(OX(oxUYiO{pbL{m-au*{f0@(j9O7iLJPoe=96SktQCu6ms`{C3iNYTLWu zm?-k7T93DBc0>Na<6@8aHYnn>!Cq;8IH3Km(RcBxO(5%6yh{F$pDqyDObNs=7@UvY zHH!TaFgUHamY-o~%oUNqYXLHDJ^J)Ja8P)Cd*s^WMMpkf~+!r+0ya3o@ zO+DD-IEz-%G)nm}fl`s$~bVkBkuFztx~zeRVuJ^5K6(lKtSF)K+!Gzg=O}ny$kU zITRWH)i~ZpodQ9d*t*+PS5A36(YX*_-~2&8Y^u(AXQN9w!d$+tnYp zj^%VinLG2siCmkqboSQM3_*rZB4b{`m8Bg_hJW}RFhne2`w}Z zE--k{z5fAxqX{SnXTdgL^_R}gD`qjjp^>Ll%2Rl2OqQhZ$4-%*0xIPC245UB+4AM7 z_#(du?qdXC)S25-ra9}KGIuOop+!^uJnR(f0&sssK{3sB>J>J9(| zVkIX=^nBDYI`|bTQ`Fe?`>upZlaTxVrGkZ1wX^Im6Gnl$R%8yGByzy&_emN}?=zclB%wt!eRcZ*z zQAyz`TAGX2okIuTF~D|^Ym|#|w<*uhcJNpJ*S%HDC)zV-P$dr! zpUB?qQOiPq!zZacOUTcegRwP7LN$Q+0cU+hhi?2>Mwh8oYuIMstA?uUx%nk(;pU0n zDA~YC7s`6LygG0k3lob`U|^B>AqXIcL0%cL-m2qq5Bu2;rA@{RCjxyaKMapBd!@PD z@2B#9CR~P>M$;|ULGyzw4AQ93Cyy^j9gdh=Hv$pdr;|}q3a8&|jw5`er0LY9S$~^V z1>ThQEQC;nb|LM= z_tVzSY1M=;Fk^>3oJD73`WjBZoXGb7vd3pWHo<9LYuPzru6=;P(}je%?jsA4f`=u!2HILWT)BUWIB@iDD0a*55u%~Bmv&Ic z4NvPU?}W^E0MACwWZM(XwcG;HPfm*YSbFV_CqAFyx}opDbuuJ+j7{^qjZt~EA8vAG z$SxX&Um`LHG8S?IJWj|A$gdPW(cPGSdP!Yz7zKi+xa;3lw#(+w=zsscn@**C_lEIi zwkz#zs`SZ207u;IN(h{cB*Xgx1To(_^c#?7(cqp1gR>2QIsj3XaII?hw;6Yl&O9F$ z3??QPxYe=(8J1^7yJ?b7+Nze8mVaha&H{>tD~`six661ZfVc|o@W(Kb`zk3Ur7v#k zvDUsjTwM}A@?H!4CrQ%Pef?9KH{0P=UPo3puZpVfyYxAY*cYj)njcKvVmjqjr#mE_ zSuuEwjYlFHZOuKZSK=2>5WE=+vJRiEWv0F%{Nio<$7e+PP>29&@;?tRv^Z)e>n|l# z0KR-KoSq4&-{Q`4xwNayrOZ{34~^&2o6gEHyLbU3ThBsfxp#p9^m6${oAXHMigT_E zhYP;CGl!dFyhCMuQ_jnswu34#sX zMeX5#H=HXkB`_Wb3TGoqgoH*<0BT%L;-pnP^6Ke6hwuq)PzowR^3?%vrAN+8K3^V@q*Ymepl=Ub`9?LlQ zW167Y#d#5yHf1l8*kIMf%Gyo3|$xPUG(9ZsQFo)x@-d^6sU-%2)F^x4lx33I_&(fh5m5S^ZL; z5$?}zs7@l@(DB{3pM>&7+EZQn)+Xw{|B(cP3$Mpx<88VB(80~1JeS80T@AAw4hTMD z(7}~obY>QQpl;$@C1P%C-$-qGyqh+y!?vz=)9q_lt3P4HiZ$#0QSZpv#|Xzl{4!iwG2ld71sMcPoBxfS?0DkMfF=n_@8cd877Mq8QRDf<5nV zteV+27uVNOQa5eRe@t*1sYs_)UH?GEzWaM+V<7gnOE{M4}A18ots^+pEhS7zo2`e?ISwoKBNC$18w-|5(Rve zJv?Fk1Y*gbGQ5s%^`-9Z{b?nlSfbon&3VY^ymrEm@+|zp9*0q38`PL#{x82efx}{J ziK9DUkZ`1%tbY|-&N5zbfR;4FL_En|Hg4(|ptYwne>=W|zSCe|xG_-}zC@cTt~9hR zE_s#(&`b&MFYfmZygBJGzzMD6tR1KmqBnC_sU9IUzqggU0c~GN2#t-Hypv`*1P>U- zCBsJRn)nMMcDjmImW*SUhY6#I{F=Ok{I5*{8PDuO_0lR&oMJ7P zJv7HZdUk7%VJRIu!bS39NUqkO3)b)Xul;tb%?vB*cIxL>+p|+x+Y^<4e_C=|=uMg8 z9BpcfOGJO}|4(rgcsDyd`et|~M4C$!$rPnUE^D5xtP)NV9qToo;n#XY6C_muGbN z=A>y9uABgELfMgRS?@8wIoz@H-B2nT;rQE=P%@5v#JvF)dD%yeQklztGx1H{%RN5~ zK$X$S;-V4@GKg&9i-~KmEmgx_|HmDqKoq6R5YTp*E2Fo-)mim(rnSu%^J4V_KxKez z{7`CDU3lU6pn24GNLq6&5EiAp9X3FF@K0)CJ1mD2SSJR!WLWDB81~& zOOarh!s>KUXAl@gEOYocCT5Os{Jd>+Bt@4bkf$Ly@a5mSKTy{1dvQ=faWNZ)g$%7= zmR8PREI{$s#?N*!FZQ0EYfbe+YCOxIFxf+N@FWTY+ z>qyi$n)0IEheDB!WYhoF-?6vv5(MEjRau3uo%6p3iYI76G8LFzTK?M)$NCFSonFRw zF-axP`PFMyX`R8vVLP6sc_IlkNyRioxjsoO$aE1#3SrX}rp%xOni#RV;&xSKY2kzG zJ{!?;Lp`LN3Qx>lSEPuj0%=~fKIg%$ePBJJX^t}o-)ErRKR{G`-<5S9WX|GxYTK%) z0!}n>G#4^_(H9oXB=t42j^oQlVbrC&zn{R zFBLG_8D|}sL+nJFY^5pibPf0_-B1eR4kDD@4=h|r+mp5Gq}0=coj%r~|=3o{Na z7#n`tc%pd0AT!kU$o(%P188zMbK(UwG_S_V03mMOgg?O z6!`CE`AOyF>&z8kJSEQKRfQ9uUNmZB2rl#e==M{ZKC(&XNX1Q^iUvb~Mk2XBc2RG! zUjq5Ae;e@vKg)dp+)=;~A;N%SA_)$n*~TX6oE=(kX>~%I2uJ4#9t>HWfL3|xE{{_D zJFPfdx>q*oUVapa)iEOtj#RNh+yT=@5iO5I<;DBxmRe+vllu4?Hz7ArgL8(c_XQ z=jlce5QG#k%A*Ga zuJ*BT5d~ook6WIJtLfaO0xB@SR!BxdJ3%ypOw{va1r^U0s+W{CSaa`r6`NNP(a~Dl zE37U5(oDu(MKrt`MIKj1@W^i&*-b~b3=`42QR6!i_pya0lf=0pH_N)@Y@WthqQ<`d zo&zuy1~r^eFoZfm<@*dN9Npn_Ef$t~sLT~>42vdj-fB(fOHS@J^97zs4NBj-Do?@D zu_*E8j%&Ypo_}MWYbS+}MVg#n;0F|+CV6VP+V*Fu*xMuHF;f_Jyx_Y(L%c4KMY6MO z%J_F=ia6AEd%EwvQdz$pOC>x0RXZvY9B+8hZfvsP&ZaQ~OJc=MJ^xj?GeL_lN0-l- zy}nnMcO+X;)7Ir(W89(n!STAy>&?D50n0a@DJWe4bdg7gCyO6Hbv{#7a5&lFk<@p6_Vr2Xs4S~3NQD(eVk zk!zHWgM=7dpB^OhWo{+tj_At3KJaJBvwCEwyyNfoIMVkzd1^Cz%tv@Am7b?;cnx47 z$OHf=14~W)5DR~G*e645K*^>&HMaXg?t{Ucr#v$Dnb@ht<+^_~ zXDi5Mc)#?V)P{p)RmkhJJZ6s>5t3F_5ieVX!)0OGgv@vj48ltqLt*XP&!}Efd9o2~ z)hnSJVr%Ns($dF{E{%N`eXE`8FGUOF!NHlM6Pcr6*O02 z`MZg!J{kn&c>$^1-5u}JS@}29Eg;xSBG|^KWxpEYULx_KGMv`)P2pG_^PONtV!(Ytgaqz7J+@j zybW8jZ(%b%3t+VlE*;Z%d7Qh|=*ZThVz58} z?)$emL|BX$BAR1_8Lf>uFI9?2`hU4n*cIm|_y^|U+rGT5;)5*2aR;_(YSeM!4HS{A z80ToHu&{3l+USgNL{BJn`XAZ?$&Zg6>sxClisPt|by6NG{#=*mZz)Y=nqV;2T;Lm| zM5ep7wN`)qcjFxLsUs!A`pDK5MGhV4zv?QyasFy!l1C-@_x+hD{1Lg;#Qc0cc*6gg z4t%Ey+Y2oyp)q?_h20|E*Rped+?+nU<*@>y!|Am9CGdepsU_($>NS*nmHq|AZZ5zB z>KmD}a2!T%Zc~zQg^h7d&x+dH`y`9<0v>)ZzZ&KHcWuw&M^IUSMCQ8*9@Js) z{7f(9cd~xL{p=?GhWRG~ly%N$g~=v52mmJq-0HVlu-WmhcN&_3fvzMs*ggGi*YbY1 z(|5HpP%06^0csX7(xU?psh8!4m@r{a(`T`>y?8cB|7qlnI zRUZOj1t=U2*A&5Tl77+1)ikRtH6HL?vVWH_<2@8nRjspjwn_cvaOZuJSigM11KnL= zs9ra6-3iP&Z?XZR(xc_W`KtqVY;zc0EH#nhT8+Cfi}|O_f7owV7zvQ?XIF5=kP|Z^ zqWEt%!bB|6r3!R#g8~GzY8`C920CYI=nbS!j()tXRN1z&G_1uEHc>S^T`^5_Rs@j8 z4}^|8ohbwWEt(G>ufVScn*D_bS#|!+E4$|b-4FK-IbWVF0xy5(%_Ju5lW?vydN@P#g-n! zk%^eDG9rk)MIB(X+eORb(Y_{bIwoE>FYdp#e4oiJF3kf5qX+GzeYL1$n+O?<7YxQu z?&dtxym)T*ZW2tf1-yLD^Dpo_TFc9iboylAw9mue6s!2h^cBXeQsH|geM@q|o4G_z zH9gYz8XED*_LiGeWSRu#7&_6h#Qm9N3S)SY5&6e;4Grxr{9C90#ykw>E?4~%8BZ_u z3=kgL-T-f?-a@YVbXL|W_%OTl^jUH*-`^~^!zc+(ntZRkEgdQft7N`dUOqI>hY25T z1X}nOao#);Ur`83a-dZ*fArbsCFkLzTn)^yI%j~YpYpamcTrNEE{)IWQz!!G2YtR= zwH4u2K@{&~&eD7v#@zXL(ZgT9aDQy7GMSeGZjzo6f2n4tAYaZq9$23~udws88hU2| z049GVg;DevP-die`z|V0SDp)<1t|ql`Jhiya%|?l?SSQ`VgXK2iK0^t_{=&G=7~~R zbat)|zS0=Ck%$0uq(;^-7DNS{Tvuo|_~Kgo>A;eKZ{g^494(GXke(n~XNjg~?g-P^ zpkhzq3G1dU=Jo~2!{+pbaF0PKwz4%4a1?`o$+~OGZ6+>p{CQnI*tw(pu_Y-`r^871 z?niV0@jtc+Nr$@tQ9^ugj<4~DC9)&260L^#ZSuo=#6>2gr7p3_R0aya)<(XnED5t& zyD+Kd9|HlXm`&dF{Eid^k>?IDXh^BXnD@6IeaP=k?HTPvxTySCIhrcZ>4D<6imm9R zv8e(ML${%5<_tuUm5?fmiPnW;>R?l)^{5(HCsdiLpNUMt{?DS))OnCC~Je>(izPV8}VM%=kMpwY|X@y-?cqa4&${px? z@Nj~g2*HX|AKmz{tqKS% z1Gm_f@(Xdg2Q0Um1m~xuh=`B!b6Xr4#g!H~ogceD;sm_B$s13&av4AJ6BXPr8H1m# z%SMk?XVPUXw$c;4ofGX9*jo*Ze(37S`4ht*%j}Jlh6~CR&5*N|4|x~*oa+HKGH`P$ z8$qfPy14cq!$P^MoTTs2*_3dY;%@G~?OmjaY0K~ZyX6l1$(4xCh}tCpWlM@sz1|%a zz_#(MdQN3loZ{N|So!BoTE00(DaF4F4=2!n^HzgU?g`>#?URSAa9Udi>%%ieL}LWlVPWZ zwVyLurc712BO39v$Q$G`>2k_dn5{P~{JlFM1%U)8Gc;`oa#tS15Qme1X$GAq+gX)YC?w|JGT z0*_)D(3F4na$vn`3m->QfJa)ai6*$MxdU%tOpqG#Wsm}APdwbnjG zSH2>>O;gE3oOv33k zsv<*5VnI+^F2o|!CZEP~%SKz&@UbS)RHqOigt-XJTAoS(-$il-Q9t^ttRU+At=3P zTrL^d$(%GLAIit}5g`w1H1>tllhOh%#lnM`Gbpo@#)Y8QQgTN{=5v>VF+Eh0F46;z zdHC=Gp3%dtp26Y9-;uyw;5+EQ3$dw_tnP}Kct5uyV#N-eW0b_T7~e_uU3aCFs2N3zZd$h^Sgdd;W_&TcUHN-AUNs}bs zgN`0P-@nlus~=m!@|tjve>6QhCP_x~A@+fe92%{p$H(|aBE~pyf?nEqLTjl`@wH1= zhJlXjylC?19qa#n>?LcVT$&(pgc?EV-spYm!D)nKprrl{SGt=(lXlr7- z#_Qdp0`L5tlN{fg7%6B?IN<`iygPj`M5Y3!ORbWu3Gb-}Lt!DRuOZ zm%h<1aX6I|Ri0iGozy^=Yt1E9qYZroJ4rz9`dyGd7ieck>@E|>QaeMR~@EFfud|IMLa*QE_wPF zLq|Fa74FOG7OA{nPgjU2BHlGXx%pJ|vM5nrO4EjgQFp-fb*Q#_g_`J4qR_LoFh)w8 zM|{p$adET~G%G4`2B>k={-JetvJ&PX%N6ahE#oVn64v?YN9PJFdUsH9F1Ma#Of_su z^j2A)iwQCRRGDu7+^x*g4FyU~UQS}K`6J57#K(c;aCALpoLaVMXv)RedABtSaz_9& z!b3_XE0r&(L9p#?M?YQCX5)hyHRa&g2tad@E`O=& zX;@4z{JYiDO7H=oI|2$%efVOe_SS(@XLHwzv1*?yt;#PCsQ5YeIEO?SRsh`{>cw?x zXR)|Zw0)dk8h1b@U#`7>@pJw6dLtd0{*bW*Me;ZSs{_5htQE~jm@+q z)1bC=c9_sc^%YDy!R}8Z@ks$l?eG=(@B_xyr`(#s`#;o(O|qxep%_hx%{ z8_;yN@E&#v2YBOBcMrrV9dLY;&Xv0baC5G9ixlDCWJxNZ&I-^%XTKVQXeOrmIpi8H z=wcqTP+Bhr;L!?bCURPxd4?H-qJqavZ>`&bfP(!L>o~#QeWM*kdM%6iXOG0asRz2D zm+Wl^ge}2o%VAGcAQo}dhmMD9ZH;G00LJ40PF#YmNH5|2m_=L`*xLHDC;H}s_haYD zH!rdRSNZia;?DYd1j^bL{?Hd&(Jqk|Oh6>n?H8J3XeKg3YH7-oTJ8Z*fLg}wWkY9< zPH$pb?KXE#X%Af*e$V&o!>TE$dpezPLrha6yn%XfAgtIhm#YJYC-Wqe$reTORv#)Y zvMNrPi8(svvToM(^$yEI^B8qPb6r5&tiw4l3>U`!xlNFmr9eNb2ecs$I@r&g{zLmN z^d$w|VYttvYclpPwX%eaI#(kQ!Hy!U5cOk)XJ_1}v-xXTGx{tgoP!JV!~-qzsq;zG z><=eqwyv7FR}u68A*40~{NQsPMe|s#Ohw0eb_;r@m&>O-O-3w7E=HU5Tle6wsz3AT zSyhUfyo(Q$w`@mLKWk@G@@{w#Ejl=_ay*khF3w0KigsZc>*!c~Bc{%}cJ6Drqo0+V zQ-!?qN@BGEq3VdVs;ir$k$^|RO>l}qE}4|VbBX6;Dl}{`E!AQyq>-x$T8uKP;m z4$Jb(5wKRRCHrW}d@Ae@=@lE0l)RW)DcVvTNtEPCK9#3$Zb?4I$Zib!@R06;CR?TB z9UxbfvIbmnx6g@I>4@yDUcM@}^cOXo2OnxPK46sf@_}$Tj_*e71V|Q-SPdF*?mOTDJg)n8AHB4cs%T)lhIA&W(Ih-0A2=*y) z9f1el3c`{NP8^2`-*aow=rG4`<9}@HXBa>^XrTk6twJ;LQ;pFLEfIIF;D{g>#^w95 zUTGDjVv4nif|`W$7hJzQA;boigu&W#;s-Oo9_5dV=A;PQED5yGRMrE#F8{_+BG&{? zpu~@-CQy3Zo$JFeCr#c5%}Xx`u?QDKu2K*`lyX>%E*@%`BlYKL>%(gEFk6MGJlVN6 z%9pONCO9oOIQ(TRe_o01E@PQ|4^Ek%|CfWU{)ejLn|l&e?RFXHh-wqf#aGNkepuJ9 zqjDc6)>>t7_U6{|Jl-Q3VNO)I<3(*0|AwmXJ3`i##Pa;_hJ2}2c=2_>M^U??f(-q? z#k;x+bbSM>cB#$ucY1p1qCpB9RF}0kXC(6JEMTKLf|X@6qplV_1w{IVMEz8?`D-u1 zR#Nj|)!y51!wt7#@WgI*D=b8VQ}c!~{AJn+b@;@bk$|zv43d3V@ntc_F86T$3u!BNyRl>FV9kJR*h^dRG6)NHq3x} z?|2`wNELn0?BzObO)O7;iI8iwtdmM2966uwhL*fKxRp|RFZQn|^KE!3y~DAVhu9&D zgr)1B^O~hB<{e0%P>({8IWqS(W}v_1H^tcI{kA(J9rnDbRFhj{iJml;V;JeBYZL6# zcdZ;tO`wKyyi}%NIlWXzuBZW9m8R-&@pL7XigKm-q&Ci7f9chg#_JfOn8^-E%|>u dMg-4(xqjya5!ALf1f+hstROH8iaGM}e*vsf$lCw_ diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index f8843d1d902..df6a3637955 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; -export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { +export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) { return { type: TIMELINE_REFRESH_SUCCESS, timeline, statuses, skipLoading, next, + partial, }; }; @@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) { return function (dispatch, getState) { const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); - if (timeline.get('isLoading') || timeline.get('online')) { + if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) { return; } @@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) { dispatch(refreshTimelineRequest(timelineId, skipLoading)); api(getState).get(path, { params }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null)); + if (response.status === 206) { + dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true)); + } else { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false)); + } }).catch(error => { dispatch(refreshTimelineFail(timelineId, error, skipLoading)); }); diff --git a/app/javascript/mastodon/components/missing_indicator.js b/app/javascript/mastodon/components/missing_indicator.js index 87df7f61ce3..70d8c3b9841 100644 --- a/app/javascript/mastodon/components/missing_indicator.js +++ b/app/javascript/mastodon/components/missing_indicator.js @@ -2,9 +2,14 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; const MissingIndicator = () => ( -

+
- +
+ +
+ + +
); diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js index 58a7b228a94..5acaf714ec6 100644 --- a/app/javascript/mastodon/components/status_list.js +++ b/app/javascript/mastodon/components/status_list.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from './scrollable_list'; +import { FormattedMessage } from 'react-intl'; export default class StatusList extends ImmutablePureComponent { @@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: PropTypes.bool, shouldUpdateScroll: PropTypes.func, isLoading: PropTypes.bool, + isPartial: PropTypes.bool, hasMore: PropTypes.bool, prepend: PropTypes.node, emptyMessage: PropTypes.node, @@ -48,8 +50,23 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, ...other } = this.props; - const { isLoading } = other; + const { statusIds, ...other } = this.props; + const { isLoading, isPartial } = other; + + if (isPartial) { + return ( +
+
+
+ +
+ + +
+
+
+ ); + } const scrollableContent = (isLoading || statusIds.size > 0) ? ( statusIds.map((statusId) => ( diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index a4bc60facee..31f5a3c8b2b 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { expandHomeTimeline } from '../../actions/timelines'; +import { expandHomeTimeline, refreshHomeTimeline } from '../../actions/timelines'; import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../../components/column'; @@ -16,6 +16,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, + isPartial: state.getIn(['timelines', 'home', 'isPartial'], false), }); @connect(mapStateToProps) @@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, + isPartial: PropTypes.bool, columnId: PropTypes.string, multiColumn: PropTypes.bool, }; @@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent { this.props.dispatch(expandHomeTimeline()); } + componentDidMount () { + this._checkIfReloadNeeded(false, this.props.isPartial); + } + + componentDidUpdate (prevProps) { + this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial); + } + + componentWillUnmount () { + this._stopPolling(); + } + + _checkIfReloadNeeded (wasPartial, isPartial) { + const { dispatch } = this.props; + + if (wasPartial === isPartial) { + return; + } else if (!wasPartial && isPartial) { + this.polling = setInterval(() => { + dispatch(refreshHomeTimeline()); + }, 3000); + } else if (wasPartial && !isPartial) { + this._stopPolling(); + } + } + + _stopPolling () { + if (this.polling) { + clearInterval(this.polling); + this.polling = null; + } + } + render () { const { intl, hasUnread, columnId, multiColumn } = this.props; const pinned = !!columnId; diff --git a/app/javascript/mastodon/features/list_timeline/index.js b/app/javascript/mastodon/features/list_timeline/index.js index ae136e48f9b..3b97ac62a2f 100644 --- a/app/javascript/mastodon/features/list_timeline/index.js +++ b/app/javascript/mastodon/features/list_timeline/index.js @@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent { if (typeof list === 'undefined') { return ( - +
+ +
); } else if (list === false) { return ( - +
+ +
); } diff --git a/app/javascript/mastodon/features/ui/containers/status_list_container.js b/app/javascript/mastodon/features/ui/containers/status_list_container.js index a0aec440326..59b53d8235d 100644 --- a/app/javascript/mastodon/features/ui/containers/status_list_container.js +++ b/app/javascript/mastodon/features/ui/containers/status_list_container.js @@ -47,6 +47,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { timelineId }) => ({ statusIds: getStatusIds(state, { type: timelineId }), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), + isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: !!state.getIn(['timelines', timelineId, 'next']), }); diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 9984c3b5d6a..7b7b5470fc0 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({ items: ImmutableList(), }); -const normalizeTimeline = (state, timeline, statuses, next) => { +const normalizeTimeline = (state, timeline, statuses, next, isPartial) => { const oldIds = state.getIn([timeline, 'items'], ImmutableList()); const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); const wasLoaded = state.getIn([timeline, 'loaded']); @@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => { mMap.set('isLoading', false); if (!hadNext) mMap.set('next', next); mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids); + mMap.set('isPartial', isPartial); })); }; @@ -124,7 +125,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next); + return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_EXPAND_SUCCESS: return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); case TIMELINE_UPDATE: diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index dbb277af965..6fbecee7c37 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2303,7 +2303,7 @@ } } -.missing-indicator { +.regeneration-indicator { text-align: center; font-size: 16px; font-weight: 500; @@ -2314,11 +2314,46 @@ flex: 1 1 auto; align-items: center; justify-content: center; + padding: 20px; & > div { - background: url('../images/mastodon-not-found.png') no-repeat center -50px; - padding-top: 210px; width: 100%; + background: transparent; + padding-top: 0; + } + + &__figure { + background: url('../images/elephant_ui_working.svg') no-repeat center 0; + width: 100%; + height: 160px; + background-size: contain; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &.missing-indicator { + padding-top: 20px + 48px; + + .regeneration-indicator__figure { + background-image: url('../images/elephant_ui_disappointed.svg'); + } + } + + &__label { + margin-top: 200px; + + strong { + display: block; + margin-bottom: 10px; + color: lighten($ui-base-color, 34%); + } + + span { + font-size: 15px; + font-weight: 400; + } } } @@ -2749,7 +2784,6 @@ @keyframes heartbeat { from { transform: scale(1); - transform-origin: center center; animation-timing-function: ease-out; } @@ -2775,6 +2809,7 @@ } .pulse-loading { + transform-origin: center center; animation: heartbeat 1.5s ease-in-out infinite both; } diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb index 36aabaa001b..4f771ff7233 100644 --- a/app/services/precompute_feed_service.rb +++ b/app/services/precompute_feed_service.rb @@ -3,5 +3,6 @@ class PrecomputeFeedService < BaseService def call(account) FeedManager.instance.populate_feed(account) + Redis.current.del("account:#{account.id}:regeneration") end end diff --git a/app/workers/regeneration_worker.rb b/app/workers/regeneration_worker.rb index 8cee21ae1fb..5c6a040bd58 100644 --- a/app/workers/regeneration_worker.rb +++ b/app/workers/regeneration_worker.rb @@ -3,7 +3,7 @@ class RegenerationWorker include Sidekiq::Worker - sidekiq_options queue: 'pull', backtrace: true, unique: :until_executed + sidekiq_options unique: :until_executed def perform(account_id, _ = :home) account = Account.find(account_id) diff --git a/spec/controllers/concerns/user_tracking_concern_spec.rb b/spec/controllers/concerns/user_tracking_concern_spec.rb index 168d44ba6d9..d08095ef827 100644 --- a/spec/controllers/concerns/user_tracking_concern_spec.rb +++ b/spec/controllers/concerns/user_tracking_concern_spec.rb @@ -43,15 +43,39 @@ describe ApplicationController, type: :controller do expect_updated_sign_in_at(user) end - it 'regenerates feed when sign in is older than two weeks' do - allow(RegenerationWorker).to receive(:perform_async) - user.update(current_sign_in_at: 3.weeks.ago) - sign_in user, scope: :user - get :show + describe 'feed regeneration' do + before do + alice = Fabricate(:account) + bob = Fabricate(:account) - expect_updated_sign_in_at(user) - expect(Redis.current.get("account:#{user.account_id}:regeneration")).to eq 'true' - expect(RegenerationWorker).to have_received(:perform_async) + user.account.follow!(alice) + user.account.follow!(bob) + + Fabricate(:status, account: alice, text: 'hello world') + Fabricate(:status, account: bob, text: 'yes hello') + Fabricate(:status, account: user.account, text: 'test') + + user.update(last_sign_in_at: 'Tue, 04 Jul 2017 14:45:56 UTC +00:00', current_sign_in_at: 'Wed, 05 Jul 2017 22:10:52 UTC +00:00') + + sign_in user, scope: :user + end + + it 'sets a regeneration marker while regenerating' do + allow(RegenerationWorker).to receive(:perform_async) + get :show + + expect_updated_sign_in_at(user) + expect(Redis.current.get("account:#{user.account_id}:regeneration")).to eq 'true' + expect(RegenerationWorker).to have_received(:perform_async) + end + + it 'regenerates feed when sign in is older than two weeks' do + get :show + + expect_updated_sign_in_at(user) + expect(Redis.current.zcard(FeedManager.instance.key(:home, user.account_id))).to eq 3 + expect(Redis.current.get("account:#{user.account_id}:regeneration")).to be_nil + end end def expect_updated_sign_in_at(user) From e56404be414ff2fc7ff11171fdd2b31a0658aa11 Mon Sep 17 00:00:00 2001 From: "Renato \"Lond\" Cerqueira" Date: Thu, 18 Jan 2018 16:12:10 +0100 Subject: [PATCH 3/5] When must_be_following_dm is on, only notify if recipient dm'ed user (#6283) * When must_be_following_dm is on, only notify if recipient dm'ed user Currently, when must_be_following_dm is on, if a user sends a direct message replying to any status from the recipient, the recipient gets a notification. This should not be the case, as if the recipient posted something publicly this can be used to spam their notifications. * Refactor replied_to_status_is_direct_message? Following suggestion in PR --- app/services/notify_service.rb | 2 +- spec/services/notify_service_spec.rb | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index d5960c3adc6..ba086449cc1 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -54,7 +54,7 @@ class NotifyService < BaseService end def response_to_recipient? - @notification.target_status.in_reply_to_account_id == @recipient.id + @notification.target_status.in_reply_to_account_id == @recipient.id && @notification.target_status.thread&.direct_visibility? end def optional_non_following_and_direct? diff --git a/spec/services/notify_service_spec.rb b/spec/services/notify_service_spec.rb index eb6210a1e78..abe557cf382 100644 --- a/spec/services/notify_service_spec.rb +++ b/spec/services/notify_service_spec.rb @@ -62,10 +62,19 @@ RSpec.describe NotifyService do is_expected.to_not change(Notification, :count) end - context 'if the message chain initiated by recipient' do + context 'if the message chain initiated by recipient, but is not direct message' do let(:reply_to) { Fabricate(:status, account: recipient) } let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } + it 'does not notify' do + is_expected.to_not change(Notification, :count) + end + end + + context 'if the message chain initiated by recipient and is direct message' do + let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) } + let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) } + it 'does notify' do is_expected.to change(Notification, :count) end From bcd86404daf90b120eeb339f0cfeee7a6c25dd94 Mon Sep 17 00:00:00 2001 From: David Yip Date: Thu, 18 Jan 2018 10:25:37 -0600 Subject: [PATCH 4/5] Port 7badad7797b487b411a2ab34e0f7413741974bb4 to glitch frontend --- .../flavours/glitch/actions/timelines.js | 13 +++-- .../glitch/components/missing_indicator.js | 9 +++- .../flavours/glitch/components/status_list.js | 21 +++++++- .../glitch/features/home_timeline/index.js | 37 ++++++++++++- .../glitch/features/list_timeline/index.js | 8 ++- .../ui/containers/status_list_container.js | 1 + .../images/elephant_ui_disappointed.svg | 1 + .../glitch/images/elephant_ui_working.svg | 1 + .../flavours/glitch/reducers/timelines.js | 5 +- .../glitch/styles/components/index.scss | 18 ++----- .../components/regeneration_indicator.scss | 53 +++++++++++++++++++ 11 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg create mode 100644 app/javascript/flavours/glitch/images/elephant_ui_working.svg create mode 100644 app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 9a5b2e6da91..3a9d64084cd 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE'; -export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) { +export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) { return { type: TIMELINE_REFRESH_SUCCESS, timeline, statuses, skipLoading, next, + partial, }; }; @@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) { return function (dispatch, getState) { const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); - if (timeline.get('isLoading') || timeline.get('online')) { + if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) { return; } @@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) { dispatch(refreshTimelineRequest(timelineId, skipLoading)); api(getState).get(path, { params }).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null)); + if (response.status === 206) { + dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true)); + } else { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false)); + } }).catch(error => { dispatch(refreshTimelineFail(timelineId, error, skipLoading)); }); diff --git a/app/javascript/flavours/glitch/components/missing_indicator.js b/app/javascript/flavours/glitch/components/missing_indicator.js index 87df7f61ce3..70d8c3b9841 100644 --- a/app/javascript/flavours/glitch/components/missing_indicator.js +++ b/app/javascript/flavours/glitch/components/missing_indicator.js @@ -2,9 +2,14 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; const MissingIndicator = () => ( -
+
- +
+ +
+ + +
); diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js index f190ba6ab61..f253f0fdc12 100644 --- a/app/javascript/flavours/glitch/components/status_list.js +++ b/app/javascript/flavours/glitch/components/status_list.js @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'; import StatusContainer from 'flavours/glitch/containers/status_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ScrollableList from './scrollable_list'; +import { FormattedMessage } from 'react-intl'; export default class StatusList extends ImmutablePureComponent { @@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent { trackScroll: PropTypes.bool, shouldUpdateScroll: PropTypes.func, isLoading: PropTypes.bool, + isPartial: PropTypes.bool, hasMore: PropTypes.bool, prepend: PropTypes.node, emptyMessage: PropTypes.node, @@ -48,8 +50,23 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, ...other } = this.props; - const { isLoading } = other; + const { statusIds, ...other } = this.props; + const { isLoading, isPartial } = other; + + if (isPartial) { + return ( +
+
+
+ +
+ + +
+
+
+ ); + } const scrollableContent = (isLoading || statusIds.size > 0) ? ( statusIds.map((statusId) => ( diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js index 2dfec6bbed3..c20c0244a68 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.js +++ b/app/javascript/flavours/glitch/features/home_timeline/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { expandHomeTimeline } from 'flavours/glitch/actions/timelines'; +import { expandHomeTimeline, refreshHomeTimeline } from 'flavours/glitch/actions/timelines'; import PropTypes from 'prop-types'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; import Column from 'flavours/glitch/components/column'; @@ -16,6 +16,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, + isPartial: state.getIn(['timelines', 'home', 'isPartial'], false), }); @connect(mapStateToProps) @@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent { dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, + isPartial: PropTypes.bool, columnId: PropTypes.string, multiColumn: PropTypes.bool, }; @@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent { this.props.dispatch(expandHomeTimeline()); } + componentDidMount () { + this._checkIfReloadNeeded(false, this.props.isPartial); + } + + componentDidUpdate (prevProps) { + this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial); + } + + componentWillUnmount () { + this._stopPolling(); + } + + _checkIfReloadNeeded (wasPartial, isPartial) { + const { dispatch } = this.props; + + if (wasPartial === isPartial) { + return; + } else if (!wasPartial && isPartial) { + this.polling = setInterval(() => { + dispatch(refreshHomeTimeline()); + }, 3000); + } else if (wasPartial && !isPartial) { + this._stopPolling(); + } + } + + _stopPolling () { + if (this.polling) { + clearInterval(this.polling); + this.polling = null; + } + } + render () { const { intl, hasUnread, columnId, multiColumn } = this.props; const pinned = !!columnId; diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.js b/app/javascript/flavours/glitch/features/list_timeline/index.js index c6a89a92022..f9476d92d54 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.js +++ b/app/javascript/flavours/glitch/features/list_timeline/index.js @@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent { if (typeof list === 'undefined') { return ( - +
+ +
); } else if (list === false) { return ( - +
+ +
); } diff --git a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js index eca85b8e636..f85a2eeb8aa 100644 --- a/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js +++ b/app/javascript/flavours/glitch/features/ui/containers/status_list_container.js @@ -51,6 +51,7 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { timelineId }) => ({ statusIds: getStatusIds(state, { type: timelineId }), isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true), + isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false), hasMore: !!state.getIn(['timelines', timelineId, 'next']), }); diff --git a/app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg b/app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg new file mode 100644 index 00000000000..580c15a1388 --- /dev/null +++ b/app/javascript/flavours/glitch/images/elephant_ui_disappointed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/flavours/glitch/images/elephant_ui_working.svg b/app/javascript/flavours/glitch/images/elephant_ui_working.svg new file mode 100644 index 00000000000..8ba475db0a0 --- /dev/null +++ b/app/javascript/flavours/glitch/images/elephant_ui_working.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index 679e1601eb2..4c62d8df39b 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({ items: ImmutableList(), }); -const normalizeTimeline = (state, timeline, statuses, next) => { +const normalizeTimeline = (state, timeline, statuses, next, isPartial) => { const oldIds = state.getIn([timeline, 'items'], ImmutableList()); const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); const wasLoaded = state.getIn([timeline, 'loaded']); @@ -41,6 +41,7 @@ const normalizeTimeline = (state, timeline, statuses, next) => { mMap.set('isLoading', false); if (!hadNext) mMap.set('next', next); mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids); + mMap.set('isPartial', isPartial); })); }; @@ -125,7 +126,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next); + return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_EXPAND_SUCCESS: return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); case TIMELINE_UPDATE: diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 8f051f7a054..8f06209c618 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -838,21 +838,10 @@ } .missing-indicator { - text-align: center; - font-size: 16px; - font-weight: 500; - color: lighten($ui-base-color, 16%); - background: $ui-base-color; - cursor: default; - display: flex; - flex: 1 1 auto; - align-items: center; - justify-content: center; + padding-top: 20px + 48px; - & > div { - background: url('~images/mastodon-not-found.png') no-repeat center -50px; - padding-top: 210px; - width: 100%; + .regeneration-indicator__figure { + background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg'); } } @@ -1162,6 +1151,7 @@ noscript { @import 'metadata'; @import 'composer'; @import 'columns'; +@import 'regeneration_indicator'; @import 'search'; @import 'emoji'; @import 'doodle'; diff --git a/app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss b/app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss new file mode 100644 index 00000000000..9c1873cdffc --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/regeneration_indicator.scss @@ -0,0 +1,53 @@ +.regeneration-indicator { + text-align: center; + font-size: 16px; + font-weight: 500; + color: lighten($ui-base-color, 16%); + background: $ui-base-color; + cursor: default; + display: flex; + flex: 1 1 auto; + align-items: center; + justify-content: center; + padding: 20px; + + & > div { + width: 100%; + background: transparent; + padding-top: 0; + } + + &__figure { + background: url('~flavours/glitch/images/elephant_ui_working.svg') no-repeat center 0; + width: 100%; + height: 160px; + background-size: contain; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + &.missing-indicator { + padding-top: 20px + 48px; + + .regeneration-indicator__figure { + background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg'); + } + } + + &__label { + margin-top: 200px; + + strong { + display: block; + margin-bottom: 10px; + color: lighten($ui-base-color, 34%); + } + + span { + font-size: 15px; + font-weight: 400; + } + } +} From 3896cd5d79d8b46473b935b00caf31ef943fb21d Mon Sep 17 00:00:00 2001 From: David Yip Date: Thu, 18 Jan 2018 10:25:54 -0600 Subject: [PATCH 5/5] Use absolute paths for new working/not-found SVGs This allows these component styles to be used in i.e. the win95 skin. --- app/javascript/styles/mastodon/components.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 4ac0deea1cc..d13a18ad7e9 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2323,7 +2323,7 @@ } &__figure { - background: url('../images/elephant_ui_working.svg') no-repeat center 0; + background: url('~images/elephant_ui_working.svg') no-repeat center 0; width: 100%; height: 160px; background-size: contain; @@ -2337,7 +2337,7 @@ padding-top: 20px + 48px; .regeneration-indicator__figure { - background-image: url('../images/elephant_ui_disappointed.svg'); + background-image: url('~images/elephant_ui_disappointed.svg'); } }