Compare commits

...

115 Commits

Author SHA1 Message Date
Eugen Rochko
b4f8e87358 Add LDAP options to .env.production.sample (#6592) 2018-03-02 08:14:34 +01:00
Eugen Rochko
e72db6d9dd Move "compose" on mobile to floating action button (#6594)
* Move "compose" on mobile to floating action button

* Fix contrast on floating action button
2018-03-02 07:12:40 +01:00
Eugen Rochko
036dd98abb Responsively enforce 16:9 ratio on all media thumbnails in web UI (#6590)
* Responsively enforce 16:9 ratio on all media thumbnails in web UI

Also change video player behaviour to "contain" rather than
"cover" videos that don't fit the ratio, unlike images and GIFs,
it's expected that a video is shown fully.

* Fix spacing issues and remove floor

* Remove floor
2018-03-02 07:00:04 +01:00
Eugen Rochko
7901f9f63e When search enabled, display hint in search popout (#6593)
* When advanced search is enabled, show different hint in search popout

* Change "getting started" icon in tabs bar from asterisk to hamburger
2018-03-02 06:02:42 +01:00
Yamagishi Kazutoshi
0963b6fd22 Weblate translations (2018-03-02) (#6588)
* Translated using Weblate (Galician)

Currently translated at 100.0% (57 of 57 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/gl/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (57 of 57 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/nl/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (57 of 57 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sv/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (57 of 57 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ca/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (57 of 57 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (75 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (559 of 559 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/hu/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/hu/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/nl/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (75 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/

* Translated using Weblate (Arabic)

Currently translated at 98.2% (55 of 56 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/

* Translated using Weblate (Slovak)

Currently translated at 61.1% (342 of 559 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sv/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sv/

* Translated using Weblate (Portuguese)

Currently translated at 96.4% (54 of 56 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/pt/

* Translated using Weblate (Portuguese)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/pt/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (558 of 559 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt_BR/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/

* Translated using Weblate (Catalan)

Currently translated at 99.8% (558 of 559 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* 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 (Japanese)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Arabic)

Currently translated at 98.2% (55 of 56 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/

* Translated using Weblate (Arabic)

Currently translated at 79.0% (49 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/

* Translated using Weblate (Arabic)

Currently translated at 52.9% (296 of 559 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (Slovak)

Currently translated at 61.1% (342 of 559 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/nl/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (57 of 57 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/sk/

* Translated using Weblate (Galician)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/

* Translated using Weblate (Galician)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/gl/

* Translated using Weblate (Galician)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/gl/

* Translated using Weblate (Japanese)

Currently translated at 99.2% (561 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Slovak)

Currently translated at 60.5% (342 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Polish)

Currently translated at 99.1% (560 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 62.4% (353 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Swedish)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sv/

* Translated using Weblate (Japanese)

Currently translated at 99.2% (561 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ja/

* Translated using Weblate (Catalan)

Currently translated at 99.1% (560 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Finnish)

Currently translated at 99.6% (263 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Slovak)

Currently translated at 62.6% (354 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (French)

Currently translated at 99.1% (560 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (Japanese)

Currently translated at 93.1% (54 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ja/

* Translated using Weblate (Galician)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/gl/

* Translated using Weblate (Polish)

Currently translated at 99.2% (561 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/

* Translated using Weblate (Polish)

Currently translated at 99.8% (564 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pl/

* Translated using Weblate (Polish)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/pl/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Slovak)

Currently translated at 72.5% (45 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sk/

* Translated using Weblate (Finnish)

Currently translated at 99.6% (263 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ca/

* Translated using Weblate (Catalan)

Currently translated at 99.8% (564 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Dutch)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/nl/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ca/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Translated using Weblate (Slovak)

Currently translated at 62.8% (355 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Arabic)

Currently translated at 99.6% (263 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (Polish)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/pl/

* Translated using Weblate (Arabic)

Currently translated at 96.0% (72 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/

* Translated using Weblate (Slovak)

Currently translated at 93.5% (58 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sk/

* Translated using Weblate (Arabic)

Currently translated at 98.2% (57 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/

* Translated using Weblate (German)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/de/

* Translated using Weblate (Catalan)

Currently translated at 99.8% (564 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Finnish)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fi/

* Translated using Weblate (German)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/de/

* Translated using Weblate (French)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/fr/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/sk/

* Translated using Weblate (Slovak)

Currently translated at 63.7% (360 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/

* Translated using Weblate (Slovak)

Currently translated at 64.9% (367 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/ar/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (2 of 2 strings)

Translation: Mastodon/Activerecord
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/activerecord/ar/

* Translated using Weblate (Arabic)

Currently translated at 98.6% (74 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/

* Translated using Weblate (Catalan)

Currently translated at 99.8% (564 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ca/

* Translated using Weblate (Catalan)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ca/

* Translated using Weblate (Slovak)

Currently translated at 69.5% (393 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Japanese)

Currently translated at 99.2% (561 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ja/

* Translated using Weblate (Arabic)

Currently translated at 88.7% (55 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/

* Translated using Weblate (Arabic)

Currently translated at 92.0% (69 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/ar/

* Translated using Weblate (Arabic)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/ar/

* Translated using Weblate (Slovak)

Currently translated at 70.6% (399 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Slovak)

Currently translated at 74.1% (419 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Arabic)

Currently translated at 67.6% (382 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/ar/

* Translated using Weblate (French)

Currently translated at 99.4% (562 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/fr/

* Translated using Weblate (Portuguese)

Currently translated at 95.3% (539 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/pt/

* Translated using Weblate (Slovak)

Currently translated at 75.3% (426 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Croatian)

Currently translated at 58.0% (36 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/hr/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/hu/

* Translated using Weblate (Esperanto)

Currently translated at 58.6% (34 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Occitan)

Currently translated at 96.8% (547 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/oc/

* Translated using Weblate (Slovak)

Currently translated at 76.2% (431 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/sk/

* Translated using Weblate (French)

Currently translated at 99.6% (263 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/fr/

* Translated using Weblate (Arabic)

Currently translated at 93.5% (58 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/ar/

* Translated using Weblate (Slovak)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/sk/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 77.8% (440 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (58 of 58 strings)

Translation: Mastodon/Preferences
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/simple_form/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (75 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (75 of 75 strings)

Translation: Mastodon/Doorkeeper
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/doorkeeper/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (565 of 565 strings)

Translation: Mastodon/Backend
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/backend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (264 of 264 strings)

Translation: Mastodon/React
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/frontend/eo/

* Translated using Weblate (Esperanto)

Currently translated at 100.0% (62 of 62 strings)

Translation: Mastodon/Devise
Translate-URL: https://weblate.joinmastodon.org/projects/mastodon/devise/eo/
2018-03-02 04:36:16 +01:00
Eugen Rochko
379cdfaac5 Fix #6586: Add close modal icon to report dialog (#6591) 2018-03-02 04:36:00 +01:00
Eugen Rochko
38b9af76a2 Improve style of web UI account tabs (#6589) 2018-03-02 04:35:49 +01:00
Patrick Figel
e4db0f28d2 Update omniauth-saml to 1.10 (#6587)
Fixes CVE-2017-11428
2018-03-02 02:32:08 +01:00
mayaeh
e7d741ece3 i18n: Update Japanese translations (#6581)
* yarn manage:translations

* Update Japanese translations.
2018-03-02 07:48:17 +09:00
Thomas Leister
ecd36c1ede Fixes #6584 (#6585) 2018-03-01 23:30:06 +01:00
Eugen Rochko
64f2ada5d4 Bump version to 2.3.0rc1 2018-03-01 20:50:23 +01:00
Eugen Rochko
a3c4138197 Add contact_account and languages to instance API (#6574) 2018-03-01 20:48:11 +01:00
Marcin Mikołajczak
51b7a22ea7 i18n: Update Polish translation (#6578)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-03-01 18:26:51 +09:00
Eugen Rochko
68218d97c8 Add only_media param to public and hashtag timelines API (#6576) 2018-03-01 03:21:21 +01:00
Eugen Rochko
5131012505 Add "Toots/Toots with replies/Media" tab below profile header (#6572)
* Add "Toots/Toots with replies/Media" tab below profile header

* Add focal point display to account gallery timeline

* Fix visual glitch of standalone GIFV
2018-03-01 02:48:44 +01:00
Eugen Rochko
473a69ab18 Fix margin on top action button, fix width of counters on frontpage (#6573) 2018-03-01 02:48:08 +01:00
Eugen Rochko
fce8464077 Ensure that boolean params in the API are parsed for truthiness (#6575)
Use Rails smart boolean cast to account for values such as "f",
"0", "false", etc. Previously, if a param was present in the request,
it would count as true.
2018-03-01 02:47:59 +01:00
Eugen Rochko
47bdb9b33b Fix #942: Seamless LDAP login (#6556) 2018-02-28 19:04:53 +01:00
Eugen Rochko
e852872846 Fix #5708: Reject->Follow will remove the follow if it exists (#6571) 2018-02-28 06:55:06 +01:00
Eugen Rochko
41a01bec23 Federated reports (#6570)
* Fix #2176: Federated reports

* UI for federated reports

* Add spec for ActivityPub Flag handler

* Add spec for ReportService
2018-02-28 06:54:55 +01:00
beatrix
4072b68686 remove Uglifier call from production.rb (#6568) 2018-02-27 05:48:11 +01:00
TrashMacNugget
6f5f434caa Specify AGPLv3+ (#6546)
* Specify AGPLv3+

Since the documentation doesn't specify you can use Mastodon as AGPLv3 or any later version.

* Use newest version of SPDX AGPLv3+ identifier
2018-02-27 01:52:27 +01:00
Paul Woolcock
76198c63b6 Some images can cause convert to fail, which crashes this whole task (#6565)
* Some images can cause `convert` to fail, which crashes this whole task

* Add more specific exception
2018-02-26 22:01:49 +01:00
Lynx Kotoura
7150f2e9d3 Grid layout for tag pages (#6545)
* Use grid layout for the landing page

* Use grid layout for tag pages

* Set 2 columns width as explicit percentage for tag pages
2018-02-26 17:43:45 +01:00
Marcin Mikołajczak
3a6ace4874 Add Liberapay link to README.md (#6563)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-02-26 16:20:47 +01:00
masarakki
22a441e374 remove-uglifier (#6561) 2018-02-26 16:19:48 +01:00
Lynx Kotoura
a40167cf4d Better grid layout for the landing page (#6543)
* Use grid layout for the landing page

* Fix column settings

Set the ratio explicitly

* Improve information board
2018-02-26 16:19:07 +01:00
Eugen Rochko
18513a978a Improve public account cards (#6559)
- Add follow/unfollow/remote follow buttons
- Format the bio properly
- Always show username@domain, even for local accounts
2018-02-26 16:18:41 +01:00
Ian McCowan
c33931b613 Fix prev/next links on public profile page (#6497)
* Fix prev/next links on public profile page

* Don't make pagination urls if no available statuses

* Fix empty check method

* Put left chevron before prev page link

* Add scope for pagination "starting at" a given id

* Status pagination try 2:

s/prev/older and s/next/newer
"older" on left, "newer" on right
Use new scope for "newer" link
Extract magic 20 page size to constant
Remove max_id from feed pagination as it's not respected

* Reinstate max_id for accounts atom stream

* normalize
2018-02-26 03:31:28 +01:00
Eugen Rochko
5cc716688a Ensure the app does not even start if OTP_SECRET is not set (#6557)
* Ensure the app does not even start if OTP_SECRET is not set

* Remove PAPERCLIP_SECRET (it's not used by anything, actually)

Imports are for internal consumption and the url option isn't even
used correctly, so we can remove the hash stuff from them
2018-02-26 01:31:44 +01:00
Eugen Rochko
f0a1b1a152 Fix #6536 (#6558) 2018-02-26 00:24:55 +01:00
Akihiko Odaki
2e8a492e88 Raise Mastodon::HostValidationError when host for HTTP request is private (#6410) 2018-02-24 19:16:11 +01:00
Akihiko Odaki
7cb49eaa3a Do not use function name to track components (#6542)
UglifyJS2 is allowed to mangle function names, and function names can also
be duplicate if they are from different scopes. Therefore function names
are not reliable as identifiers.

Functions as keys for Map object is a cheaper and more reliable
alternative.
2018-02-24 19:10:57 +01:00
mayaeh
4d8c0d9959 i18n: Update Japanese translations (#6550)
* Update Japanese translations.

* Add quotation marks.
2018-02-25 01:27:03 +09:00
Akihiko Odaki
f8f0572ee0 Do not push status to feed if its reblog is already inserted (#6488)
A complemental change for precompute_feed_service_spec.rb also fixes its
random failure which is caused by the Snowlake randomization of the order
of an original status and its reblog.
2018-02-24 05:40:18 +01:00
Ghislain Loaec
e668180044 New variable OAUTH_REDIRECT_AT_SIGN_IN + Ref #6538 (not only SAML strategies) (#6540) 2018-02-23 01:16:17 +01:00
Alexander
8fa924e372 Update pam documentation (#6518)
* document pam email extraction

* remove superfluous newline
2018-02-22 23:41:21 +01:00
Marcin Mikołajczak
3e46f12340 i18n: Update Polish translation (#6539)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-02-22 23:31:41 +01:00
Ghislain Loaec
3084fe4959 New env variable: SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED + fixes #6533 (#6538) 2018-02-22 23:31:25 +01:00
Eugen Rochko
b8535ad4df Fix nil error in focal_point? (#6537) 2018-02-22 17:42:33 +01:00
Eugen Rochko
5f3bee345d Fix container regression (#6531)
* Update public.html.haml

* Update auth.html.haml

* Update modal.html.haml
2018-02-22 03:04:27 +01:00
beatrix
755aad534a fix typo in image serializer (#6530)
respond to!
2018-02-22 02:47:17 +01:00
Eugen Rochko
c71aa468b5 Redesign landing page (again) (#6486)
* Redesign landing page (again)

* Move login form in small version to the right column

* Display closed registrations message

* Add site setting for the hero image

* Fix test

* Increase spacing, maximum width, change call to action section
2018-02-22 01:03:48 +01:00
Eugen Rochko
d8bc64bb09 Fix #6526: Only store redirect location if not in JSON format (#6528) 2018-02-22 00:51:30 +01:00
Eugen Rochko
90f12f2e5a Focal points (#6520)
* Add focus param to media API, center thumbnails on focus point

* Add UI for setting a focal point

* Improve focal point icon on upload item

* Use focal point in upload preview

* Add focalPoint property to ActivityPub

* Don't show focal point button for non-image attachments
2018-02-22 00:35:46 +01:00
Eugen Rochko
d3a62d2637 Fix #6525: Make sure file is opened in LazyThumbnail processor (#6529) 2018-02-22 00:28:19 +01:00
Eugen Rochko
4bc625166e Fix bug in relationships API introduced by #6482 (#6527)
It was merge when it needed to be deep_merge. And added some tests
2018-02-21 23:22:12 +01:00
Eugen Rochko
61ed133fea Account archive download (#6460)
* Fix #201: Account archive download

* Export actor and private key in the archive

* Optimize BackupService

- Add conversation to cached associations of status, because
  somehow it was forgotten and is source of N+1 queries
- Explicitly call GC between batches of records being fetched
  (Model class allocations are the worst offender)
- Stream media files into the tar in 1MB chunks
  (Do not allocate media file (up to 8MB) as string into memory)
- Use #bytesize instead of #size to calculate file size for JSON
  (Fix FileOverflow error)
- Segment media into subfolders by status ID because apparently
  GIF-to-MP4 media are all named "media.mp4" for some reason

* Keep uniquely generated filename in Paperclip::GifTranscoder

* Ensure dumped files do not overwrite each other by maintaing directory partitions

* Give tar archives a good name

* Add scheduler to remove week-old backups

* Fix code style issue
2018-02-21 23:21:32 +01:00
りんすき
c1e77b56a9 fix #6523 (#6524) 2018-02-21 19:33:23 +01:00
takayamaki
f69d7cb43b fix purge_removed_accounts task should suspend account before delete it (#6521) 2018-02-21 16:30:46 +01:00
Eugen Rochko
a7171af0a3 Fix avatar and header issues by using custom geometry detector (#6515)
* Fix avatar and header issues by using custom geometry detector

Revert a part of #6508. The file passed to dynamic styles method
was not actually a file, but an instance of Paperclip::Attachment,
which broke all styles by always returning {} from the method.

One problem with GIF avatars was that Paperclip::GeometryDetector
reported wrong dimensions for them, e.g. 120x120 GIF avatar would
for some reason be detected as 120x53. By writing our own geometry
parser, we can use FastImage, which also happens to be faster than
ImageMagick, to detect image dimensions, which are also correct.

Unfortunately, this PR does not implement skipping a `convert`
entirely if the dimensions are already correct, as I found no easy
way to write that behaviour into Paperclip without rewriting the
Paperclip::Thumbnail class.

* Only invoke convert if dimension or format needs to be changed
2018-02-21 03:40:12 +01:00
Moritz Heiber
a4fd4ad1d5 Fix build error for missing variable interpolation in chown instruction (#6519) 2018-02-20 19:11:36 +01:00
Eugen Rochko
02856073f7 Fix #6509: Use pull queue for chewy jobs (#6513) 2018-02-20 17:25:16 +01:00
Eugen Rochko
be9bab171d Set Docker permissions during the build process (#6514)
* Set Docker permissions during the build process

* Remove docker_entrypoint.sh and use COPY with chown
2018-02-20 17:25:01 +01:00
abcang
7124881273 Improve performance of feed_manager_spec (#6517) 2018-02-20 16:50:12 +01:00
Eugen Rochko
66105929e0 Don't resize avatars/headers if their dimensions are already right (#6508)
Also don't apply "-quality 80" option which is probably the reason
for slight color differences between original and remote image
(because it would apply it twice, once on original instance, and
again on the receiving instance)
2018-02-19 16:06:12 +01:00
Eugen Rochko
cbb69d41f6 Fix media spoiler design (#6507)
- 4px rounded corners on media attachments
- Better colors/contrast for CW/media spoiler on public pages
- Fix vertical alignment of "Show more" button
- Fix layout jump when unhiding standalone media
2018-02-19 02:39:18 +01:00
Konrad Pozniak
bb26cdda24 add parameter locked to /api/v1/update_credentials (#6506) 2018-02-18 22:57:53 +01:00
Kazushige Tominaga
78936461d7 Added fetch_remote_status_service call spec case actibitypub (#6500)
* Added #link_header spec

* Added #call spec

* Delete spec of private methods

* Added call test case activitypub
2018-02-18 16:34:03 +01:00
HellPie
bc6751ecce Remove outline from body window (Fixes #6501) (#6502) 2018-02-18 16:32:58 +01:00
Akihiko Odaki
51869f2a8c Remove unnecessary g++ configuration (#6499) 2018-02-18 16:32:17 +01:00
Eugen Rochko
cba2897108 Cache relationships in API (#6482)
* Cache relationships in API

* Fetch relationships for search results in UI

* Only save one account's maps in each cache item
2018-02-18 03:14:46 +01:00
Akihiko Odaki
9b8a448477 Isolate each specs for cache store (#6450)
The cache store is explicitly used by some specs, but they were not
isolated and therefore not reliable. This fixes the issue by clearing
the cache after each specs.
2018-02-17 22:35:05 +01:00
Eugen Rochko
a71af98401 Push discovered status through streaming API within a time window (#6484)
Time window of 6 hours
2018-02-17 14:28:48 +01:00
Akihiko Odaki
a7c50c7aba Limit the languages used for notification mailer test (#6487)
Some available languages lack translations for notification mails. Now it
tests for two languages which is certain to have required translations:
German and English.

German is the language the current project owner, Eugen Rochko speaks, and
providing English translations for new messages is de facto mandatory.
2018-02-17 14:27:51 +01:00
Simó Albert i Beltran
c770b503c0 Fix Spanish translation of remote_follow acct (#6475) 2018-02-17 03:53:38 +09:00
Simó Albert i Beltran
ffdf0f2ff6 Fix Catalan translation of remote_follow acct (#6476) 2018-02-16 19:15:56 +01:00
Daniel Hunsaker
eb3262b941 [Nanobox] Fix backups for file storage (#6483) 2018-02-16 17:10:01 +01:00
Eugen Rochko
9dbae6e8a1 Save video metadata and improve video OpenGraph tags (#6481)
* Save metadata from video attachments, put correct dimensions into OG tags

* Add twitter:player for videos

* Fix code style and test
2018-02-16 07:22:20 +01:00
Eugen Rochko
1122579216 Do not hide NSFW media/CW'd text in OpenGraph tags (#6479)
Reasoning: HTML title tag affects everyone. But OpenGraph only affects
when somebody is deliberately sharing the content, usually in an
environment where such content is expected. Hiding the content in
OpenGraph tags results in deceitful previews which inhibit the
shareability of the post.

Example: Somebody writes a clever post about politics but kindly
puts a "uspol" content warning on it. Mastodon users are thankful,
but sharing this post on another platform results in non-Mastodon
users believing the entire contents of the post is "uspol" and not
clicking through/reading and re-sharing.
2018-02-16 04:40:53 +01:00
Eugen Rochko
478ca39e5e After click to embed video, autoplay it (#6480) 2018-02-15 23:05:12 +01:00
Eugen Rochko
f7765acf9d Fix #5173: Click card to embed external content (#6471) 2018-02-15 07:04:28 +01:00
abcang
ecdac9017e Fix media button type (#6478) 2018-02-15 04:40:42 +01:00
Marcin Mikołajczak
ba8ec4eed6 i18n: Update Polish translation (#6470)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-02-14 07:55:45 +09:00
Daniel King
6ef3874b2e Fix URLs incorrectly having trailing hyphen removed (#6465)
In cases where a URL has a trailing hyphen the FetchLinkCardService incorrectly removes the hyphen when it is parsed

The hyphen is not a reserved character in the URI spec https://tools.ietf.org/html/rfc3986#section-2.2
2018-02-11 23:49:18 +01:00
Eugen Rochko
e20700fe8f Fix Chewy trying to update index with the wrong strategy (#6464) 2018-02-11 22:59:44 +01:00
Eugen Rochko
cf36d184f4 Interactive rake mastodon:setup task (#6451)
* Add better CLI prompt

* Add rake mastodon:setup interactive wizard

* Test db/redis/smtp configurations and add admin user at the end

* Test database connection even when database does not exist yet
2018-02-11 18:40:57 +01:00
Kazushige Tominaga
718802a05d Added FetchRemoteAccountService spec (#6456)
* Added #link_header spec

* Added #call spec

* Delete spec of private methods

* Added #call spec
2018-02-10 17:10:57 +01:00
ThibG
411c9ecb4b Fix password recovery (#6459)
* Fix password recovery

* Use “resource” instead of “current_user”
2018-02-10 17:09:44 +01:00
Kazushige Tominaga
cbe8743e47 Added #call spec (#6455)
* Added #link_header spec

* Added #call spec

* Delete spec of private methods
2018-02-10 03:31:38 +01:00
Eugen Rochko
3ebc0ad4d3 Full-text search for authorized statuses (#6423)
* Add full-text search for authorized statuses

- Search API will return statuses that match the query
- Only for logged in users
- Only if you are author of the status,
- Or you were mentioned in it
- Or you favourited or reblogged it
- Configuration over `ES_ENABLED`, `ES_HOST`, `ES_PORT`, `ES_PREFIX`
- Run `rails chewy:deploy` to create & populate index

Fix #5880
Fix #4293
Fix #1152

* Add commented out docker-compose configuration for ES container

* Optimize index import, filter search results

* Add basic normalization to the index

* Add better stemming and normalization to the index

* Skip webfinger request if search query includes both @ and a space

* Fix code style

* Visually separate search result sections

* Fix code style issues
2018-02-09 23:04:47 +01:00
masarakki
235c14c79d fix-indent (#6453) 2018-02-09 15:29:48 +01:00
Eugen Rochko
2ef9d0e101 Change web UI "posts" to "toots" on profile for consistency (#6447) 2018-02-09 00:27:18 +01:00
Eugen Rochko
76f3d5d16b Add preference to always display sensitive media (#6448) 2018-02-09 00:26:57 +01:00
Kazushige Tominaga
1167c6dbf8 Perform request spec (#6446)
* Added #link_header spec

* Added #perform_request spec
2018-02-09 08:12:35 +09:00
abcang
298c81c00f Clear account cache of notification target_status (#6442) 2018-02-08 15:33:23 +01:00
abcang
cf32f7da5c Fix response of signature_verification_failure_reason (#6441) 2018-02-08 05:00:45 +01:00
Kazushige Tominaga
2bb393684b Added #link_header spec (#6439) 2018-02-08 08:17:53 +09:00
Akihiko Odaki
67f7ffa792 Change user_id column non-nullable (#6435) 2018-02-07 16:35:44 +01:00
Daniel King
95c8232109 match hashtag regex in js client with server (#6431)
the slight mismatch in hashtag regex between js and ruby was causing
hashtag warning to be displayed for unlisted tweets when an invalid
hashtag was entered

exact version of ruby regex not possible in js as POSIX bracket
expressions are not supported, this version approximates and doesn't
give same unicode support
2018-02-05 02:44:13 +01:00
Eugen Rochko
38e0133e1b Make PAM gem optional, allow configuration over environment (#6415) 2018-02-04 15:05:53 +01:00
abcang
9b6223f5e2 Validation of count works even when text of status is nil (#6429) 2018-02-04 12:32:41 +01:00
abcang
3f35d43222 Exclude nil from relationships array (#6427) 2018-02-04 12:32:10 +01:00
abcang
c156a83e7d Make sure status is not nil (#6428) 2018-02-04 12:31:46 +01:00
Daniel King
258dcb849f Upgrade Vagrant box to Xenial (#6421)
* upgrade vagrant box to xenial

this allows the redis version to be upgraded to support the new redis
features used in the activity tracker

* add libpam0g package to vagrant box

this is required for native extensions of gems to build after the
addition of PAM support was added in #5303
2018-02-04 06:03:01 +01:00
Renato "Lond" Cerqueira
4e4f1b0dcb Add option to show only local toots in timeline preview (#6292)
* Add option to show only local toots in timeline preview
Right know, toots from all the known fediverse are shown in the main
page of an instance. That however doesn't reflect the instance itself.
With this option the admin may choose to display only local toots so
that users checking the instance get a better idea of internal
conversations.

* Fix issues pointed by codeclimate and eslint

* Add default message for community timeline

* Update pl.yml
2018-02-04 06:00:10 +01:00
Eugen Rochko
26f21fd5a0 CAS + SAML authentication feature (#6425)
* Cas authentication feature

* Config

* Remove class_eval + Omniauth initializer

* Codeclimate review

* Codeclimate review 2

* Codeclimate review 3

* Remove uid/email reconciliation

* SAML authentication

* Clean up code

* Improve login form

* Fix code style issues

* Add locales
2018-02-04 05:42:13 +01:00
Akihiko Odaki
9da81a1639 Isolate internal services from external networks in Docker configuration (#6369)
The database and Redis do not need external connections, so isolate them
and prevent unauthorized access.
2018-02-03 18:44:22 +01:00
takayamaki
d75d2a9f99 fix ColumnBackButtonSlim should extended from ColumnBackButton (#6417) 2018-02-03 18:41:51 +01:00
Akihiko Odaki
f7bf36d8fc Require environment for generate_static_pages (#6420)
It is required for ApplicationController.
2018-02-03 18:41:01 +01:00
abcang
33f56811e3 Fix column header button (#6411) 2018-02-02 13:31:28 +01:00
abcang
7e5c433dfc Fix saving of oEmbed image (#6409) 2018-02-02 11:57:59 +01:00
Akihiko Odaki
c1efe0aa1d Set minimum height for mastodon on drawer (#6142) 2018-02-02 11:56:50 +01:00
ThibG
ac1093256c Allow HTTP caching of atom-rendered public toots (OStatus compatibility) (#6207) 2018-02-02 10:54:04 +01:00
Charlotte Fields
af40824998 moved save button (#3792)
* moved save button

* added save back to the bottom

* Update show.html.haml
2018-02-02 10:45:43 +01:00
Akihiko Odaki
77dd9e7d27 Remove wave from list drawer (#6381) 2018-02-02 10:32:41 +01:00
Akihiko Odaki
5da5c65db8 Unify links container implementation in about pages (#6382)
They were redundant, and also had a inconsistency; the button for
"other instances" had an icon for the external link in "more" page, but
it didn't in the other pages.

This unifies the implementation, and the external link icon is now shown
in all the about pages.
2018-02-02 10:32:21 +01:00
Akihiko Odaki
0be9a1e321 Accept ActivityPub announce from the author of the original note (#6236) 2018-02-02 10:22:15 +01:00
puckipedia
8e4cf6282b Allow retrieval of private statuses (single or in outbox) using HTTP signatures (#6225) 2018-02-02 10:19:59 +01:00
Alexander
04fef7b888 pam authentication (#5303)
* add pam support, without extra column

* bugfixes for pam login

* document options

* fix code style

* fix codestyle

* fix tests

* don't call remember_me without password

* fix codestyle

* improve checks for pam usage (should fix tests)

* fix remember_me part 1

* add remember_token column because :rememberable requires either a password or this column.

* migrate db for remember_token

* move pam_authentication to the right place, fix logic bug in edit.html.haml

* fix tests

* fix pam authentication, improve username lookup, add comment

* valid? is sometimes not honored, return nil instead trying to authenticate with pam

* update devise_pam_authenticatable2 and adjust code. Fixes sideeffects observed in tests

* update devise_pam_authenticatable gem, fixes for codeconventions, fix finding user

* codeconvention fixes

* code convention fixes

* fix idention

* update dependency, explicit conflict check

* fix disabled password updates if in pam mode

* fix check password if password is present, fix templates

* block registration if account is maintained by pam

* Revert "block registration if account is maintained by pam"

This reverts commit 8e7a083d650240b6fac414926744b4b90b435f20.

* fix identation error introduced by rebase

* block usernames maintained by pam

* document pam settings better

* fix code style
2018-02-02 10:18:55 +01:00
abcang
1afc70c990 Fix mistake in cache deletion (#6408) 2018-02-02 10:10:18 +01:00
Rob Watson
f4bd51da1e Upgrade Paperclip > 5.2.1 (#6404)
Mitigation for CVE-2017-0889.

https://www.cvedetails.com/cve/CVE-2017-0889/
https://medium.com/in-the-weeds/all-about-paperclips-cve-2017-0889-server-side-request-forgery-ssrf-vulnerability-8cb2b1c96fe8
2018-02-01 17:54:22 +01:00
abcang
ffb2b8ef8c Fix button hiding when header title is too long (#6406) 2018-02-01 17:17:17 +01:00
Evgeny Petrov
3ed194b67d Russian language updated (#6397) 2018-02-01 07:33:54 +09:00
Daniel King
2cff744cdf fix ruby 2.5 rvm install in vagrant (#6396)
RVM has a known issue with installing Ruby 2.5 on the version of Ubuntu
the Vagrant box is using: https://github.com/rvm/rvm/issues/4291

This bug was preventing any gem installs in the vagrant box
2018-01-31 21:52:58 +01:00
317 changed files with 6882 additions and 2069 deletions

View File

@@ -9,11 +9,15 @@ DB_USER=postgres
DB_NAME=postgres
DB_PASS=
DB_PORT=5432
# Optional ElasticSearch configuration
# ES_ENABLED=true
# ES_HOST=localhost
# ES_PORT=9200
# Federation
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=example.com
LOCAL_DOMAIN=example.com
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
@@ -29,7 +33,6 @@ LOCAL_DOMAIN=example.com
# Application secrets
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
PAPERCLIP_SECRET=
SECRET_KEY_BASE=
OTP_SECRET=
@@ -58,7 +61,7 @@ VAPID_PUBLIC_KEY=
# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587
@@ -135,3 +138,73 @@ STREAMING_CLUSTER_NUM=1
# If you use Docker, you may want to assign UID/GID manually.
# UID=1000
# GID=1000
# LDAP authentication (optional)
# LDAP_ENABLED=true
# LDAP_HOST=localhost
# LDAP_PORT=389
# LDAP_METHOD=simple_tls
# LDAP_BASE=
# LDAP_BIND_DN=
# LDAP_PASSWORD=
# LDAP_UID=cn
# PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable
# and optional as fallback PAM_DEFAULT_SUFFIX
# The pam environment variable "email" is provided by:
# https://github.com/devkral/pam_email_extractor
# PAM_ENABLED=true
# Fallback Suffix for email address generation (nil by default)
# PAM_DEFAULT_SUFFIX=pam
# Name of the pam service (pam "auth" section is evaluated)
# PAM_DEFAULT_SERVICE=rpam
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated)
# PAM_CONTROLLED_SERVICE=rpam
# Global OAuth settings (optional) :
# If you have only one strategy, you may want to enable this
# OAUTH_REDIRECT_AT_SIGN_IN=true
# Optional CAS authentication (cf. omniauth-cas) :
# CAS_ENABLED=true
# CAS_URL=https://sso.myserver.com/
# CAS_HOST=sso.myserver.com/
# CAS_PORT=443
# CAS_SSL=true
# CAS_VALIDATE_URL=
# CAS_CALLBACK_URL=
# CAS_LOGOUT_URL=
# CAS_LOGIN_URL=
# CAS_UID_FIELD='user'
# CAS_CA_PATH=
# CAS_DISABLE_SSL_VERIFICATION=false
# CAS_UID_KEY='user'
# CAS_NAME_KEY='name'
# CAS_EMAIL_KEY='email'
# CAS_NICKNAME_KEY='nickname'
# CAS_FIRST_NAME_KEY='firstname'
# CAS_LAST_NAME_KEY='lastname'
# CAS_LOCATION_KEY='location'
# CAS_IMAGE_KEY='image'
# CAS_PHONE_KEY='phone'
# Optional SAML authentication (cf. omniauth-saml)
# SAML_ENABLED=true
# SAML_ACS_URL=
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
# SAML_IDP_CERT=
# SAML_IDP_CERT_FINGERPRINT=
# SAML_NAME_IDENTIFIER_FORMAT=
# SAML_CERT=
# SAML_PRIVATE_KEY=
# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

View File

@@ -3,15 +3,15 @@ cache:
bundler: true
yarn: true
directories:
- node_modules
- public/assets
- public/packs-test
- tmp/cache/babel-loader
- node_modules
- public/assets
- public/packs-test
- tmp/cache/babel-loader
dist: trusty
sudo: false
branches:
only:
- master
- master
notifications:
email: false
@@ -23,21 +23,20 @@ env:
- RAILS_ENV=test
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true
- PARALLEL_TEST_PROCESSORS=2
- "PATH=$HOME:$PATH"
addons:
postgresql: 9.4
apt:
sources:
- trusty-media
- sourceline: deb https://dl.yarnpkg.com/debian/ stable main
key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
- trusty-media
- sourceline: deb https://dl.yarnpkg.com/debian/ stable main
key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
packages:
- ffmpeg
- libicu-dev
- libprotobuf-dev
- protobuf-compiler
- yarn
- ffmpeg
- libicu-dev
- libprotobuf-dev
- protobuf-compiler
- yarn
rvm:
- 2.4.2
@@ -53,7 +52,6 @@ install:
before_script:
- ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
script:
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec

View File

@@ -3,8 +3,10 @@ FROM ruby:2.5.0-alpine3.7
LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server"
ENV UID=991 GID=991 \
RAILS_SERVE_STATIC_FILES=true \
ARG UID=991
ARG GID=991
ENV RAILS_SERVE_STATIC_FILES=true \
RAILS_ENV=production NODE_ENV=production
ARG YARN_VERSION=1.3.2
@@ -68,12 +70,12 @@ RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-in
&& yarn --pure-lockfile \
&& yarn cache clean
COPY . /mastodon
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon
COPY docker_entrypoint.sh /usr/local/bin/run
RUN chmod +x /usr/local/bin/run
COPY --chown=mastodon:mastodon . /mastodon
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs
ENTRYPOINT ["/usr/local/bin/run"]
USER mastodon
ENTRYPOINT ["/sbin/tini", "--"]

18
Gemfile
View File

@@ -7,7 +7,6 @@ gem 'pkg-config', '~> 1.2'
gem 'puma', '~> 3.10'
gem 'rails', '~> 5.1.4'
gem 'uglifier', '~> 3.2'
gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 0.20'
@@ -20,6 +19,7 @@ gem 'fog-local', '~> 0.4', require: false
gem 'fog-openstack', '~> 0.1', require: false
gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0'
gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.5'
@@ -27,11 +27,20 @@ gem 'bootsnap'
gem 'browser'
gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639'
gem 'chewy', '~> 0.10', git: 'https://github.com/toptal/chewy.git'
gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.4'
gem 'devise-two-factor', '~> 3.0'
gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
gem 'net-ldap', '~> 0.10', install_if: -> { ENV['LDAP_ENABLED'] == 'true' }
gem 'omniauth-cas', '~> 1.1', install_if: -> { ENV['CAS_ENABLED'] == 'true' }
gem 'omniauth-saml', '~> 1.10', install_if: -> { ENV['SAML_ENABLED'] == 'true' }
gem 'omniauth', '~> 1.2'
gem 'doorkeeper', '~> 4.2'
gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.5'
@@ -69,6 +78,8 @@ gem 'simple-navigation', '~> 4.0'
gem 'simple_form', '~> 3.4'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'strong_migrations'
gem 'tty-command'
gem 'tty-prompt'
gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2017'
gem 'webpacker', '~> 3.0'
@@ -85,6 +96,10 @@ group :development, :test do
gem 'rspec-rails', '~> 3.7'
end
group :production, :test do
gem 'private_address_check', '~> 0.4.1'
end
group :test do
gem 'capybara', '~> 2.15'
gem 'climate_control', '~> 0.2'
@@ -105,6 +120,7 @@ group :development do
gem 'bullet', '~> 5.5'
gem 'letter_opener', '~> 1.4'
gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler'
gem 'rubocop', require: false
gem 'brakeman', '~> 4.0', require: false
gem 'bundler-audit', '~> 0.6', require: false

View File

@@ -1,3 +1,12 @@
GIT
remote: https://github.com/toptal/chewy.git
revision: a7d21eb4b0bd7415533ef134bb6d31b2df309701
specs:
chewy (0.10.1)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
GEM
remote: https://rubygems.org/
specs:
@@ -137,6 +146,9 @@ GEM
devise (~> 4.0)
railties (< 5.2)
rotp (~> 2.0)
devise_pam_authenticatable2 (8.0.1)
devise (>= 4.0.0)
rpam2 (~> 3.0)
diff-lcs (1.3)
docile (1.1.5)
domain_name (0.5.20170404)
@@ -151,16 +163,28 @@ GEM
json
thread
thread_safe
elasticsearch (6.0.1)
elasticsearch-api (= 6.0.1)
elasticsearch-transport (= 6.0.1)
elasticsearch-api (6.0.1)
multi_json
elasticsearch-dsl (0.1.5)
elasticsearch-transport (6.0.1)
faraday
multi_json
encryptor (3.0.0)
equatable (0.5.0)
erubi (1.7.0)
et-orbi (1.0.8)
tzinfo
excon (0.59.0)
execjs (2.7.0)
fabrication (2.18.0)
faker (1.8.4)
i18n (~> 0.5)
faraday (0.14.0)
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0)
fastimage (2.1.1)
ffi (1.9.18)
fog-core (1.45.0)
builder
@@ -198,8 +222,10 @@ GEM
hamster (3.0.0)
concurrent-ruby (~> 1.0)
hashdiff (0.3.7)
hashie (3.5.7)
highline (1.7.10)
hiredis (0.6.1)
hitimes (1.2.6)
hkdf (0.3.0)
htmlentities (4.3.4)
http (3.0.0)
@@ -215,7 +241,7 @@ GEM
httplog (0.99.7)
colorize
rack
i18n (0.9.1)
i18n (0.9.3)
concurrent-ruby (~> 1.0)
i18n-tasks (0.9.19)
activesupport (>= 4.0.2)
@@ -274,6 +300,7 @@ GEM
mini_mime (>= 0.1.1)
mario-redis-lock (1.2.0)
redis (~> 3, >= 3.0.5)
memory_profiler (0.9.10)
method_source (0.9.0)
microformats (4.0.7)
json
@@ -284,9 +311,12 @@ GEM
mimemagic (0.3.2)
mini_mime (1.0.0)
mini_portile2 (2.3.0)
minitest (5.10.3)
minitest (5.11.3)
msgpack (1.1.0)
multi_json (1.12.2)
multipart-post (2.0.0)
necromancer (0.4.0)
net-ldap (0.16.1)
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (4.2.0)
@@ -301,13 +331,23 @@ GEM
sidekiq (>= 3.5.0)
statsd-ruby (~> 1.2.0)
oj (3.3.10)
omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3)
omniauth-cas (1.1.1)
addressable (~> 2.3)
nokogiri (~> 1.5)
omniauth (~> 1.2)
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
orm_adapter (0.5.0)
ostatus2 (2.0.3)
addressable (~> 2.5)
http (~> 3.0)
nokogiri (~> 1.8)
ox (2.8.2)
paperclip (5.1.0)
paperclip (5.2.1)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
cocaine (~> 0.5.5)
@@ -321,6 +361,9 @@ GEM
parallel
parser (2.4.0.2)
ast (~> 2.3)
pastel (0.7.2)
equatable (~> 0.5.0)
tty-color (~> 0.4.0)
pg (0.21.0)
pghero (1.7.0)
activerecord
@@ -333,6 +376,7 @@ GEM
premailer-rails (1.10.1)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
private_address_check (0.4.1)
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -420,6 +464,7 @@ GEM
actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3)
rotp (2.1.2)
rpam2 (3.1.0)
rqrcode (0.10.1)
chunky_png (~> 1.0)
rspec-core (3.7.0)
@@ -451,6 +496,8 @@ GEM
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-oembed (0.12.0)
ruby-progressbar (1.9.0)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
rufus-scheduler (3.4.2)
et-orbi (~> 1.0)
safe_yaml (1.0.4)
@@ -503,6 +550,8 @@ GEM
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
statsd-ruby (1.2.1)
streamio-ffmpeg (3.0.2)
multi_json (~> 1.8)
strong_migrations (0.1.9)
activerecord (>= 3.2.0)
temple (0.8.0)
@@ -512,14 +561,29 @@ GEM
thread (0.2.2)
thread_safe (0.3.6)
tilt (2.0.8)
timers (4.1.2)
hitimes
tty-color (0.4.2)
tty-command (0.7.0)
pastel (~> 0.7.0)
tty-cursor (0.5.0)
tty-prompt (0.15.0)
necromancer (~> 0.4.0)
pastel (~> 0.7.0)
timers (~> 4.0)
tty-cursor (~> 0.5.0)
tty-reader (~> 0.2.0)
tty-reader (0.2.0)
tty-cursor (~> 0.5.0)
tty-screen (~> 0.6.4)
wisper (~> 2.0.0)
tty-screen (0.6.4)
twitter-text (1.14.7)
unf (~> 0.1.0)
tzinfo (1.2.4)
thread_safe (~> 0.1)
tzinfo-data (1.2017.3)
tzinfo (>= 1.0.0)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.4)
@@ -541,6 +605,7 @@ GEM
websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3)
wisper (2.0.0)
xpath (2.1.0)
nokogiri (~> 1.3)
@@ -566,15 +631,18 @@ DEPENDENCIES
capistrano-yarn (~> 2.0)
capybara (~> 2.15)
charlock_holmes (~> 0.7.5)
chewy (~> 0.10)!
cld3 (~> 3.2.0)
climate_control (~> 0.2)
devise (~> 4.4)
devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 8.0)
doorkeeper (~> 4.2)
dotenv-rails (~> 2.2)
fabrication (~> 2.18)
faker (~> 1.7)
fast_blank (~> 1.0)
fastimage
fog-core (~> 1.45)
fog-local (~> 0.4)
fog-openstack (~> 0.1)
@@ -596,11 +664,16 @@ DEPENDENCIES
link_header (~> 0.0)
lograge (~> 0.7)
mario-redis-lock (~> 1.2)
memory_profiler
microformats (~> 4.0)
mime-types (~> 3.1)
net-ldap (~> 0.10)
nokogiri (~> 1.8)
nsa (~> 0.2)
oj (~> 3.3)
omniauth (~> 1.2)
omniauth-cas (~> 1.1)
omniauth-saml (~> 1.10)
ostatus2 (~> 2.0)
ox (~> 2.8)
paperclip (~> 5.1)
@@ -610,6 +683,7 @@ DEPENDENCIES
pghero (~> 1.7)
pkg-config (~> 1.2)
premailer-rails
private_address_check (~> 0.4.1)
pry-rails (~> 0.3)
puma (~> 3.10)
pundit (~> 1.1)
@@ -640,10 +714,12 @@ DEPENDENCIES
simple_form (~> 3.4)
simplecov (~> 0.14)
sprockets-rails (~> 3.2)
streamio-ffmpeg (~> 3.0)
strong_migrations
tty-command
tty-prompt
twitter-text (~> 1.14)
tzinfo-data (~> 1.2017)
uglifier (~> 3.2)
webmock (~> 3.0)
webpacker (~> 3.0)
webpush

View File

@@ -17,9 +17,10 @@ Click on the screenshot below to watch a demo of the UI:
**Ruby on Rails** is used for the back-end, while **React.js** and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
If you would like, you can [support the development of this project on Patreon][patreon] or [Liberapay][liberapay]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
[patreon]: https://www.patreon.com/user?u=619786
[liberapay]: https://liberapay.com/Mastodon/
---

5
Vagrantfile vendored
View File

@@ -39,6 +39,7 @@ sudo apt-get install \
libidn11-dev \
libprotobuf-dev \
libreadline-dev \
libpam0g-dev \
-y
# Install rvm
@@ -48,7 +49,7 @@ curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-instal
source /home/vagrant/.rvm/scripts/rvm
# Install Ruby
rvm install ruby-$RUBY_VERSION
rvm reinstall ruby-$RUBY_VERSION --disable-binary
# Configure database
sudo -u postgres createuser -U postgres vagrant -s
@@ -79,7 +80,7 @@ VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.box = "ubuntu/xenial64"
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"

View File

@@ -0,0 +1,61 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
settings index: { refresh_interval: '15m' }, analysis: {
filter: {
english_stop: {
type: 'stop',
stopwords: '_english_',
},
english_stemmer: {
type: 'stemmer',
language: 'english',
},
english_possessive_stemmer: {
type: 'stemmer',
language: 'possessive_english',
},
},
analyzer: {
content: {
tokenizer: 'uax_url_email',
filter: %w(
english_possessive_stemmer
lowercase
asciifolding
cjk_width
english_stop
english_stemmer
),
},
},
}
define_type ::Status.without_reblogs do
crutch :mentions do |collection|
data = ::Mention.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :favourites do |collection|
data = ::Favourite.where(status_id: collection.map(&:id)).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :reblogs do |collection|
data = ::Status.where(reblog_of_id: collection.map(&:id)).pluck(:reblog_of_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
root date_detection: false do
field :account_id, type: 'long'
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].join("\n\n") } do
field :stemmed, type: 'text', analyzer: 'content'
end
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }
field :created_at, type: 'date'
end
end
end

View File

@@ -31,7 +31,7 @@ class AboutController < ApplicationController
def initial_state_params
{
settings: {},
settings: { known_fediverse: Setting.show_known_fediverse_at_about_page },
token: current_session&.token,
}
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class AccountsController < ApplicationController
PAGE_SIZE = 20
include AccountControllerConcern
before_action :set_cache_headers
@@ -16,13 +18,16 @@ class AccountsController < ApplicationController
end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_statuses.paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = filtered_status_page(params)
@statuses = cache_collection(@statuses, Status)
@next_url = next_url unless @statuses.empty?
unless @statuses.empty?
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
@newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
end
end
format.atom do
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id])
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
end
@@ -69,13 +74,22 @@ class AccountsController < ApplicationController
@account = Account.find_local!(params[:username])
end
def next_url
def older_url
::Rails.logger.info("older: max_id #{@statuses.last.id}, url #{pagination_url(max_id: @statuses.last.id)}")
pagination_url(max_id: @statuses.last.id)
end
def newer_url
pagination_url(min_id: @statuses.first.id)
end
def pagination_url(max_id: nil, min_id: nil)
if media_requested?
short_account_media_url(@account, max_id: @statuses.last.id)
short_account_media_url(@account, max_id: max_id, min_id: min_id)
elsif replies_requested?
short_account_with_replies_url(@account, max_id: @statuses.last.id)
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
else
short_account_url(@account, max_id: @statuses.last.id)
short_account_url(@account, max_id: max_id, min_id: min_id)
end
end
@@ -86,4 +100,12 @@ class AccountsController < ApplicationController
def replies_requested?
request.path.ends_with?('/with_replies')
end
def filtered_status_page(params)
if params[:min_id].present?
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end
end
end

View File

@@ -11,7 +11,7 @@ class ActivityPub::InboxesController < Api::BaseController
process_payload
head 202
else
[signature_verification_failure_reason, 401]
render plain: signature_verification_failure_reason, status: 401
end
end

View File

@@ -1,10 +1,12 @@
# frozen_string_literal: true
class ActivityPub::OutboxesController < Api::BaseController
include SignatureVerification
before_action :set_account
def show
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = cache_collection(@statuses, Status)
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'

View File

@@ -16,9 +16,11 @@ module Admin
show_staff_badge
bootstrap_timeline_accounts
thumbnail
hero
min_invite_role
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
).freeze
BOOLEAN_SETTINGS = %w(
@@ -28,10 +30,12 @@ module Admin
show_staff_badge
activity_api_enabled
peers_api_enabled
show_known_fediverse_at_about_page
).freeze
UPLOAD_SETTINGS = %w(
thumbnail
hero
).freeze
def edit

View File

@@ -51,6 +51,10 @@ class Api::BaseController < ApplicationController
[params[:limit].to_i.abs, default_limit * 2].min
end
def truthy_param?(key)
ActiveModel::Type::Boolean.new.cast(params[key])
end
def current_resource_owner
@current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Api::SalmonController < Api::BaseController
include SignatureVerification
before_action :set_account
respond_to :txt
@@ -9,7 +11,7 @@ class Api::SalmonController < Api::BaseController
process_salmon
head 202
elsif payload.present?
[signature_verification_failure_reason, 401]
render plain: signature_verification_failure_reason, status: 401
else
head 400
end

View File

@@ -20,6 +20,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
private
def account_params
params.permit(:display_name, :note, :avatar, :header)
params.permit(:display_name, :note, :avatar, :header, :locked)
end
end

View File

@@ -10,7 +10,7 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
accounts = Account.where(id: account_ids).select('id')
# .where doesn't guarantee that our results are in the same order
# we requested them, so return the "right" order to the requestor.
@accounts = accounts.index_by(&:id).values_at(*account_ids)
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
end
@@ -21,6 +21,6 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
end
def account_ids
@_account_ids ||= Array(params[:id]).map(&:to_i)
Array(params[:id]).map(&:to_i)
end
end

View File

@@ -22,8 +22,4 @@ class Api::V1::Accounts::SearchController < Api::BaseController
following: truthy_param?(:following)
)
end
def truthy_param?(key)
params[key] == 'true'
end
end

View File

@@ -28,9 +28,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
def account_statuses
default_statuses.tap do |statuses|
statuses.merge!(only_media_scope) if params[:only_media]
statuses.merge!(pinned_scope) if params[:pinned]
statuses.merge!(no_replies_scope) if params[:exclude_replies]
statuses.merge!(only_media_scope) if truthy_param?(:only_media)
statuses.merge!(pinned_scope) if truthy_param?(:pinned)
statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
end
end

View File

@@ -13,9 +13,9 @@ class Api::V1::AccountsController < Api::BaseController
end
def follow
FollowService.new.call(current_user.account, @account.acct, reblogs: params[:reblogs])
FollowService.new.call(current_user.account, @account.acct, reblogs: truthy_param?(:reblogs))
options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: params[:reblogs] } }, requested_map: { @account.id => false } }
options = @account.locked? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end
@@ -26,7 +26,7 @@ class Api::V1::AccountsController < Api::BaseController
end
def mute
MuteService.new.call(current_user.account, @account, notifications: params[:notifications])
MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications))
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end

View File

@@ -27,7 +27,7 @@ class Api::V1::MediaController < Api::BaseController
private
def media_params
params.permit(:file, :description)
params.permit(:file, :description, :focus)
end
def file_type_error

View File

@@ -13,14 +13,14 @@ class Api::V1::ReportsController < Api::BaseController
end
def create
@report = current_account.reports.create!(
target_account: reported_account,
@report = ReportService.new.call(
current_account,
reported_account,
status_ids: reported_status_ids,
comment: report_params[:comment]
comment: report_params[:comment],
forward: report_params[:forward]
)
User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
render json: @report, serializer: REST::ReportSerializer
end
@@ -39,6 +39,6 @@ class Api::V1::ReportsController < Api::BaseController
end
def report_params
params.permit(:account_id, :comment, status_ids: [])
params.permit(:account_id, :comment, :forward, status_ids: [])
end
end

View File

@@ -33,12 +33,8 @@ class Api::V1::SearchController < Api::BaseController
SearchService.new.call(
params[:q],
RESULTS_LIMIT,
resolving_search?,
truthy_param?(:resolve),
current_account
)
end
def resolving_search?
params[:resolve] == 'true'
end
end

View File

@@ -21,15 +21,23 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def public_statuses
public_timeline_statuses.paginate_by_max_id(
statuses = public_timeline_statuses.paginate_by_max_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id]
)
if truthy_param?(:only_media)
# `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
statuses.where(id: status_ids)
else
statuses
end
end
def public_timeline_statuses
Status.as_public_timeline(current_account, params[:local])
Status.as_public_timeline(current_account, truthy_param?(:local))
end
def insert_pagination_headers
@@ -37,7 +45,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end
def pagination_params(core_params)
params.permit(:local, :limit).merge(core_params)
params.permit(:local, :limit, :only_media).merge(core_params)
end
def next_path

View File

@@ -29,16 +29,24 @@ class Api::V1::Timelines::TagController < Api::BaseController
if @tag.nil?
[]
else
tag_timeline_statuses.paginate_by_max_id(
statuses = tag_timeline_statuses.paginate_by_max_id(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id]
)
if truthy_param?(:only_media)
# `SELECT DISTINCT id, updated_at` is too slow, so pluck ids at first, and then select id, updated_at with ids.
status_ids = statuses.joins(:media_attachments).distinct(:id).pluck(:id)
statuses.where(id: status_ids)
else
statuses
end
end
end
def tag_timeline_statuses
Status.as_tag_timeline(@tag, current_account, params[:local])
Status.as_tag_timeline(@tag, current_account, truthy_param?(:local))
end
def insert_pagination_headers
@@ -46,7 +54,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
end
def pagination_params(core_params)
params.permit(:local, :limit).merge(core_params)
params.permit(:local, :limit, :only_media).merge(core_params)
end
def next_path

View File

@@ -14,6 +14,7 @@ class ApplicationController < ActionController::Base
helper_method :current_session
helper_method :current_theme
helper_method :single_user_mode?
helper_method :use_seamless_external_login?
rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found
@@ -34,7 +35,7 @@ class ApplicationController < ActionController::Base
end
def store_current_location
store_location_for(:user, request.url)
store_location_for(:user, request.url) unless request.format == :json
end
def require_admin!
@@ -75,6 +76,10 @@ class ApplicationController < ActionController::Base
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
end
def use_seamless_external_login?
Devise.pam_authentication || Devise.ldap_authentication
end
def current_account
@current_account ||= current_user.try(:account)
end

View File

@@ -2,4 +2,28 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
before_action :set_user, only: [:finish_signup]
# GET/PATCH /users/:id/finish_signup
def finish_signup
return unless request.patch? && params[:user]
if @user.update(user_params)
@user.skip_reconfirmation!
sign_in(@user, bypass: true)
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
else
@show_errors = true
end
end
private
def set_user
@user = current_user
end
def user_params
params.require(:user).permit(:email)
end
end

View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true
class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token
def self.provides_callback_for(provider)
provider_id = provider.to_s.chomp '_oauth2'
define_method provider do
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: provider_id.capitalize) if is_navigational_format?
else
session["devise.#{provider}_data"] = request.env['omniauth.auth']
redirect_to new_user_registration_url
end
end
end
Devise.omniauth_configs.each_key do |provider|
provides_callback_for provider
end
def after_sign_in_path_for(resource)
if resource.email_verified?
root_path
else
finish_signup_path
end
end
end

View File

@@ -14,6 +14,11 @@ class Auth::RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
super
end
def build_resource(hash = nil)
super(hash)

View File

@@ -10,6 +10,15 @@ class Auth::SessionsController < Devise::SessionsController
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
before_action :set_instance_presenter, only: [:new]
def new
Devise.omniauth_configs.each do |provider, config|
if config.strategy.redirect_at_sign_in
return redirect_to(omniauth_authorize_path(resource_name, provider))
end
end
super
end
def create
super do |resource|
remember_me(resource)
@@ -28,7 +37,11 @@ class Auth::SessionsController < Devise::SessionsController
if session[:otp_user_id]
User.find(session[:otp_user_id])
elsif user_params[:email]
User.find_for_authentication(email: user_params[:email])
if use_seamless_external_login? && Devise.check_at_sign && user_params[:email].index('@').nil?
User.joins(:account).find_by(accounts: { username: user_params[:email] })
else
User.find_for_authentication(email: user_params[:email])
end
end
end

View File

@@ -0,0 +1,11 @@
# frozen_string_literal: true
module SignatureAuthentication
extend ActiveSupport::Concern
include SignatureVerification
def current_account
super || signed_request_account
end
end

View File

@@ -7,7 +7,9 @@ class FollowerAccountsController < ApplicationController
@follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
respond_to do |format|
format.html
format.html do
@relationships = AccountRelationshipsPresenter.new(@follows.map(&:account_id), current_user.account_id) if user_signed_in?
end
format.json do
render json: collection_presenter,

View File

@@ -7,7 +7,9 @@ class FollowingAccountsController < ApplicationController
@follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
respond_to do |format|
format.html
format.html do
@relationships = AccountRelationshipsPresenter.new(@follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
end
format.json do
render json: collection_presenter,

View File

@@ -3,20 +3,26 @@
class MediaController < ApplicationController
include Authorization
before_action :verify_permitted_status
before_action :set_media_attachment
before_action :verify_permitted_status!
def show
redirect_to media_attachment.file.url(:original)
redirect_to @media_attachment.file.url(:original)
end
def player
@body_classes = 'player'
raise ActiveRecord::RecordNotFound unless @media_attachment.video? || @media_attachment.gifv?
end
private
def media_attachment
MediaAttachment.attached.find_by!(shortcode: params[:id])
def set_media_attachment
@media_attachment = MediaAttachment.attached.find_by!(shortcode: params[:id] || params[:medium_id])
end
def verify_permitted_status
authorize media_attachment.status, :show?
def verify_permitted_status!
authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound

View File

@@ -1,11 +1,23 @@
# frozen_string_literal: true
class Settings::ExportsController < ApplicationController
include Authorization
layout 'admin'
before_action :authenticate_user!
def show
@export = Export.new(current_account)
@export = Export.new(current_account)
@backups = current_user.backups
end
def create
authorize :backup, :create?
backup = current_user.backups.create!
BackupWorker.perform_async(backup.id)
redirect_to settings_export_path
end
end

View File

@@ -39,6 +39,7 @@ class Settings::PreferencesController < ApplicationController
:setting_boost_modal,
:setting_delete_modal,
:setting_auto_play_gif,
:setting_display_sensitive_media,
:setting_reduce_motion,
:setting_system_font_ui,
:setting_noindex,

View File

@@ -1,6 +1,7 @@
# frozen_string_literal: true
class StatusesController < ApplicationController
include SignatureAuthentication
include Authorization
layout 'public'

View File

@@ -10,6 +10,7 @@ class StreamEntriesController < ApplicationController
before_action :set_stream_entry
before_action :set_link_headers
before_action :check_account_suspension
before_action :set_cache_headers
def show
respond_to do |format|
@@ -19,6 +20,10 @@ class StreamEntriesController < ApplicationController
end
format.atom do
unless @stream_entry.hidden?
skip_session!
expires_in 3.minutes, public: true
end
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
end
end

View File

@@ -0,0 +1,4 @@
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -178,11 +178,11 @@ export function uploadCompose(files) {
};
};
export function changeUploadCompose(id, description) {
export function changeUploadCompose(id, params) {
return (dispatch, getState) => {
dispatch(changeUploadComposeRequest());
api(getState).put(`/api/v1/media/${id}`, { description }).then(response => {
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));

View File

@@ -10,6 +10,7 @@ export const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL';
export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
export function initReport(account, status) {
return dispatch => {
@@ -45,6 +46,7 @@ export function submitReport() {
account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment']),
forward: getState().getIn(['reports', 'new', 'forward']),
}).then(response => {
dispatch(closeModal());
dispatch(submitReportSuccess(response.data));
@@ -78,3 +80,10 @@ export function changeReportComment(comment) {
comment,
};
};
export function changeReportForward(forward) {
return {
type: REPORT_FORWARD_CHANGE,
forward,
};
};

View File

@@ -1,4 +1,5 @@
import api from '../api';
import { fetchRelationships } from './accounts';
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
@@ -38,6 +39,7 @@ export function submitSearch() {
},
}).then(response => {
dispatch(fetchSearchSuccess(response.data));
dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
}).catch(error => {
dispatch(fetchSearchFail(error));
});

View File

@@ -120,7 +120,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);
@@ -161,7 +161,7 @@ export function expandTimeline(timelineId, path, params = {}) {
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
export const expandAccountTimeline = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);

View File

@@ -1,17 +1,8 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ColumnBackButton from './column_back_button';
export default class ColumnBackButtonSlim extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
};
handleClick = () => {
if (window.history && window.history.length === 1) this.context.router.history.push('/');
else this.context.router.history.goBack();
}
export default class ColumnBackButtonSlim extends ColumnBackButton {
render () {
return (

View File

@@ -133,9 +133,7 @@ export default class ColumnHeader extends React.PureComponent {
<h1 className={buttonClassName}>
<button onClick={this.handleTitleClick}>
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
<span className='column-header__title'>
{title}
</span>
{title}
</button>
<div className='column-header__buttons'>

View File

@@ -6,12 +6,32 @@ import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile';
import classNames from 'classnames';
import { autoPlayGif } from '../initial_state';
import { autoPlayGif, displaySensitiveMedia } from '../initial_state';
const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
});
const shiftToPoint = (containerToImageRatio, containerSize, imageSize, focusSize, toMinus) => {
const containerCenter = Math.floor(containerSize / 2);
const focusFactor = (focusSize + 1) / 2;
const scaledImage = Math.floor(imageSize / containerToImageRatio);
let focus = Math.floor(focusFactor * scaledImage);
if (toMinus) focus = scaledImage - focus;
let focusOffset = focus - containerCenter;
const remainder = scaledImage - focus;
const containerRemainder = containerSize - containerCenter;
if (remainder < containerRemainder) focusOffset -= containerRemainder - remainder;
if (focusOffset < 0) focusOffset = 0;
return (focusOffset * -100 / containerSize) + '%';
};
class Item extends React.PureComponent {
static contextTypes = {
@@ -24,6 +44,8 @@ class Item extends React.PureComponent {
index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
containerWidth: PropTypes.number,
containerHeight: PropTypes.number,
};
static defaultProps = {
@@ -62,7 +84,7 @@ class Item extends React.PureComponent {
}
render () {
const { attachment, index, size, standalone } = this.props;
const { attachment, index, size, standalone, containerWidth, containerHeight } = this.props;
let width = 50;
let height = 100;
@@ -116,16 +138,40 @@ class Item extends React.PureComponent {
let thumbnail = '';
if (attachment.get('type') === 'image') {
const previewUrl = attachment.get('preview_url');
const previewUrl = attachment.get('preview_url');
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
const originalUrl = attachment.get('url');
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const originalUrl = attachment.get('url');
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const originalHeight = attachment.getIn(['meta', 'original', 'height']);
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null;
const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
const sizes = hasSize ? `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw` : null;
const focusX = attachment.getIn(['meta', 'focus', 'x']);
const focusY = attachment.getIn(['meta', 'focus', 'y']);
const imageStyle = {};
if (originalWidth && originalHeight && containerWidth && containerHeight && focusX && focusY) {
const widthRatio = originalWidth / (containerWidth * (width / 100));
const heightRatio = originalHeight / (containerHeight * (height / 100));
let hShift = 0;
let vShift = 0;
if (widthRatio > heightRatio) {
hShift = shiftToPoint(heightRatio, (containerWidth * (width / 100)), originalWidth, focusX);
} else if(widthRatio < heightRatio) {
vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true);
}
imageStyle.top = vShift;
imageStyle.left = hShift;
} else {
imageStyle.height = '100%';
}
thumbnail = (
<a
@@ -134,7 +180,14 @@ class Item extends React.PureComponent {
onClick={this.handleClick}
target='_blank'
>
<img src={previewUrl} srcSet={srcSet} sizes={sizes} alt={attachment.get('description')} title={attachment.get('description')} />
<img
src={previewUrl}
srcSet={srcSet}
sizes={sizes}
alt={attachment.get('description')}
title={attachment.get('description')}
style={imageStyle}
/>
</a>
);
} else if (attachment.get('type') === 'gifv') {
@@ -187,7 +240,7 @@ export default class MediaGallery extends React.PureComponent {
};
state = {
visible: !this.props.sensitive,
visible: !this.props.sensitive || displaySensitiveMedia,
};
componentWillReceiveProps (nextProps) {
@@ -205,7 +258,7 @@ export default class MediaGallery extends React.PureComponent {
}
handleRef = (node) => {
if (node && this.isStandaloneEligible()) {
if (node /*&& this.isStandaloneEligible()*/) {
// offsetWidth triggers a layout, so only calculate when we need to
this.setState({
width: node.offsetWidth,
@@ -227,15 +280,12 @@ export default class MediaGallery extends React.PureComponent {
const style = {};
if (this.isStandaloneEligible()) {
if (!visible && width) {
// only need to forcibly set the height in "sensitive" mode
if (width) {
style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
} else {
// layout automatically, using image's natural aspect ratio
style.height = '';
}
} else if (width) {
style.height = width / (16/9);
} else {
// crop the image
style.height = height;
}
@@ -249,7 +299,7 @@ export default class MediaGallery extends React.PureComponent {
}
children = (
<button className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
<button type='button' className='media-spoiler' onClick={this.handleOpen} style={style} ref={this.handleRef}>
<span className='media-spoiler__warning'>{warning}</span>
<span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</button>
@@ -260,12 +310,12 @@ export default class MediaGallery extends React.PureComponent {
if (this.isStandaloneEligible()) {
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
} else {
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} containerWidth={width} containerHeight={style.height} />);
}
}
return (
<div className='media-gallery' style={style}>
<div className='media-gallery' style={style} ref={this.handleRef}>
<div className={classNames('spoiler-button', { 'spoiler-button--visible': visible })}>
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
</div>

View File

@@ -184,6 +184,7 @@ export default class Status extends ImmutablePureComponent {
src={video.get('url')}
width={239}
height={110}
inline
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
/>

View File

@@ -6,6 +6,7 @@ import { hydrateStore } from '../actions/store';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import PublicTimeline from '../features/standalone/public_timeline';
import CommunityTimeline from '../features/standalone/community_timeline';
import HashtagTimeline from '../features/standalone/hashtag_timeline';
import initialState from '../initial_state';
@@ -23,17 +24,24 @@ export default class TimelineContainer extends React.PureComponent {
static propTypes = {
locale: PropTypes.string.isRequired,
hashtag: PropTypes.string,
showPublicTimeline: PropTypes.bool.isRequired,
};
static defaultProps = {
showPublicTimeline: initialState.settings.known_fediverse,
};
render () {
const { locale, hashtag } = this.props;
const { locale, hashtag, showPublicTimeline } = this.props;
let timeline;
if (hashtag) {
timeline = <HashtagTimeline hashtag={hashtag} />;
} else {
} else if (showPublicTimeline) {
timeline = <PublicTimeline />;
} else {
timeline = <CommunityTimeline />;
}
return (

View File

@@ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent {
let extraInfo = '';
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
if ('share' in navigator) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
}
menu.push(null);
menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
menu.push(null);
if (account.get('id') === me) {
@@ -122,7 +122,7 @@ export default class ActionBar extends React.PureComponent {
<div className='account__action-bar-links'>
<Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
<span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
<span><FormattedMessage id='account.posts' defaultMessage='Toots' /></span>
<strong><FormattedNumber value={account.get('statuses_count')} /></strong>
</Link>

View File

@@ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent {
render () {
const { media } = this.props;
const status = media.get('status');
const focusX = media.getIn(['meta', 'focus', 'x']);
const focusY = media.getIn(['meta', 'focus', 'y']);
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
const style = {};
let content, style;
let content;
if (media.get('type') === 'gifv') {
content = <span className='media-gallery__gifv__label'>GIF</span>;
}
if (!status.get('sensitive')) {
style = { backgroundImage: `url(${media.get('preview_url')})` };
style.backgroundImage = `url(${media.get('preview_url')})`;
style.backgroundPosition = `${x}% ${y}%`;
}
return (
<div className='account-gallery__item'>
<Permalink
to={`/statuses/${status.get('id')}`}
href={status.get('url')}
style={style}
>
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
{content}
</Permalink>
</div>

View File

@@ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { getAccountGallery } from '../../selectors';
import MediaItem from './components/media_item';
import HeaderContainer from '../account_timeline/containers/header_container';
import { FormattedMessage } from 'react-intl';
import { ScrollContainer } from 'react-router-scroll-4';
import LoadMore from '../../components/load_more';
@@ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent {
<div className='scrollable' onScroll={this.handleScroll}>
<HeaderContainer accountId={this.props.params.accountId} />
<div className='account-section-headline'>
<FormattedMessage id='account.media' defaultMessage='Media' />
</div>
<div className='account-gallery__container'>
{medias.map(media => (
<MediaItem

View File

@@ -6,6 +6,8 @@ import ActionBar from '../../account/components/action_bar';
import MissingIndicator from '../../../components/missing_indicator';
import ImmutablePureComponent from 'react-immutable-pure-component';
import MovedNote from './moved_note';
import { FormattedMessage } from 'react-intl';
import { NavLink } from 'react-router-dom';
export default class Header extends ImmutablePureComponent {
@@ -91,6 +93,12 @@ export default class Header extends ImmutablePureComponent {
onBlockDomain={this.handleBlockDomain}
onUnblockDomain={this.handleUnblockDomain}
/>
<div className='account__section-headline'>
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div>
</div>
);
}

View File

@@ -12,11 +12,15 @@ import ColumnBackButton from '../../components/column_back_button';
import { List as ImmutableList } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({
statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
});
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
const path = withReplies ? `${accountId}:with_replies` : accountId;
return {
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
};
};
@connect(mapStateToProps)
export default class AccountTimeline extends ImmutablePureComponent {
@@ -27,23 +31,24 @@ export default class AccountTimeline extends ImmutablePureComponent {
statusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
withReplies: PropTypes.bool,
};
componentWillMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId));
this.props.dispatch(refreshAccountTimeline(this.props.params.accountId));
this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies));
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId));
this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
}
}
handleScrollToBottom = () => {
if (!this.props.isLoading && this.props.hasMore) {
this.props.dispatch(expandAccountTimeline(this.props.params.accountId));
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
}
}

View File

@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Overlay from 'react-overlays/lib/Overlay';
import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import { searchEnabled } from '../../../initial_state';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@@ -17,7 +18,7 @@ class SearchPopout extends React.PureComponent {
render () {
const { style } = this.props;
const extraInformation = searchEnabled ? <FormattedMessage id='search_popout.tips.full_text' defaultMessage='Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.' /> : <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />;
return (
<div style={{ ...style, position: 'absolute', width: 285 }}>
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
@@ -32,7 +33,7 @@ class SearchPopout extends React.PureComponent {
<li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
</ul>
<FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />
{extraInformation}
</div>
)}
</Motion>

View File

@@ -22,6 +22,8 @@ export default class SearchResults extends ImmutablePureComponent {
count += results.get('accounts').size;
accounts = (
<div className='search-results__section'>
<h5><FormattedMessage id='search_results.accounts' defaultMessage='People' /></h5>
{results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
</div>
);
@@ -31,6 +33,8 @@ export default class SearchResults extends ImmutablePureComponent {
count += results.get('statuses').size;
statuses = (
<div className='search-results__section'>
<h5><FormattedMessage id='search_results.statuses' defaultMessage='Toots' /></h5>
{results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
</div>
);
@@ -40,6 +44,8 @@ export default class SearchResults extends ImmutablePureComponent {
count += results.get('hashtags').size;
hashtags = (
<div className='search-results__section'>
<h5><FormattedMessage id='search_results.hashtags' defaultMessage='Hashtags' /></h5>
{results.get('hashtags').map(hashtag => (
<Link key={hashtag} className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
#{hashtag}

View File

@@ -1,15 +1,13 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import IconButton from '../../../components/icon_button';
import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
const messages = defineMessages({
undo: { id: 'upload_form.undo', defaultMessage: 'Undo' },
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
});
@@ -21,6 +19,7 @@ export default class Upload extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
onUndo: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired,
onOpenFocalPoint: PropTypes.func.isRequired,
};
state = {
@@ -33,6 +32,10 @@ export default class Upload extends ImmutablePureComponent {
this.props.onUndo(this.props.media.get('id'));
}
handleFocalPointClick = () => {
this.props.onOpenFocalPoint(this.props.media.get('id'));
}
handleInputChange = e => {
this.setState({ dirtyDescription: e.target.value });
}
@@ -63,13 +66,20 @@ export default class Upload extends ImmutablePureComponent {
const { intl, media } = this.props;
const active = this.state.hovered || this.state.focused;
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || '';
const focusX = media.getIn(['meta', 'focus', 'x']);
const focusY = media.getIn(['meta', 'focus', 'y']);
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
return (
<div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
{({ scale }) => (
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})` }}>
<IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.handleUndoClick} />
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className={classNames('compose-form__upload__actions', { active })}>
<button className='icon-button' onClick={this.handleUndoClick}><i className='fa fa-times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Undo' /></button>
{media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><i className='fa fa-crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>}
</div>
<div className={classNames('compose-form__upload-description', { active })}>
<label>

View File

@@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import Upload from '../components/upload';
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
import { openModal } from '../../../actions/modal';
const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
@@ -13,7 +14,11 @@ const mapDispatchToProps = dispatch => ({
},
onDescriptionChange: (id, description) => {
dispatch(changeUploadCompose(id, description));
dispatch(changeUploadCompose(id, { description }));
},
onOpenFocalPoint: id => {
dispatch(openModal('FOCAL_POINT', { id }));
},
});

View File

@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { me } from '../../../initial_state';
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\w*[a-zA-Z]\w*)/i;
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),

View File

@@ -12,6 +12,7 @@ import Motion from '../ui/util/optional_motion';
import spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
@@ -94,7 +95,11 @@ export default class Compose extends React.PureComponent {
<div className='drawer__inner' onFocus={this.onFocus}>
<NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer />
{multiColumn && <div className='mastodon' />}
{multiColumn && (
<div className='drawer__inner__mastodon'>
<img alt='' src={elephantUIPlane} />
</div>
)}
</div>
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>

View File

@@ -0,0 +1,74 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusListContainer from '../../ui/containers/status_list_container';
import {
refreshCommunityTimeline,
expandCommunityTimeline,
} from '../../../actions/timelines';
import Column from '../../../components/column';
import ColumnHeader from '../../../components/column_header';
import { defineMessages, injectIntl } from 'react-intl';
import { connectCommunityStream } from '../../../actions/streaming';
const messages = defineMessages({
title: { id: 'standalone.public_title', defaultMessage: 'A look inside...' },
});
@connect()
@injectIntl
export default class CommunityTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleHeaderClick = () => {
this.column.scrollTop();
}
setRef = c => {
this.column = c;
}
componentDidMount () {
const { dispatch } = this.props;
dispatch(refreshCommunityTimeline());
this.disconnect = dispatch(connectCommunityStream());
}
componentWillUnmount () {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
}
handleLoadMore = () => {
this.props.dispatch(expandCommunityTimeline());
}
render () {
const { intl } = this.props;
return (
<Column ref={this.setRef}>
<ColumnHeader
icon='users'
title={intl.formatMessage(messages.title)}
onClick={this.handleHeaderClick}
/>
<StatusListContainer
timelineId='community'
loadMore={this.handleLoadMore}
scrollKey='standalone_public_timeline'
trackScroll={false}
/>
</Column>
);
}
}

View File

@@ -20,6 +20,39 @@ const getHostname = url => {
return parser.hostname;
};
const trim = (text, len) => {
const cut = text.indexOf(' ', len);
if (cut === -1) {
return text;
}
return text.substring(0, cut) + (text.length > len ? '…' : '');
};
const domParser = new DOMParser();
const addAutoPlay = html => {
const document = domParser.parseFromString(html, 'text/html').documentElement;
const iframe = document.querySelector('iframe');
if (iframe) {
if (iframe.src.indexOf('?') !== -1) {
iframe.src += '&';
} else {
iframe.src += '?';
}
iframe.src += 'autoplay=1&auto_play=1';
// DOM parser creates html/body elements around original HTML fragment,
// so we need to get innerHTML out of the body and not the entire document
return document.querySelector('body').innerHTML;
}
return html;
};
export default class Card extends React.PureComponent {
static propTypes = {
@@ -33,9 +66,16 @@ export default class Card extends React.PureComponent {
};
state = {
width: 0,
width: 280,
embedded: false,
};
componentWillReceiveProps (nextProps) {
if (this.props.card !== nextProps.card) {
this.setState({ embedded: false });
}
}
handlePhotoClick = () => {
const { card, onOpenMedia } = this.props;
@@ -57,56 +97,14 @@ export default class Card extends React.PureComponent {
);
};
renderLink () {
const { card, maxDescription } = this.props;
const { width } = this.state;
const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width);
let image = '';
let provider = card.get('provider_name');
if (card.get('image')) {
image = (
<div className='status-card__image'>
<img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' width={card.get('width')} height={card.get('height')} />
</div>
);
}
if (provider.length < 1) {
provider = decodeIDNA(getHostname(card.get('url')));
}
const className = classnames('status-card', { horizontal });
return (
<a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
{image}
<div className='status-card__content'>
<strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
{!horizontal && <p className='status-card__description'>{(card.get('description') || '').substring(0, maxDescription)}</p>}
<span className='status-card__host'>{provider}</span>
</div>
</a>
);
}
renderPhoto () {
handleEmbedClick = () => {
const { card } = this.props;
return (
<img
className='status-card-photo'
onClick={this.handlePhotoClick}
role='button'
tabIndex='0'
src={card.get('embed_url')}
alt={card.get('title')}
width={card.get('width')}
height={card.get('height')}
/>
);
if (card.get('type') === 'photo') {
this.handlePhotoClick();
} else {
this.setState({ embedded: true });
}
}
setRef = c => {
@@ -117,7 +115,7 @@ export default class Card extends React.PureComponent {
renderVideo () {
const { card } = this.props;
const content = { __html: card.get('html') };
const content = { __html: addAutoPlay(card.get('html')) };
const { width } = this.state;
const ratio = card.get('width') / card.get('height');
const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
@@ -125,7 +123,7 @@ export default class Card extends React.PureComponent {
return (
<div
ref={this.setRef}
className='status-card-video'
className='status-card__image status-card-video'
dangerouslySetInnerHTML={content}
style={{ height }}
/>
@@ -133,23 +131,76 @@ export default class Card extends React.PureComponent {
}
render () {
const { card } = this.props;
const { card, maxDescription } = this.props;
const { width, embedded } = this.state;
if (card === null) {
return null;
}
switch(card.get('type')) {
case 'link':
return this.renderLink();
case 'photo':
return this.renderPhoto();
case 'video':
return this.renderVideo();
case 'rich':
default:
return null;
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link';
const className = classnames('status-card', { horizontal });
const interactive = card.get('type') !== 'link';
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const ratio = card.get('width') / card.get('height');
const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
const description = (
<div className='status-card__content'>
{title}
{!horizontal && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
<span className='status-card__host'>{provider}</span>
</div>
);
let embed = '';
let thumbnail = <div style={{ backgroundImage: `url(${card.get('image')})`, width: horizontal ? width : null, height: horizontal ? height : null }} className='status-card__image-image' />;
if (interactive) {
if (embedded) {
embed = this.renderVideo();
} else {
let iconVariant = 'play';
if (card.get('type') === 'photo') {
iconVariant = 'search-plus';
}
embed = (
<div className='status-card__image'>
{thumbnail}
<div className='status-card__actions'>
<div>
<button onClick={this.handleEmbedClick}><i className={`fa fa-${iconVariant}`} /></button>
<a href={card.get('url')} target='_blank' rel='noopener'><i className='fa fa-external-link' /></a>
</div>
</div>
</div>
);
}
return (
<div className={className} ref={this.setRef}>
{embed}
{description}
</div>
);
} else if (card.get('image')) {
embed = (
<div className='status-card__image'>
{thumbnail}
</div>
);
}
return (
<a href={card.get('url')} className={className} target='_blank' rel='noopener' ref={this.setRef}>
{embed}
{description}
</a>
);
}
}

View File

@@ -57,6 +57,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
src={video.get('url')}
width={300}
height={150}
inline
onOpenVideo={this.handleOpenVideo}
sensitive={status.get('sensitive')}
/>

View File

@@ -26,7 +26,7 @@ class Bundle extends React.Component {
onFetchFail: noop,
}
static cache = {}
static cache = new Map
state = {
mod: undefined,
@@ -51,13 +51,12 @@ class Bundle extends React.Component {
load = (props) => {
const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
const cachedMod = Bundle.cache.get(fetchComponent);
onFetch();
if (Bundle.cache[fetchComponent.name]) {
const mod = Bundle.cache[fetchComponent.name];
this.setState({ mod: mod.default });
if (cachedMod) {
this.setState({ mod: cachedMod.default });
onFetchSuccess();
return Promise.resolve();
}
@@ -71,7 +70,7 @@ class Bundle extends React.Component {
return fetchComponent()
.then((mod) => {
Bundle.cache[fetchComponent.name] = mod;
Bundle.cache.set(fetchComponent, mod);
this.setState({ mod: mod.default });
onFetchSuccess();
})

View File

@@ -6,6 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeableViews from 'react-swipeable-views';
import { links, getIndex, getLink } from './tabs_bar';
import { Link } from 'react-router-dom';
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
@@ -152,11 +153,19 @@ export default class ColumnsArea extends ImmutablePureComponent {
this.pendingIndex = null;
if (singleColumn) {
return columnIndex !== -1 ? (
<ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
const floatingActionButton = this.context.router.history.location.pathname === '/statuses/new' ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button'><i className='fa fa-pencil' /></Link>;
return columnIndex !== -1 ? [
<ReactSwipeableViews key='content' index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
{links.map(this.renderView)}
</ReactSwipeableViews>
) : <div className='columns-area'>{children}</div>;
</ReactSwipeableViews>,
floatingActionButton,
] : [
<div className='columns-area'>{children}</div>,
floatingActionButton,
];
}
return (

View File

@@ -0,0 +1,122 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import ImageLoader from './image_loader';
import classNames from 'classnames';
import { changeUploadCompose } from '../../../actions/compose';
import { getPointerPosition } from '../../video';
const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
});
const mapDispatchToProps = (dispatch, { id }) => ({
onSave: (x, y) => {
dispatch(changeUploadCompose(id, { focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
},
});
@connect(mapStateToProps, mapDispatchToProps)
export default class FocalPointModal extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
};
state = {
x: 0,
y: 0,
focusX: 0,
focusY: 0,
dragging: false,
};
componentWillMount () {
this.updatePositionFromMedia(this.props.media);
}
componentWillReceiveProps (nextProps) {
if (this.props.media.get('id') !== nextProps.media.get('id')) {
this.updatePositionFromMedia(nextProps.media);
}
}
componentWillUnmount () {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
}
handleMouseDown = e => {
document.addEventListener('mousemove', this.handleMouseMove);
document.addEventListener('mouseup', this.handleMouseUp);
this.updatePosition(e);
this.setState({ dragging: true });
}
handleMouseMove = e => {
this.updatePosition(e);
}
handleMouseUp = () => {
document.removeEventListener('mousemove', this.handleMouseMove);
document.removeEventListener('mouseup', this.handleMouseUp);
this.setState({ dragging: false });
this.props.onSave(this.state.focusX, this.state.focusY);
}
updatePosition = e => {
const { x, y } = getPointerPosition(this.node, e);
const focusX = (x - .5) * 2;
const focusY = (y - .5) * -2;
this.setState({ x, y, focusX, focusY });
}
updatePositionFromMedia = media => {
const focusX = media.getIn(['meta', 'focus', 'x']);
const focusY = media.getIn(['meta', 'focus', 'y']);
if (focusX && focusY) {
const x = (focusX / 2) + .5;
const y = (focusY / -2) + .5;
this.setState({ x, y, focusX, focusY });
} else {
this.setState({ x: 0.5, y: 0.5, focusX: 0, focusY: 0 });
}
}
setRef = c => {
this.node = c;
}
render () {
const { media } = this.props;
const { x, y, dragging } = this.state;
const width = media.getIn(['meta', 'original', 'width']) || null;
const height = media.getIn(['meta', 'original', 'height']) || null;
return (
<div className='modal-root__modal media-modal'>
<div className={classNames('media-modal__content focal-point', { dragging })} ref={this.setRef}>
<ImageLoader
previewSrc={media.get('preview_url')}
src={media.get('url')}
width={width}
height={height}
/>
<div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
<div className='focal-point__overlay' onMouseDown={this.handleMouseDown} />
</div>
</div>
);
}
}

View File

@@ -8,6 +8,7 @@ import MediaModal from './media_modal';
import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal';
import FocalPointModal from './focal_point_modal';
import {
OnboardingModal,
MuteModal,
@@ -27,6 +28,7 @@ const MODAL_COMPONENTS = {
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
'EMBED': EmbedModal,
'LIST_EDITOR': ListEditor,
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
};
export default class ModalRoot extends React.PureComponent {

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
import { changeReportComment, submitReport } from '../../../actions/reports';
import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
import { refreshAccountTimeline } from '../../../actions/timelines';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -10,8 +10,11 @@ import StatusCheckBox from '../../report/containers/status_check_box_container';
import { OrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Button from '../../../components/button';
import Toggle from 'react-toggle';
import IconButton from '../../../components/icon_button';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
submit: { id: 'report.submit', defaultMessage: 'Submit' },
});
@@ -26,6 +29,7 @@ const makeMapStateToProps = () => {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: getAccount(state, accountId),
comment: state.getIn(['reports', 'new', 'comment']),
forward: state.getIn(['reports', 'new', 'forward']),
statusIds: OrderedSet(state.getIn(['timelines', `account:${accountId}`, 'items'])).union(state.getIn(['reports', 'new', 'status_ids'])),
};
};
@@ -42,14 +46,19 @@ export default class ReportModal extends ImmutablePureComponent {
account: ImmutablePropTypes.map,
statusIds: ImmutablePropTypes.orderedSet.isRequired,
comment: PropTypes.string.isRequired,
forward: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleCommentChange = (e) => {
handleCommentChange = e => {
this.props.dispatch(changeReportComment(e.target.value));
}
handleForwardChange = e => {
this.props.dispatch(changeReportForward(e.target.checked));
}
handleSubmit = () => {
this.props.dispatch(submitReport());
}
@@ -65,26 +74,25 @@ export default class ReportModal extends ImmutablePureComponent {
}
render () {
const { account, comment, intl, statusIds, isSubmitting } = this.props;
const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
if (!account) {
return null;
}
const domain = account.get('acct').split('@')[1];
return (
<div className='modal-root__modal report-modal'>
<div className='report-modal__target'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
<FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
</div>
<div className='report-modal__container'>
<div className='report-modal__statuses'>
<div>
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
</div>
</div>
<div className='report-modal__comment'>
<p><FormattedMessage id='report.hint' defaultMessage='The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:' /></p>
<textarea
className='setting-text light'
placeholder={intl.formatMessage(messages.placeholder)}
@@ -92,11 +100,26 @@ export default class ReportModal extends ImmutablePureComponent {
onChange={this.handleCommentChange}
disabled={isSubmitting}
/>
</div>
</div>
<div className='report-modal__action-bar'>
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
{domain && (
<div>
<p><FormattedMessage id='report.forward_hint' defaultMessage='The account is from another server. Send an anonymized copy of the report there as well?' /></p>
<div className='setting-toggle'>
<Toggle id='report-forward' checked={forward} disabled={isSubmitting} onChange={this.handleForwardChange} />
<label htmlFor='report-forward' className='setting-toggle__label'><FormattedMessage id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} /></label>
</div>
</div>
)}
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} />
</div>
<div className='report-modal__statuses'>
<div>
{statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
</div>
</div>
</div>
</div>
);

View File

@@ -6,14 +6,13 @@ import { debounce } from 'lodash';
import { isUserTouching } from '../../../is_mobile';
export const links = [
<NavLink className='tabs-bar__link primary' to='/statuses/new' data-preview-title-id='tabs_bar.compose' data-preview-icon='pencil' ><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></NavLink>,
<NavLink className='tabs-bar__link primary' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
<NavLink className='tabs-bar__link primary' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
<NavLink className='tabs-bar__link secondary' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
<NavLink className='tabs-bar__link secondary' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
<NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='asterisk' ><i className='fa fa-fw fa-asterisk' /></NavLink>,
<NavLink className='tabs-bar__link primary' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><i className='fa fa-fw fa-bars' /></NavLink>,
];
export function getIndex (path) {

View File

@@ -398,6 +398,7 @@ export default class UI extends React.Component {
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />

View File

@@ -35,14 +35,19 @@ export class WrappedRoute extends React.Component {
component: PropTypes.func.isRequired,
content: PropTypes.node,
multiColumn: PropTypes.bool,
}
componentParams: PropTypes.object,
};
static defaultProps = {
componentParams: {},
};
renderComponent = ({ match }) => {
const { component, content, multiColumn } = this.props;
const { component, content, multiColumn, componentParams } = this.props;
return (
<BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}>
{Component => <Component params={match.params} multiColumn={multiColumn}>{content}</Component>}
{Component => <Component params={match.params} multiColumn={multiColumn} {...componentParams}>{content}</Component>}
</BundleContainer>
);
}

View File

@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { throttle } from 'lodash';
import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
import { displaySensitiveMedia } from '../../initial_state';
const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' },
@@ -29,7 +30,7 @@ const formatTime = secondsNum => {
return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
};
const findElementPosition = el => {
export const findElementPosition = el => {
let box;
if (el.getBoundingClientRect && el.parentNode) {
@@ -60,7 +61,7 @@ const findElementPosition = el => {
};
};
const getPointerPosition = (el, event) => {
export const getPointerPosition = (el, event) => {
const position = {};
const box = findElementPosition(el);
const boxW = el.offsetWidth;
@@ -76,7 +77,7 @@ const getPointerPosition = (el, event) => {
pageY = event.changedTouches[0].pageY;
}
position.y = Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
position.y = Math.max(0, Math.min(1, (pageY - boxY) / boxH));
position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
return position;
@@ -96,6 +97,7 @@ export default class Video extends React.PureComponent {
onOpenVideo: PropTypes.func,
onCloseVideo: PropTypes.func,
detailed: PropTypes.bool,
inline: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
@@ -104,14 +106,21 @@ export default class Video extends React.PureComponent {
duration: 0,
paused: true,
dragging: false,
containerWidth: false,
fullscreen: false,
hovered: false,
muted: false,
revealed: !this.props.sensitive,
revealed: !this.props.sensitive || displaySensitiveMedia,
};
setPlayerRef = c => {
this.player = c;
if (c) {
this.setState({
containerWidth: c.offsetWidth,
});
}
}
setVideoRef = c => {
@@ -245,12 +254,23 @@ export default class Video extends React.PureComponent {
}
render () {
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = (currentTime / duration) * 100;
const playerStyle = {};
let { width, height } = this.props;
if (inline && containerWidth) {
width = containerWidth;
height = containerWidth / (16/9);
playerStyle.width = width;
playerStyle.height = height;
}
return (
<div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<div className={classNames('video-player', { inactive: !revealed, detailed, inline: inline && !fullscreen, fullscreen })} style={playerStyle} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<video
ref={this.setVideoRef}
src={src}
@@ -270,7 +290,7 @@ export default class Video extends React.PureComponent {
onProgress={this.handleProgress}
/>
<button className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
<button type='button' className={classNames('video-player__spoiler', { active: !revealed })} onClick={this.toggleReveal}>
<span className='video-player__spoiler__title'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
<span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</button>
@@ -289,10 +309,10 @@ export default class Video extends React.PureComponent {
<div className='video-player__buttons-bar'>
<div className='video-player__buttons left'>
<button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
<button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
{!onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
{(detailed || fullscreen) &&
<span>
@@ -304,9 +324,9 @@ export default class Video extends React.PureComponent {
</div>
<div className='video-player__buttons right'>
{(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
{onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
<button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
</div>
</div>
</div>

View File

@@ -5,9 +5,11 @@ const getMeta = (prop) => initialState && initialState.meta && initialState.meta
export const reduceMotion = getMeta('reduce_motion');
export const autoPlayGif = getMeta('auto_play_gif');
export const displaySensitiveMedia = getMeta('display_sensitive_media');
export const unfollowModal = getMeta('unfollow_modal');
export const boostModal = getMeta('boost_modal');
export const deleteModal = getMeta('delete_modal');
export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export default initialState;

View File

@@ -13,7 +13,8 @@
"account.moved_to": "{name} إنتقل إلى :",
"account.mute": "أكتم @{name}",
"account.mute_notifications": "كتم إخطارات @{name}",
"account.posts": "المشاركات",
"account.posts": "التبويقات",
"account.posts_with_replies": "Toots with replies",
"account.report": "أبلغ عن @{name}",
"account.requested": "في انتظار الموافقة",
"account.share": "مشاركة @{name}'s profile",
@@ -50,7 +51,7 @@
"column_header.unpin": "فك التدبيس",
"column_subheading.navigation": "التصفح",
"column_subheading.settings": "الإعدادات",
"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": "هذا التبويق لن يُدرَج تحت أي وسم كان بما أنه غير مُدرَج. لا يُسمح بالبحث إلّا عن التبويقات العمومية عن طريق الوسوم.",
"compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
"compose_form.lock_disclaimer.lock": "مقفل",
"compose_form.placeholder": "فيمَ تفكّر؟",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "إلغاء",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "تعليقات إضافية",
"report.submit": "إرسال",
"report.target": "إبلاغ",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "حالة",
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
"search_popout.tips.user": "مستخدِم",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
"standalone.public_title": "نظرة على ...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "إسحب ثم أفلت للرفع",
"upload_button.label": "إضافة وسائط",
"upload_form.description": "وصف للمعاقين بصريا",
"upload_form.focus": "Crop",
"upload_form.undo": "إلغاء",
"upload_progress.label": "يرفع...",
"video.close": "إغلاق الفيديو",

View File

@@ -14,6 +14,7 @@
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Публикации",
"account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}",
"account.requested": "В очакване на одобрение",
"account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Отказ",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Reporting",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Добави медия",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Отмяна",
"upload_progress.label": "Uploading...",
"video.close": "Close video",

View File

@@ -1,29 +1,30 @@
{
"account.block": "Bloquejar @{name}",
"account.block_domain": "Amagar tot de {domain}",
"account.block": "Bloca @{name}",
"account.block_domain": "Amaga-ho tot de {domain}",
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
"account.edit_profile": "Editar perfil",
"account.follow": "Seguir",
"account.edit_profile": "Edita el perfil",
"account.follow": "Segueix",
"account.followers": "Seguidors",
"account.follows": "Seguint",
"account.follows_you": "et segueix",
"account.follows_you": "Et segueix",
"account.hide_reblogs": "Amaga els impulsos de @{name}",
"account.media": "Media",
"account.mention": "Esmentar @{name}",
"account.moved_to": "{name} s'ha mogut a:",
"account.mute": "Silenciar @{name}",
"account.mute": "Silencia @{name}",
"account.mute_notifications": "Notificacions desactivades de @{name}",
"account.posts": "Publicacions",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Informe @{name}",
"account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
"account.share": "Compartir el perfil de @{name}",
"account.share": "Comparteix el perfil de @{name}",
"account.show_reblogs": "Mostra els impulsos de @{name}",
"account.unblock": "Desbloquejar @{name}",
"account.unblock": "Desbloca @{name}",
"account.unblock_domain": "Mostra {domain}",
"account.unfollow": "Deixar de seguir",
"account.unfollow": "Deixa de seguir",
"account.unmute": "Treure silenci de @{name}",
"account.unmute_notifications": "Activar notificacions de @{name}",
"account.view_full_profile": "Veure el perfil complet",
"account.view_full_profile": "Mostra el perfil complet",
"boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
"bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
"bundle_column_error.retry": "Torna-ho a provar",
@@ -31,7 +32,7 @@
"bundle_modal_error.close": "Tanca",
"bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
"bundle_modal_error.retry": "Torna-ho a provar",
"column.blocks": "Usuaris bloquejats",
"column.blocks": "Usuaris blocats",
"column.community": "Línia de temps local",
"column.favourites": "Favorits",
"column.follow_requests": "Peticions per seguir-te",
@@ -45,46 +46,46 @@
"column_header.hide_settings": "Amaga la configuració",
"column_header.moveLeft_settings": "Mou la columna cap a l'esquerra",
"column_header.moveRight_settings": "Mou la columna cap a la dreta",
"column_header.pin": "Fixar",
"column_header.pin": "Fixa",
"column_header.show_settings": "Mostra la configuració",
"column_header.unpin": "Deslligar",
"column_header.unpin": "No fixis",
"column_subheading.navigation": "Navegació",
"column_subheading.settings": "Configuració",
"compose_form.hashtag_warning": "Aquest toot no es mostrarà en cap etiqueta ja que no està llistat. Només els toots públics poden ser cercats per etiqueta.",
"compose_form.lock_disclaimer": "El teu compte no està bloquejat {locked}. Tothom pot seguir-te i veure els teus missatges a seguidors.",
"compose_form.lock_disclaimer.lock": "bloquejat",
"compose_form.lock_disclaimer.lock": "blocat",
"compose_form.placeholder": "En què estàs pensant?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar multimèdia com a sensible",
"compose_form.spoiler": "Amagar text darrera l'advertència",
"compose_form.spoiler_placeholder": "Escriu l'advertència aquí",
"confirmation_modal.cancel": "Cancel·lar",
"confirmations.block.confirm": "Bloquejar",
"confirmations.block.message": "Estàs segur que vols bloquejar {name}?",
"confirmations.delete.confirm": "Esborrar",
"confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?",
"confirmations.domain_block.confirm": "Amagar tot el domini",
"confirmations.domain_block.message": "Estàs realment, realment segur que vols bloquejar totalment {domain}? En la majoria dels casos bloquejar o silenciar és suficient i preferible.",
"confirmations.mute.confirm": "Silenciar",
"compose_form.sensitive": "Marca el contingut multimèdia com a sensible",
"compose_form.spoiler": "Amaga el text darrera darrere un avís",
"compose_form.spoiler_placeholder": "Escriu l'avís aquí",
"confirmation_modal.cancel": "Cancel·la",
"confirmations.block.confirm": "Bloca",
"confirmations.block.message": "Estàs segur que vols blocar {name}?",
"confirmations.delete.confirm": "Suprimeix",
"confirmations.delete.message": "Estàs segur que vols suprimir aquest estat?",
"confirmations.delete_list.confirm": "Suprimeix",
"confirmations.delete_list.message": "Estàs segur que vols suprimir permanentment aquesta llista?",
"confirmations.domain_block.confirm": "Amaga tot el domini",
"confirmations.domain_block.message": "Estàs realment, realment segur que vols blocar totalment {domain}? En la majoria dels casos blocar o silenciar uns pocs objectius és suficient i preferible.",
"confirmations.mute.confirm": "Silencia",
"confirmations.mute.message": "Estàs segur que vols silenciar {name}?",
"confirmations.unfollow.confirm": "Deixar de seguir",
"confirmations.unfollow.confirm": "Deixa de seguir",
"confirmations.unfollow.message": "Estàs segur que vols deixar de seguir {name}?",
"embed.instructions": "Incrusta aquest estat al lloc web copiant el codi a continuació.",
"embed.preview": "Aquí tenim quin aspecte tindrá:",
"emoji_button.activity": "Activitat",
"emoji_button.custom": "Personalitzat",
"emoji_button.flags": "Marques",
"emoji_button.food": "Menjar i Beure",
"emoji_button.label": "Inserir emoji",
"emoji_button.flags": "Banderes",
"emoji_button.food": "Menjar i beure",
"emoji_button.label": "Insereix un emoji",
"emoji_button.nature": "Natura",
"emoji_button.not_found": "Emojos no!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objectes",
"emoji_button.people": "Gent",
"emoji_button.recent": "Freqüentment utilitzat",
"emoji_button.search": "Cercar...",
"emoji_button.recent": "Usats freqüentment",
"emoji_button.search": "Cerca...",
"emoji_button.search_results": "Resultats de la cerca",
"emoji_button.symbols": "Símbols",
"emoji_button.travel": "Viatges i Llocs",
@@ -207,6 +208,9 @@
"relative_time.minutes": "fa {number} minuts",
"relative_time.seconds": "fa {number} segons",
"reply_indicator.cancel": "Cancel·lar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Comentaris addicionals",
"report.submit": "Enviar",
"report.target": "Informes",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags",
"search_popout.tips.user": "usuari",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
"standalone.public_title": "Una mirada a l'interior ...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arrossega i deixa anar per carregar",
"upload_button.label": "Afegir multimèdia",
"upload_form.description": "Descriure els problemes visuals",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfer",
"upload_progress.label": "Pujant...",
"video.close": "Tancar el vídeo",

View File

@@ -14,6 +14,7 @@
"account.mute": "@{name} stummschalten",
"account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
"account.posts": "Beiträge",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} melden",
"account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen",
"account.share": "Profil von @{name} teilen",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Abbrechen",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Zusätzliche Kommentare",
"report.submit": "Absenden",
"report.target": "{target} melden",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
"standalone.public_title": "Ein kleiner Einblick …",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Zum Hochladen hereinziehen",
"upload_button.label": "Mediendatei hinzufügen",
"upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",
"upload_form.focus": "Crop",
"upload_form.undo": "Entfernen",
"upload_progress.label": "Wird hochgeladen …",
"video.close": "Video schließen",

View File

@@ -317,12 +317,20 @@
},
{
"descriptors": [
{
"defaultMessage": "Toots",
"id": "account.posts"
},
{
"defaultMessage": "Toots with replies",
"id": "account.posts_with_replies"
},
{
"defaultMessage": "Media",
"id": "account.media"
}
],
"path": "app/javascript/mastodon/features/account_gallery/index.json"
"path": "app/javascript/mastodon/features/account_timeline/components/header.json"
},
{
"descriptors": [
@@ -433,7 +441,7 @@
"id": "account.view_full_profile"
},
{
"defaultMessage": "Posts",
"defaultMessage": "Toots",
"id": "account.posts"
},
{
@@ -650,6 +658,18 @@
},
{
"descriptors": [
{
"defaultMessage": "People",
"id": "search_results.accounts"
},
{
"defaultMessage": "Toots",
"id": "search_results.statuses"
},
{
"defaultMessage": "Hashtags",
"id": "search_results.hashtags"
},
{
"defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
"id": "search_results.total"
@@ -706,13 +726,17 @@
},
{
"descriptors": [
{
"defaultMessage": "Describe for the visually impaired",
"id": "upload_form.description"
},
{
"defaultMessage": "Undo",
"id": "upload_form.undo"
},
{
"defaultMessage": "Describe for the visually impaired",
"id": "upload_form.description"
"defaultMessage": "Crop",
"id": "upload_form.focus"
}
],
"path": "app/javascript/mastodon/features/compose/components/upload.json"
@@ -1230,6 +1254,15 @@
],
"path": "app/javascript/mastodon/features/public_timeline/index.json"
},
{
"descriptors": [
{
"defaultMessage": "A look inside...",
"id": "standalone.public_title"
}
],
"path": "app/javascript/mastodon/features/standalone/community_timeline/index.json"
},
{
"descriptors": [
{
@@ -1554,6 +1587,18 @@
{
"defaultMessage": "Report {target}",
"id": "report.target"
},
{
"defaultMessage": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"id": "report.hint"
},
{
"defaultMessage": "The account is from another server. Send an anonymized copy of the report there as well?",
"id": "report.forward_hint"
},
{
"defaultMessage": "Forward to {target}",
"id": "report.forward"
}
],
"path": "app/javascript/mastodon/features/ui/components/report_modal.json"

View File

@@ -13,7 +13,8 @@
"account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Posts",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request",
"account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancel",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Additional comments",
"report.submit": "Submit",
"report.target": "Reporting {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Undo",
"upload_progress.label": "Uploading...",
"video.close": "Close video",

View File

@@ -1,236 +1,243 @@
{
"account.block": "Bloki @{name}",
"account.block_domain": "Kaŝi ĉion el {domain}",
"account.disclaimer_full": "La ĉi-subaj informoj povas ne plene reflekti la profilon de la uzanto.",
"account.edit_profile": "Redakti la profilon",
"account.block_domain": "Kaŝi ĉion de {domain}",
"account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
"account.edit_profile": "Redakti profilon",
"account.follow": "Sekvi",
"account.followers": "Sekvantoj",
"account.follows": "Sekvatoj",
"account.follows_you": "Sekvas vin",
"account.hide_reblogs": "Maski diskonigitaĵojn de @{name}",
"account.media": "Sonbildaĵoj",
"account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
"account.media": "Aŭdovidaĵoj",
"account.mention": "Mencii @{name}",
"account.moved_to": "{name} movis al:",
"account.moved_to": "{name} moviĝis al:",
"account.mute": "Silentigi @{name}",
"account.mute_notifications": "Silentigi sciigojn el @{name}",
"account.posts": "Mesaĝoj",
"account.posts": "Hupoj",
"account.posts_with_replies": "Toots with replies",
"account.report": "Signali @{name}",
"account.requested": "Atendas aprobon",
"account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado",
"account.share": "Diskonigi la profilon de @{name}",
"account.show_reblogs": "Montri diskonigojn de @{name}",
"account.show_reblogs": "Montri diskonigojn de @{name}",
"account.unblock": "Malbloki @{name}",
"account.unblock_domain": "Malkaŝi {domain}",
"account.unfollow": "Ne plus sekvi",
"account.unfollow": "Ne plu sekvi",
"account.unmute": "Malsilentigi @{name}",
"account.unmute_notifications": "Malsilentigi sciigojn de @{name}",
"account.view_full_profile": "Vidi plenan profilon",
"boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi",
"bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.",
"boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
"bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
"bundle_column_error.retry": "Bonvolu reprovi",
"bundle_column_error.title": "Reta eraro",
"bundle_modal_error.close": "Fermi",
"bundle_modal_error.message": "Io malfunkciis ŝargante tiun ĉi komponanton.",
"bundle_modal_error.message": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
"bundle_modal_error.retry": "Bonvolu reprovi",
"column.blocks": "Blokitaj uzantoj",
"column.community": "Loka tempolinio",
"column.favourites": "Favoritoj",
"column.follow_requests": "Abonpetoj",
"column.favourites": "Stelumoj",
"column.follow_requests": "Petoj de sekvado",
"column.home": "Hejmo",
"column.lists": "Listoj",
"column.mutes": "Silentigitaj uzantoj",
"column.notifications": "Sciigoj",
"column.pins": "Alpinglitaj pepoj",
"column.pins": "Alpinglitaj mesaĝoj",
"column.public": "Fratara tempolinio",
"column_back_button.label": "Reveni",
"column_header.hide_settings": "Kaŝi agordojn",
"column_header.moveLeft_settings": "Movi kolumnon maldekstren",
"column_header.moveRight_settings": "Movi kolumnon dekstren",
"column_header.pin": "Alpingli",
"column_header.show_settings": "Malkaŝi agordojn",
"column_header.show_settings": "Montri agordojn",
"column_header.unpin": "Depingli",
"column_subheading.navigation": "Navigado",
"column_subheading.settings": "Agordoj",
"compose_form.hashtag_warning": "Ĉi tiu pepo ne estos listigita en iu ajn kradvorta listo pro ĝia videbleco estas “eksterlista”. Nur publikaj pepoj povas esti kradvorte trovitaj.",
"compose_form.lock_disclaimer": "Via konta ne estas ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.",
"compose_form.hashtag_warning": "Ĉi tiu mesaĝo ne estos listigita per ajna kradvorto. Nur publikaj mesaĝoj estas serĉeblaj per kradvortoj.",
"compose_form.lock_disclaimer": "Via konta ne estas {locked}. Iu ajn povas sekvi vin por vidi viajn mesaĝojn nur por sekvantoj.",
"compose_form.lock_disclaimer.lock": "ŝlosita",
"compose_form.placeholder": "Pri kio vi pensas?",
"compose_form.publish": "Hup",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marki ke la enhavo estas tikla",
"compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
"compose_form.spoiler_placeholder": "Skribu tie vian averton",
"confirmation_modal.cancel": "Malfari",
"compose_form.sensitive": "Marki aŭdovidaĵon tikla",
"compose_form.spoiler": "Kaŝi tekston malantaŭ averto",
"compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie",
"confirmation_modal.cancel": "Nuligi",
"confirmations.block.confirm": "Bloki",
"confirmations.block.message": "Ĉu vi konfirmas la blokadon de {name}?",
"confirmations.delete.confirm": "Malaperigi",
"confirmations.delete.message": "Ĉu vi konfirmas la malaperigon de tiun pepon?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Ĉu vi certas forviŝi ĉi tiun liston por ĉiam?",
"confirmations.domain_block.confirm": "Kaŝi la tutan reton",
"confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas bloki {domain} tute? Plej ofte, kelkaj celitaj blokadoj aŭ silentigoj estas sufiĉaj kaj preferindaj.",
"confirmations.block.message": "Ĉu vi certas, ke vi volas bloki {name}?",
"confirmations.delete.confirm": "Forigi",
"confirmations.delete.message": "Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon?",
"confirmations.delete_list.confirm": "Forigi",
"confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
"confirmations.domain_block.confirm": "Kaŝi la tutan domajnon",
"confirmations.domain_block.message": "Ĉu vi vere, vere certas, ke vi volas tute bloki {domain}? Plej ofte, trafa blokado kaj silentigado sufiĉas kaj preferindas.",
"confirmations.mute.confirm": "Silentigi",
"confirmations.mute.message": "Ĉu vi konfirmas la silentigon de {name}?",
"confirmations.mute.message": "Ĉu vi certas, ke vi volas silentigi {name}?",
"confirmations.unfollow.confirm": "Ne plu sekvi",
"confirmations.unfollow.message": "Ĉu vi volas ĉesi sekvi {name}?",
"embed.instructions": "Enmetu tiun statkonigon ĉe vian retejon kopiante la ĉi-suban kodon.",
"confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
"embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
"embed.preview": "Ĝi aperos tiel:",
"emoji_button.activity": "Aktivecoj",
"emoji_button.custom": "Personaj",
"emoji_button.activity": "Agadoj",
"emoji_button.custom": "Propraj",
"emoji_button.flags": "Flagoj",
"emoji_button.food": "Manĝi kaj trinki",
"emoji_button.label": "Enmeti mieneton",
"emoji_button.label": "Enmeti emoĝion",
"emoji_button.nature": "Naturo",
"emoji_button.not_found": "Neniuj mienetoj!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objektoj",
"emoji_button.not_found": "Neniu emoĝio!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "oj",
"emoji_button.people": "Homoj",
"emoji_button.recent": "Ofte uzataj",
"emoji_button.search": "Serĉo…",
"emoji_button.search_results": "Rezultatoj de serĉo",
"emoji_button.search_results": "Serĉaj rezultoj",
"emoji_button.symbols": "Simboloj",
"emoji_button.travel": "Vojaĝoj & lokoj",
"emoji_button.travel": "Vojaĝoj kaj lokoj",
"empty_column.community": "La loka tempolinio estas malplena. Skribu ion por plenigi ĝin!",
"empty_column.hashtag": "Ĝise, neniu enhavo estas asociita kun tiu kradvorto.",
"empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
"empty_column.home": "Via hejma tempolinio estas malplena! Vizitu {public} aŭ uzu la serĉilon por renkonti aliajn uzantojn.",
"empty_column.home.public_timeline": "la publika tempolinio",
"empty_column.list": "Estas ankoraŭ nenio en ĉi tiu listo. Tuj kiam anoj de ĉi tiu listo publikigos, ties pepoj aperos ĉi tie.",
"empty_column.notifications": "Vi dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.",
"empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj instancoj por plenigi la publikan tempolinion",
"follow_request.authorize": "Akcepti",
"empty_column.list": "Ankoraŭ estas nenio en ĉi tiu listo. Kiam membroj de ĉi tiu listo afiŝos novajn mesaĝojn, ili aperos ĉi tie.",
"empty_column.notifications": "Vi ankoraŭ ne havas sciigojn. Interagu kun aliaj por komenci konversacion.",
"empty_column.public": "Estas nenio ĉi tie! Publike skribu ion, aŭ mane sekvu uzantojn de aliaj nodoj por plenigi la publikan tempolinion",
"follow_request.authorize": "Rajtigi",
"follow_request.reject": "Rifuzi",
"getting_started.appsshort": "Aplikaĵoj",
"getting_started.faq": "Oftaj demandoj",
"getting_started.heading": "Por komenci",
"getting_started.open_source_notice": "Mastodono estas malfermkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
"getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en GitHub je {github}.",
"getting_started.userguide": "Gvidilo de uzo",
"home.column_settings.advanced": "Precizaj agordoj",
"home.column_settings.basic": "Bazaj agordoj",
"home.column_settings.filter_regex": "Forfiltri per regulesprimo",
"home.column_settings.filter_regex": "Filtri per regulesprimoj",
"home.column_settings.show_reblogs": "Montri diskonigojn",
"home.column_settings.show_replies": "Montri respondojn",
"home.settings": "Agordoj de la kolumno",
"keyboard_shortcuts.back": "reeniri",
"keyboard_shortcuts.boost": "diskonigi",
"keyboard_shortcuts.column": "fokusigi statuson en unu el la columnoj",
"keyboard_shortcuts.compose": "por fokusigi la redaktujon",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "subenmovi en la listo",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "ŝatitaren",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"home.settings": "Kolumnaj agordoj",
"keyboard_shortcuts.back": "por reveni",
"keyboard_shortcuts.boost": "por diskonigi",
"keyboard_shortcuts.column": "por fokusigi mesaĝon en unu el la kolumnoj",
"keyboard_shortcuts.compose": "por fokusigi la tekstujon",
"keyboard_shortcuts.description": "Priskribo",
"keyboard_shortcuts.down": "por iri suben en la listo",
"keyboard_shortcuts.enter": "por malfermi mesaĝon",
"keyboard_shortcuts.favourite": "por stelumi",
"keyboard_shortcuts.heading": "Klavaraj mallongigoj",
"keyboard_shortcuts.hotkey": "Rapidklavo",
"keyboard_shortcuts.legend": "por montri ĉi tiun legendon",
"keyboard_shortcuts.mention": "por sciigi ties aŭtoron",
"keyboard_shortcuts.legend": "por montri ĉi tiun noton",
"keyboard_shortcuts.mention": "por mencii la aŭtoron",
"keyboard_shortcuts.reply": "por respondi",
"keyboard_shortcuts.search": "por fokusigi la serĉadon",
"keyboard_shortcuts.toot": "por ekredakti tute novan pepon",
"keyboard_shortcuts.unfocus": "por malfokusigi la redaktujon aŭ la serĉilon",
"keyboard_shortcuts.up": "por suprenmovi en la listo",
"keyboard_shortcuts.search": "por fokusigi la serĉilon",
"keyboard_shortcuts.toot": "por komenci tute novan mesaĝon",
"keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon",
"keyboard_shortcuts.up": "por iri supren en la listo",
"lightbox.close": "Fermi",
"lightbox.next": "Malantaŭa",
"lightbox.next": "Sekva",
"lightbox.previous": "Antaŭa",
"lists.account.add": "Aldoni al la listo",
"lists.account.remove": "Forviŝi de la listo",
"lists.delete": "Delete list",
"lists.account.remove": "Forigi de la listo",
"lists.delete": "Forigi la liston",
"lists.edit": "Redakti la liston",
"lists.new.create": "Aldoni liston",
"lists.new.title_placeholder": "Titulo de la nova listo",
"lists.search": "Serĉi el la homoj kiujn vi sekvas",
"lists.new.title_placeholder": "Titolo de la nova listo",
"lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
"lists.subheading": "Viaj listoj",
"loading_indicator.label": "Ŝarganta…",
"media_gallery.toggle_visible": "Baskuli videblecon",
"loading_indicator.label": "Ŝargado…",
"media_gallery.toggle_visible": "Baskuligi videblecon",
"missing_indicator.label": "Ne trovita",
"missing_indicator.sublabel": "Ĉi tiu rimedo ne troviĝis",
"mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el tiu ĉi uzanto?",
"missing_indicator.sublabel": "Ĉi tiu rimedo ne estis trovita",
"mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?",
"navigation_bar.blocks": "Blokitaj uzantoj",
"navigation_bar.community_timeline": "Loka tempolinio",
"navigation_bar.edit_profile": "Redakti la profilon",
"navigation_bar.favourites": "Favoritaj",
"navigation_bar.follow_requests": "Abonpetoj",
"navigation_bar.info": "Plia informo",
"navigation_bar.keyboard_shortcuts": "Klavmallongigo",
"navigation_bar.edit_profile": "Redakti profilon",
"navigation_bar.favourites": "Stelumoj",
"navigation_bar.follow_requests": "Petoj de sekvado",
"navigation_bar.info": "Pri ĉiu tiu nodo",
"navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
"navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Elsaluti",
"navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.pins": "Alpinglitaj pepoj",
"navigation_bar.pins": "Alpinglitaj mesaĝoj",
"navigation_bar.preferences": "Preferoj",
"navigation_bar.public_timeline": "Fratara tempolinio",
"notification.favourite": "{name} favoris vian mesaĝon",
"notification.follow": "{name} sekvis vin",
"notification.favourite": "{name} stelumis vian mesaĝon",
"notification.follow": "{name} eksekvis vin",
"notification.mention": "{name} menciis vin",
"notification.reblog": "{name} diskonigis vian mesaĝon",
"notifications.clear": "Forviŝi la sciigojn",
"notifications.clear_confirmation": "Ĉu vi certe volas malaperigi ĉiujn viajn sciigojn?",
"notifications.column_settings.alert": "Retumilaj atentigoj",
"notifications.column_settings.favourite": "Favoritoj:",
"notifications.clear": "Forviŝi sciigojn",
"notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?",
"notifications.column_settings.alert": "Retumilaj sciigoj",
"notifications.column_settings.favourite": "Stelumoj:",
"notifications.column_settings.follow": "Novaj sekvantoj:",
"notifications.column_settings.mention": "Mencioj:",
"notifications.column_settings.push": "Puŝsciigoj",
"notifications.column_settings.push_meta": "Tiu ĉi aparato",
"notifications.column_settings.push_meta": "Ĉi tiu aparato",
"notifications.column_settings.reblog": "Diskonigoj:",
"notifications.column_settings.show": "Montri en kolono",
"notifications.column_settings.show": "Montri en kolumno",
"notifications.column_settings.sound": "Eligi sonon",
"onboarding.done": "Farita",
"onboarding.next": "Malantaŭa",
"onboarding.page_five.public_timelines": "La loka tempolinio enhavas mesaĝojn de ĉiuj ĉe {domain}. La federacia tempolinio enhavas ĉiujn mesaĝojn de uzantoj, kiujn iu ĉe {domain} sekvas. Ambaŭ tre utilas por trovi novajn kunparolantojn.",
"onboarding.page_four.home": "La hejma tempolinio enhavas la mesaĝojn de ĉiuj uzantoj, kiuj vi sekvas.",
"onboarding.page_four.notifications": "La sciiga kolumno informas vin kiam iu interagas kun vi.",
"onboarding.page_one.federation": "Mastodono estas reto de nedependaj serviloj, unuiĝintaj por krei pligrandan socian retejon. Ni nomas tiujn servilojn instancoj.",
"onboarding.page_one.full_handle": "Via tuta uzantnomo",
"onboarding.page_one.handle_hint": "Jen kion vi dirintus al viaj amikoj por serĉi.",
"onboarding.page_one.welcome": "Bonvenon al Mastodono!",
"onboarding.page_six.admin": "Via instancestro estas {admin}.",
"onboarding.page_six.almost_done": "Estas preskaŭ finita…",
"onboarding.page_six.appetoot": "Bonan apepiton!",
"onboarding.page_six.apps_available": "{apps} estas elŝuteblaj por iOS, Androido kaj alioj.",
"onboarding.page_six.github": "Mastodono estas libera, senpaga kaj malfermkoda programaro. Vi povas signali cimojn, proponi funkciojn aŭ kontribui al a kreskado ĉe {github}.",
"onboarding.page_six.guidelines": "komunreguloj",
"onboarding.page_six.read_guidelines": "Ni petas vin: ne forgesu legi la {guidelines}n de {domain}!",
"onboarding.page_six.various_app": "telefon-aplikaĵoj",
"onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian avataron, priskribon kaj vian nomon. Vi tie trovos ankoraŭ aliajn agordojn.",
"onboarding.page_three.search": "Uzu la serĉokampo por trovi uzantojn kaj esplori kradvortojn tiel ke {illustration} kaj {introductions}. Por trovi iun, kiu ne estas ĉe ĉi tiu instanco, uzu ĝian kompletan uznomon.",
"onboarding.page_two.compose": "Skribu pepojn en la verkkolumno. Vi povas aldoni bildojn, ŝanĝi la agordojn de privateco kaj aldoni tiklavertojn content warning») dank' al la piktogramoj malsupre.",
"onboarding.skip": "Pasigi",
"privacy.change": "Alĝustigi la privateco de la mesaĝo",
"privacy.direct.long": "Vidigi nur al la menciitaj personoj",
"onboarding.next": "Sekva",
"onboarding.page_five.public_timelines": "La loka tempolinio montras publikajn mesaĝojn de ĉiuj en {domain}. La fratara tempolinio montras publikajn mesaĝojn de ĉiuj, kiuj estas sekvataj de homoj en {domain}. Tio estas la publikaj tempolinioj, kio estas bona maniero por malkovri novajn homojn.",
"onboarding.page_four.home": "La hejma tempolinio montras mesaĝojn de ĉiuj uzantoj, kiujn vi sekvas.",
"onboarding.page_four.notifications": "La sciiga kolumno montras kiam iu interagas kun vi.",
"onboarding.page_one.federation": "Mastodon estas reto de sendependaj serviloj, unuiĝintaj por krei pligrandan socian reton. Ni nomas tiujn servilojn nodoj.",
"onboarding.page_one.full_handle": "Via kompleta uzantnomo",
"onboarding.page_one.handle_hint": "Jen kion vi petus al viaj amikoj serĉi.",
"onboarding.page_one.welcome": "Bonvenon en Mastodon!",
"onboarding.page_six.admin": "Via noda administranto estas {admin}.",
"onboarding.page_six.almost_done": "Preskaŭ finita…",
"onboarding.page_six.appetoot": "Saĝan mesaĝadon!",
"onboarding.page_six.apps_available": "{apps} estas disponeblaj por iOS, Android kaj aliaj platformoj.",
"onboarding.page_six.github": "Mastodon estas libera, senpaga kaj malfermitkoda programo. Vi povas raporti cimojn, proponi funkciojn aŭ kontribui al la kodo en {github}.",
"onboarding.page_six.guidelines": "komunumaj gvidlinioj",
"onboarding.page_six.read_guidelines": "Bonvolu atenti pri la {guidelines} de {domain}!",
"onboarding.page_six.various_app": "telefonaj aplikaĵoj",
"onboarding.page_three.profile": "Redaktu vian profilon por ŝanĝi vian profilbildon, priskribon kaj nomon. Vi ankaŭ trovos tie aliajn agordojn.",
"onboarding.page_three.search": "Uzu la serĉilon por trovi uzantojn kaj esplori kradvortojn, tiel {illustration} kaj {introductions}. Por trovi iun, kiu ne estas en ĉi tiu nodo, uzu ties kompletan uzantnomon.",
"onboarding.page_two.compose": "Skribu mesaĝojn en la skriba kolumno. Vi povas alŝuti bildojn, ŝanĝi privatecajn agordojn, kaj aldoni avertojn pri la enhavo per la subaj bildetoj.",
"onboarding.skip": "Preterpasi",
"privacy.change": "Agordi mesaĝan privatecon",
"privacy.direct.long": "Afiŝi nur al menciitaj uzantoj",
"privacy.direct.short": "Rekta",
"privacy.private.long": "Vidigi nur al viaj sekvantoj",
"privacy.private.short": "Nursekvanta",
"privacy.public.long": "Vidigi en publikaj tempolinioj",
"privacy.private.long": "Afiŝi nur al sekvantoj",
"privacy.private.short": "Nur por sekvantoj",
"privacy.public.long": "Afiŝi en publikaj tempolinioj",
"privacy.public.short": "Publika",
"privacy.unlisted.long": "Ne vidigi en publikaj tempolinioj",
"privacy.unlisted.long": "Ne afiŝi en publikaj tempolinioj",
"privacy.unlisted.short": "Nelistigita",
"regeneration_indicator.label": "Elŝultanta…",
"regeneration_indicator.sublabel": "Via ĉefpaĝo estas preparanta!",
"regeneration_indicator.label": "Ŝargado…",
"regeneration_indicator.sublabel": "Via hejma fluo pretiĝas!",
"relative_time.days": "{number}t",
"relative_time.hours": "{number}h",
"relative_time.just_now": "nun",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Malfari",
"reply_indicator.cancel": "Nuligi",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Pliaj komentoj",
"report.submit": "Sendi",
"report.target": "Signalaĵo",
"report.target": "Signali {target}",
"search.placeholder": "Serĉi",
"search_popout.search_format": "Detala serĉo",
"search_popout.tips.hashtag": "kradvorto",
"search_popout.tips.status": "statkonigo",
"search_popout.tips.text": "Simpla teksto eligas la kongruajn afiŝnomojn, uznomojn kaj kradvortojn",
"search_popout.tips.status": "mesaĝoj",
"search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
"search_popout.tips.user": "uzanto",
"search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}",
"standalone.public_title": "Rigardeti…",
"status.block": "Block @{name}",
"status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
"standalone.public_title": "Enrigardo…",
"status.block": "Bloki @{name}",
"status.cannot_reblog": "Ĉi tiu mesaĝo ne diskonigeblas",
"status.delete": "Forigi",
"status.embed": "Enmeti",
"status.favourite": "Favori",
"status.load_more": "Ŝargi plie",
"status.media_hidden": "Sonbildaĵo kaŝita",
"status.embed": "Enkorpigi",
"status.favourite": "Stelumi",
"status.load_more": "Ŝargi pli",
"status.media_hidden": "Aŭdovidaĵo kaŝita",
"status.mention": "Mencii @{name}",
"status.more": "Pli",
"status.mute": "Silentigi @{name}",
"status.mute_conversation": "Silentigi konversacion",
"status.open": "Disfaldi statkonigon",
"status.pin": "Pingli al la profilo",
"status.open": "Grandigi ĉi tiun mesaĝon",
"status.pin": "Alpingli en la profilo",
"status.reblog": "Diskonigi",
"status.reblogged_by": "{name} diskonigis",
"status.reply": "Respondi",
@@ -239,28 +246,29 @@
"status.sensitive_toggle": "Alklaki por vidi",
"status.sensitive_warning": "Tikla enhavo",
"status.share": "Diskonigi",
"status.show_less": "Refaldi",
"status.show_more": "Disfaldi",
"status.show_less": "Malgrandigi",
"status.show_more": "Grandigi",
"status.unmute_conversation": "Malsilentigi konversacion",
"status.unpin": "Depingli de profilo",
"tabs_bar.compose": "Ekskribi",
"tabs_bar.federated_timeline": "Federacia tempolinio",
"tabs_bar.federated_timeline": "Fratara tempolinio",
"tabs_bar.home": "Hejmo",
"tabs_bar.local_timeline": "Loka tempolinio",
"tabs_bar.notifications": "Sciigoj",
"ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
"upload_area.title": "Algliti por alŝuti",
"upload_button.label": "Aldoni sonbildaĵon",
"upload_form.description": "Priskribi por la misvidantaj",
"upload_area.title": "Altreni kaj lasi por alŝuti",
"upload_button.label": "Aldoni aŭdovidaĵon",
"upload_form.description": "Priskribi por misvidantaj homoj",
"upload_form.focus": "Crop",
"upload_form.undo": "Malfari",
"upload_progress.label": "Alŝutanta…",
"upload_progress.label": "Alŝutado…",
"video.close": "Fermi videon",
"video.exit_fullscreen": "Eliri el plenekrano",
"video.expand": "Vastigi videon",
"video.fullscreen": "Igi plenekrane",
"video.exit_fullscreen": "Eksigi plenekrana",
"video.expand": "Grandigi videon",
"video.fullscreen": "Igi plenekrana",
"video.hide": "Kaŝi videon",
"video.mute": "Silentigi",
"video.pause": "Paŭzi",
"video.play": "Legi",
"video.play": "Ekigi",
"video.unmute": "Malsilentigi"
}

View File

@@ -14,6 +14,7 @@
"account.mute": "Silenciar a @{name}",
"account.mute_notifications": "Silenciar notificaciones de @{name}",
"account.posts": "Publicaciones",
"account.posts_with_replies": "Toots with replies",
"account.report": "Reportar a @{name}",
"account.requested": "Esperando aprobación",
"account.share": "Compartir el perfil de @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Comentarios adicionales",
"report.submit": "Publicar",
"report.target": "Reportando",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
"search_popout.tips.user": "usuario",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Un pequeño vistazo...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arrastra y suelta para subir",
"upload_button.label": "Subir multimedia",
"upload_form.description": "Describir para los usuarios con dificultad visual",
"upload_form.focus": "Crop",
"upload_form.undo": "Deshacer",
"upload_progress.label": "Subiendo…",
"video.close": "Cerrar video",

View File

@@ -14,6 +14,7 @@
"account.mute": "بی‌صدا کردن @{name}",
"account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
"account.posts": "نوشته‌ها",
"account.posts_with_replies": "Toots with replies",
"account.report": "گزارش @{name}",
"account.requested": "در انتظار پذیرش",
"account.share": "هم‌رسانی نمایهٔ @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "لغو",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "توضیح اضافه",
"report.submit": "بفرست",
"report.target": "گزارش‌دادن",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "نوشته",
"search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
"search_popout.tips.user": "کاربر",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
"standalone.public_title": "نگاهی به کاربران این سرور...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "برای بارگذاری به این‌جا بکشید",
"upload_button.label": "افزودن تصویر",
"upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
"upload_form.focus": "Crop",
"upload_form.undo": "واگردانی",
"upload_progress.label": "بارگذاری...",
"video.close": "بستن ویدیو",

View File

@@ -1,266 +1,274 @@
{
"account.block": "Estä @{name}",
"account.block_domain": "Hide everything from {domain}",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
"account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.",
"account.edit_profile": "Muokkaa",
"account.follow": "Seuraa",
"account.followers": "Seuraajia",
"account.follows": "Seuraa",
"account.follows_you": "Seuraa sinua",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.hide_reblogs": "Piilota buustaukset käyttäjältä @{name}",
"account.media": "Media",
"account.mention": "Mainitse @{name}",
"account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Postit",
"account.moved_to": "{name} on muuttanut instanssiin:",
"account.mute": "Mykistä @{name}",
"account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
"account.posts": "Töötit",
"account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}",
"account.requested": "Odottaa hyväksyntää",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.requested": "Odottaa hyväksyntää. Klikkaa peruuttaaksesi seurauspyynnön",
"account.share": "Jaa käyttäjän @{name} profiili",
"account.show_reblogs": "Näytä boostaukset käyttäjältä @{name}",
"account.unblock": "Salli @{name}",
"account.unblock_domain": "Unhide {domain}",
"account.unfollow": "Lopeta seuraaminen",
"account.unmute": "Unmute @{name}",
"account.unmute_notifications": "Unmute notifications from @{name}",
"account.view_full_profile": "View full profile",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
"account.unblock_domain": "Näytä {domain}",
"account.unfollow": "Lakkaa seuraamasta",
"account.unmute": "Poista mykistys käyttäjältä @{name}",
"account.unmute_notifications": "Poista mykistys käyttäjän @{name} ilmoituksilta",
"account.view_full_profile": "Näytä koko profiili",
"boost_modal.combo": "Voit painaa näppäimiä {combo} ohittaaksesi tämän ensi kerralla",
"bundle_column_error.body": "Jokin meni vikaan tätä komponenttia ladatessa.",
"bundle_column_error.retry": "Yritä uudestaan",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",
"bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again",
"column.blocks": "Blocked users",
"bundle_modal_error.close": "Sulje",
"bundle_modal_error.message": "Jokin meni vikaan tätä komponenttia ladatessa.",
"bundle_modal_error.retry": "Yritä uudestaan",
"column.blocks": "Estetyt käyttäjät",
"column.community": "Paikallinen aikajana",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.favourites": "Suosikit",
"column.follow_requests": "Seurauspyynnöt",
"column.home": "Koti",
"column.lists": "Lists",
"column.mutes": "Muted users",
"column.lists": "Listat",
"column.mutes": "Mykistetyt käyttäjät",
"column.notifications": "Ilmoitukset",
"column.pins": "Pinned toot",
"column.public": "Yleinen aikajana",
"column_back_button.label": "Takaisin",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
"column_header.moveRight_settings": "Move column to the right",
"column_header.pin": "Pin",
"column_header.show_settings": "Show settings",
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"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.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"column_header.hide_settings": "Piilota asetukset",
"column_header.moveLeft_settings": "Siirrä saraketta vasemmalle",
"column_header.moveRight_settings": "Siirrä saraketta oikealle",
"column_header.pin": "Kiinnitä",
"column_header.show_settings": "Näytä asetukset",
"column_header.unpin": "Poista kiinnitys",
"column_subheading.navigation": "Navigaatio",
"column_subheading.settings": "Asetukset",
"compose_form.hashtag_warning": "Tämä töötti ei tule näkymään hashtag-hauissa, koska se ei näy julkisilla aikajanoilla. Vain julkisia tööttejä voi hakea hashtageilla.",
"compose_form.lock_disclaimer": "Tilisi ei ole {locked}. Kuka tahansa voi seurata tiliäsi ja nähdä vain seuraajille -postauksesi.",
"compose_form.lock_disclaimer.lock": "lukittu",
"compose_form.placeholder": "Mitä sinulla on mielessä?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Merkitse media herkäksi",
"compose_form.spoiler": "Piiloita teksti varoituksen taakse",
"compose_form.spoiler_placeholder": "Content warning",
"confirmation_modal.cancel": "Cancel",
"confirmations.block.confirm": "Block",
"confirmations.block.message": "Are you sure you want to block {name}?",
"confirmation_modal.cancel": "Peruuta",
"confirmations.block.confirm": "Estä",
"confirmations.block.message": "Oletko varma, että haluat estää käyttäjän {name}?",
"confirmations.delete.confirm": "Delete",
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete.message": "Oletko varma, että haluat poistaa tämän statuspäivityksen?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable.",
"confirmations.mute.confirm": "Mute",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Here is what it will look like:",
"emoji_button.activity": "Activity",
"emoji_button.custom": "Custom",
"emoji_button.flags": "Flags",
"emoji_button.food": "Food & Drink",
"emoji_button.label": "Insert emoji",
"emoji_button.nature": "Nature",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objects",
"emoji_button.people": "People",
"emoji_button.recent": "Frequently used",
"emoji_button.search": "Search...",
"emoji_button.search_results": "Search results",
"emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.hashtag": "There is nothing in this hashtag yet.",
"empty_column.home": "Your home timeline is empty! Visit {public} or use search to get started and meet other users.",
"empty_column.home.public_timeline": "the public timeline",
"empty_column.list": "There is nothing in this list yet.",
"empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
"follow_request.authorize": "Authorize",
"follow_request.reject": "Reject",
"getting_started.appsshort": "Apps",
"confirmations.delete_list.message": "Oletko varma, että haluat poistaa tämän listan pysyvästi?",
"confirmations.domain_block.confirm": "Piilota koko verkko-osoite",
"confirmations.domain_block.message": "Oletko aivan oikeasti varma että haluat estää koko verkko-osoitteen {domain}? Useimmissa tapauksissa muutamat kohdistetut estot ja mykistykset ovat riittäviä ja suositeltavampia.",
"confirmations.mute.confirm": "Mykistä",
"confirmations.mute.message": "Oletko varma että haluat mykistää käyttäjän {name}?",
"confirmations.unfollow.confirm": "Lakkaa seuraamasta",
"confirmations.unfollow.message": "Oletko varma, että haluat lakata seuraamasta käyttäjää {name}?",
"embed.instructions": "Upota tämä statuspäivitys sivullesi kopioimalla alla oleva koodi.",
"embed.preview": "Tältä se tulee näyttämään:",
"emoji_button.activity": "Aktiviteetit",
"emoji_button.custom": "Mukautetut",
"emoji_button.flags": "Liput",
"emoji_button.food": "Ruoka ja juoma",
"emoji_button.label": "Lisää emoji",
"emoji_button.nature": "Luonto",
"emoji_button.not_found": "Ei emojeja!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objektit",
"emoji_button.people": "Ihmiset",
"emoji_button.recent": "Usein käytetyt",
"emoji_button.search": "Etsi...",
"emoji_button.search_results": "Hakutulokset",
"emoji_button.symbols": "Symbolit",
"emoji_button.travel": "Matkailu",
"empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista saadaksesi pyörät pyörimään!",
"empty_column.hashtag": "Tässä hashtagissa ei ole vielä mitään.",
"empty_column.home": "Kotiaikajanasi on tyhjä! Käy vierailemassa {public}ssa tai käytä hakutoimintoa aloittaaksesi ja tavataksesi muita käyttäjiä.",
"empty_column.home.public_timeline": "yleinen aikajana",
"empty_column.list": "Tämä lista on vielä tyhjä. Kun listan jäsenet julkaisevat statuspäivityksiä, ne näkyvät tässä.",
"empty_column.notifications": "Sinulle ei ole vielä ilmoituksia. Juttele muille aloittaaksesi keskustelun.",
"empty_column.public": "Täällä ei ole mitään! Kirjoita jotain julkisesti, tai käy manuaalisesti seuraamassa käyttäjiä muista instansseista saadaksesi sisältöä",
"follow_request.authorize": "Valtuuta",
"follow_request.reject": "Hylkää",
"getting_started.appsshort": "Sovellukset",
"getting_started.faq": "FAQ",
"getting_started.heading": "Aloitus",
"getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.",
"getting_started.userguide": "User Guide",
"home.column_settings.advanced": "Advanced",
"home.column_settings.basic": "Basic",
"home.column_settings.filter_regex": "Filter out by regular expressions",
"home.column_settings.show_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies",
"home.settings": "Column settings",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"getting_started.open_source_notice": "Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}.",
"getting_started.userguide": "Käyttöopas",
"home.column_settings.advanced": "Tarkemmat asetukset",
"home.column_settings.basic": "Perusasetukset",
"home.column_settings.filter_regex": "Suodata säännöllisten lauseiden avulla",
"home.column_settings.show_reblogs": "Näytä buustaukset",
"home.column_settings.show_replies": "Näytä vastaukset",
"home.settings": "Sarakeasetukset",
"keyboard_shortcuts.back": "liikkuaksesi taaksepäin",
"keyboard_shortcuts.boost": "buustataksesi",
"keyboard_shortcuts.column": "keskittääksesi statuspäivitykseen yhdessä sarakkeista",
"keyboard_shortcuts.compose": "aktivoidaksesi tekstinkirjoitusalueen",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.down": "liikkuaksesi listassa alaspäin",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"keyboard_shortcuts.favourite": "tykätäksesi",
"keyboard_shortcuts.heading": "Näppäinoikotiet",
"keyboard_shortcuts.hotkey": "Pikanäppäin",
"keyboard_shortcuts.legend": "näyttääksesi tämän selitteen",
"keyboard_shortcuts.mention": "mainitaksesi julkaisijan",
"keyboard_shortcuts.reply": "vastataksesi",
"keyboard_shortcuts.search": "aktivoidaksesi hakukentän",
"keyboard_shortcuts.toot": "aloittaaksesi uuden töötin kirjoittamisen",
"keyboard_shortcuts.unfocus": "poistaaksesi aktivoinnin tekstikentästä/hakukentästä",
"keyboard_shortcuts.up": "liikkuaksesi listassa ylöspäin",
"lightbox.close": "Sulje",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lightbox.next": "Seuraava",
"lightbox.previous": "Edellinen",
"lists.account.add": "Lisää listaan",
"lists.account.remove": "Poista listalta",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"lists.edit": "Muokkaa listaa",
"lists.new.create": "Lisää lista",
"lists.new.title_placeholder": "Uuden listan otsikko",
"lists.search": "Etsi seuraamiesi henkilöiden joukosta",
"lists.subheading": "Omat listat",
"loading_indicator.label": "Ladataan...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
"missing_indicator.sublabel": "This resource could not be found",
"mute_modal.hide_notifications": "Hide notifications from this user?",
"navigation_bar.blocks": "Blocked users",
"media_gallery.toggle_visible": "Säädä näkyvyyttä",
"missing_indicator.label": "Ei löydetty",
"missing_indicator.sublabel": "Tätä resurssia ei löytynyt",
"mute_modal.hide_notifications": "Piilota ilmoitukset tältä käyttäjältä?",
"navigation_bar.blocks": "Estetyt käyttäjät",
"navigation_bar.community_timeline": "Paikallinen aikajana",
"navigation_bar.edit_profile": "Muokkaa profiilia",
"navigation_bar.favourites": "Favourites",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.info": "Extended information",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
"navigation_bar.lists": "Lists",
"navigation_bar.favourites": "Suosikit",
"navigation_bar.follow_requests": "Seurauspyynnöt",
"navigation_bar.info": "Tietoa tästä instanssista",
"navigation_bar.keyboard_shortcuts": "Näppäinoikotiet",
"navigation_bar.lists": "Listat",
"navigation_bar.logout": "Kirjaudu ulos",
"navigation_bar.mutes": "Muted users",
"navigation_bar.pins": "Pinned toots",
"navigation_bar.mutes": "Mykistetyt käyttäjät",
"navigation_bar.pins": "Kiinnitetyt töötit",
"navigation_bar.preferences": "Ominaisuudet",
"navigation_bar.public_timeline": "Yleinen aikajana",
"notification.favourite": "{name} tykkäsi statuksestasi",
"notification.follow": "{name} seurasi sinua",
"notification.mention": "{name} mainitsi sinut",
"notification.reblog": "{name} buustasi statustasi",
"notifications.clear": "Clear notifications",
"notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
"notifications.clear": "Tyhjennä ilmoitukset",
"notifications.clear_confirmation": "Oletko varma, että haluat lopullisesti tyhjentää kaikki ilmoituksesi?",
"notifications.column_settings.alert": "Työpöytä ilmoitukset",
"notifications.column_settings.favourite": "Tykkäyksiä:",
"notifications.column_settings.follow": "Uusia seuraajia:",
"notifications.column_settings.mention": "Mainintoja:",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.push_meta": "This device",
"notifications.column_settings.push": "Push-ilmoitukset",
"notifications.column_settings.push_meta": "Tämä laite",
"notifications.column_settings.reblog": "Buusteja:",
"notifications.column_settings.show": "Näytä sarakkeessa",
"notifications.column_settings.sound": "Play sound",
"onboarding.done": "Done",
"onboarding.next": "Next",
"onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
"onboarding.page_four.home": "The home timeline shows posts from people you follow.",
"onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
"onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
"onboarding.page_one.full_handle": "Your full handle",
"notifications.column_settings.sound": "Soita ääni",
"onboarding.done": "Valmis",
"onboarding.next": "Seuraava",
"onboarding.page_five.public_timelines": "Paikallinen aikajana näyttää kaikki julkiset julkaisut kaikilta, jotka ovat verkko-osoitteessa {domain}. Yleinen aikajana näyttää julkiset julkaisut kaikilta niiltä, joita käyttäjät verkko-osoitteessa {domain} seuraavat. Nämä ovat julkiset aikajanat, ja ne ovat hyviä tapoja löytää uusia ihmisiä.",
"onboarding.page_four.home": "Kotiaikajana näyttää julkaisut ihmisiltä joita seuraat.",
"onboarding.page_four.notifications": "Ilmoitukset-sarake näyttää sinulle, kun joku on viestii kanssasi.",
"onboarding.page_one.federation": "Mastodon on yhteisöpalvelu, joka toimii monen itsenäisen palvelimen muodostamassa verkossa. Me kutsumme näitä palvelimia instansseiksi.",
"onboarding.page_one.full_handle": "Koko käyttäjänimesi",
"onboarding.page_one.handle_hint": "This is what you would tell your friends to search for.",
"onboarding.page_one.welcome": "Welcome to Mastodon!",
"onboarding.page_six.admin": "Your instance's admin is {admin}.",
"onboarding.page_six.almost_done": "Almost done...",
"onboarding.page_six.appetoot": "Bon Appetoot!",
"onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
"onboarding.page_one.welcome": "Tervetuloa Mastodoniin!",
"onboarding.page_six.admin": "Instanssisi ylläpitäjä on {admin}.",
"onboarding.page_six.almost_done": "Melkein valmista...",
"onboarding.page_six.appetoot": "Bon Appetööt!",
"onboarding.page_six.apps_available": "{apps} on saatavilla iOS:lle, Androidille ja muille alustoille.",
"onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
"onboarding.page_six.guidelines": "community guidelines",
"onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
"onboarding.page_six.various_app": "mobile apps",
"onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
"onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
"onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
"onboarding.skip": "Skip",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Post to mentioned users only",
"privacy.direct.short": "Direct",
"privacy.private.long": "Post to followers only",
"privacy.private.short": "Followers-only",
"privacy.public.long": "Post to public timelines",
"privacy.public.short": "Public",
"privacy.unlisted.long": "Do not show in public timelines",
"privacy.unlisted.short": "Unlisted",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"onboarding.page_six.guidelines": "yhteisön säännöt",
"onboarding.page_six.read_guidelines": "Ole hyvä ja lue {domain}:n {guidelines}!",
"onboarding.page_six.various_app": "mobiilisovellukset",
"onboarding.page_three.profile": "Muokkaa profiiliasi muuttaaksesi kuvakettasi, esittelyäsi ja nimimerkkiäsi. Löydät sieltä myös muita henkilökohtaisia asetuksia.",
"onboarding.page_three.search": "Käytä hakukenttää löytääksesi ihmisiä ja etsiäksesi hashtageja, kuten {illustration} tai {introductions}. Hakeaksesi henkilöä joka on toisessa instanssissa, käytä hänen käyttäjänimeään kokonaisuudessaan.",
"onboarding.page_two.compose": "Kirjoita postauksia kirjoita-sarakkeessa. Voit ladata kuvia, vaihtaa yksityisyysasetuksia ja lisätä sisältövaroituksia alla olevista painikkeista.",
"onboarding.skip": "Ohita",
"privacy.change": "Säädä töötin yksityisyysasetuksia",
"privacy.direct.long": "Julkaise vain mainituille käyttäjille",
"privacy.direct.short": "Yksityisviesti",
"privacy.private.long": "Julkaise vain seuraajille",
"privacy.private.short": "Vain seuraajat",
"privacy.public.long": "Julkaise julkisille aikajanoille",
"privacy.public.short": "Julkinen",
"privacy.unlisted.long": "Älä julkaise yleisillä aikajanoilla",
"privacy.unlisted.short": "Julkinen, mutta älä näytä julkisella aikajanalla",
"regeneration_indicator.label": "Ladataan…",
"regeneration_indicator.sublabel": "Kotinäkymääsi valmistellaan!",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.just_now": "nyt",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Peruuta",
"report.placeholder": "Additional comments",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Lisäkommentit",
"report.submit": "Submit",
"report.target": "Reporting",
"search.placeholder": "Hae",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.hashtag": "hashtag",
"search_popout.search_format": "Tarkennettu haku",
"search_popout.tips.hashtag": "hashtagi",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
"search_popout.tips.user": "käyttäjä",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"standalone.public_title": "Kurkistus sisälle...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.cannot_reblog": "Tätä postausta ei voi buustata",
"status.delete": "Poista",
"status.embed": "Embed",
"status.embed": "Upota",
"status.favourite": "Tykkää",
"status.load_more": "Load more",
"status.media_hidden": "Media hidden",
"status.load_more": "Lataa lisää",
"status.media_hidden": "Media piilotettu",
"status.mention": "Mainitse @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",
"status.more": "Lisää",
"status.mute": "Mykistä @{name}",
"status.mute_conversation": "Mykistä keskustelu",
"status.open": "Laajenna statuspäivitys",
"status.pin": "Kiinnitä profiiliin",
"status.reblog": "Buustaa",
"status.reblogged_by": "{name} buustasi",
"status.reply": "Vastaa",
"status.replyAll": "Reply to thread",
"status.replyAll": "Vastaa ketjuun",
"status.report": "Report @{name}",
"status.sensitive_toggle": "Klikkaa nähdäksesi",
"status.sensitive_warning": "Arkaluontoista sisältöä",
"status.share": "Share",
"status.show_less": "Show less",
"status.show_more": "Show more",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"status.share": "Jaa",
"status.show_less": "Näytä vähemmän",
"status.show_more": "Näytä lisää",
"status.unmute_conversation": "Poista mykistys keskustelulta",
"status.unpin": "Irrota profiilista",
"tabs_bar.compose": "Luo",
"tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Koti",
"tabs_bar.local_timeline": "Local",
"tabs_bar.local_timeline": "Paikallinen",
"tabs_bar.notifications": "Ilmoitukset",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Drag & drop to upload",
"ui.beforeunload": "Luonnoksesi menetetään, jos poistut Mastodonista.",
"upload_area.title": "Raahaa ja pudota tähän ladataksesi",
"upload_button.label": "Lisää mediaa",
"upload_form.description": "Describe for the visually impaired",
"upload_form.description": "Anna kuvaus näkörajoitteisia varten",
"upload_form.focus": "Crop",
"upload_form.undo": "Peru",
"upload_progress.label": "Uploading...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"upload_progress.label": "Ladataan...",
"video.close": "Sulje video",
"video.exit_fullscreen": "Poistu koko näytön tilasta",
"video.expand": "Laajenna video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound"
"video.hide": "Piilota video",
"video.mute": "Mykistä ääni",
"video.pause": "Keskeytä",
"video.play": "Toista",
"video.unmute": "Poista mykistys ääneltä"
}

View File

@@ -14,6 +14,7 @@
"account.mute": "Masquer @{name}",
"account.mute_notifications": "Ignorer les notifications de @{name}",
"account.posts": "Statuts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Signaler",
"account.requested": "Invitation envoyée",
"account.share": "Partager le profil de @{name}",
@@ -163,7 +164,7 @@
"notifications.column_settings.alert": "Notifications locales",
"notifications.column_settings.favourite": "Favoris :",
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.mention": "Mentions :",
"notifications.column_settings.push": "Notifications push",
"notifications.column_settings.push_meta": "Cet appareil",
"notifications.column_settings.reblog": "Partages:",
@@ -200,13 +201,16 @@
"privacy.unlisted.long": "Ne pas afficher dans les fils publics",
"privacy.unlisted.short": "Non-listé",
"regeneration_indicator.label": "Chargement…",
"regeneration_indicator.sublabel": "Votre page principale est en cours de préparation!",
"regeneration_indicator.sublabel": "Le flux de votre page principale est en cours de préparation !",
"relative_time.days": "{number} j",
"relative_time.hours": "{number} h",
"relative_time.just_now": "à linstant",
"relative_time.minutes": "{number} min",
"relative_time.seconds": "{number} s",
"reply_indicator.cancel": "Annuler",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Commentaires additionnels",
"report.submit": "Envoyer",
"report.target": "Signalement",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "statuts",
"search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms dutilisateur⋅ice et les hashtags correspondants",
"search_popout.tips.user": "utilisateur⋅ice",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
"standalone.public_title": "Jeter un coup dœil…",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Glissez et déposez pour envoyer",
"upload_button.label": "Joindre un média",
"upload_form.description": "Décrire pour les malvoyants",
"upload_form.focus": "Crop",
"upload_form.undo": "Annuler",
"upload_progress.label": "Envoi en cours…",
"video.close": "Fermer la vidéo",

View File

@@ -13,7 +13,8 @@
"account.moved_to": "{name} marchou a:",
"account.mute": "Acalar @{name}",
"account.mute_notifications": "Acalar as notificacións de @{name}",
"account.posts": "Publicacións",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Informar sobre @{name}",
"account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento",
"account.share": "Compartir o perfil de @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Comentarios adicionais",
"report.submit": "Enviar",
"report.target": "Informar {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "estado",
"search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
"search_popout.tips.user": "usuaria",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
"standalone.public_title": "Ollada dentro...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arrastre e solte para subir",
"upload_button.label": "Engadir medios",
"upload_form.description": "Describa para deficientes visuais",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfacer",
"upload_progress.label": "Subindo...",
"video.close": "Pechar video",

View File

@@ -14,6 +14,7 @@
"account.mute": "להשתיק את @{name}",
"account.mute_notifications": "להסתיר התראות מאת @{name}",
"account.posts": "הודעות",
"account.posts_with_replies": "Toots with replies",
"account.report": "לדווח על @{name}",
"account.requested": "בהמתנה לאישור",
"account.share": "לשתף את אודות @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "ביטול",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "הערות נוספות",
"report.submit": "שליחה",
"report.target": "דיווח",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
"search_popout.tips.user": "משתמש(ת)",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
"standalone.public_title": "הצצה פנימה...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "ניתן להעלות על ידי Drag & drop",
"upload_button.label": "הוספת מדיה",
"upload_form.description": "תיאור לכבדי ראיה",
"upload_form.focus": "Crop",
"upload_form.undo": "ביטול",
"upload_progress.label": "עולה...",
"video.close": "סגירת וידאו",

View File

@@ -14,6 +14,7 @@
"account.mute": "Utišaj @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Postovi",
"account.posts_with_replies": "Toots with replies",
"account.report": "Prijavi @{name}",
"account.requested": "Čeka pristanak",
"account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Otkaži",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Dodatni komentari",
"report.submit": "Pošalji",
"report.target": "Prijavljivanje",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Povuci i spusti kako bi uploadao",
"upload_button.label": "Dodaj media",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Poništi",
"upload_progress.label": "Uploadam...",
"video.close": "Close video",

View File

@@ -7,13 +7,14 @@
"account.followers": "Követők",
"account.follows": "Követve",
"account.follows_you": "Követnek téged",
"account.hide_reblogs": "@{name} kedvenceinek elrejtése",
"account.hide_reblogs": "Rejtsd el a tülkölést @{name}-tól/től",
"account.media": "Média",
"account.mention": "@{name} említése",
"account.moved_to": "{name} átköltözött:",
"account.mute": "@{name} némítása",
"account.mute_notifications": "@{name} értesítések némítása",
"account.posts": "Státuszok",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} jelentése",
"account.requested": "Engedélyre vár. Kattintson a követési kérés visszavonására",
"account.share": "@{name} profiljának megosztása",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Mégsem",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "További kommentek",
"report.submit": "Submit",
"report.target": "Reporting",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "felhasználó",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "Betekintés...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Húzza ide a feltöltéshez",
"upload_button.label": "Média hozzáadása",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Mégsem",
"upload_progress.label": "Uploading...",
"video.close": "Close video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Լռեցնել @{name}֊ին",
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
"account.posts": "Գրառումներ",
"account.posts_with_replies": "Toots with replies",
"account.report": "Բողոքել @{name}֊ից",
"account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
"account.share": "Կիսվել @{name}֊ի էջով",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}ր",
"relative_time.seconds": "{number}վ",
"reply_indicator.cancel": "Չեղարկել",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Լրացուցիչ մեկնաբանություններ",
"report.submit": "Ուղարկել",
"report.target": "Բողոքել {target}֊ի մասին",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "թութ",
"search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
"search_popout.tips.user": "օգտատեր",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "Այս պահին…",
"status.block": "Արգելափակել @{name}֊ին",
@@ -252,6 +259,7 @@
"upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
"upload_button.label": "Ավելացնել մեդիա",
"upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
"upload_form.focus": "Crop",
"upload_form.undo": "Հետարկել",
"upload_progress.label": "Վերբեռնվում է…",
"video.close": "Փակել տեսագրությունը",

View File

@@ -14,6 +14,7 @@
"account.mute": "Bisukan @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Postingan",
"account.posts_with_replies": "Toots with replies",
"account.report": "Laporkan @{name}",
"account.requested": "Menunggu persetujuan",
"account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Batal",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Komentar tambahan",
"report.submit": "Kirim",
"report.target": "Melaporkan",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Seret & lepaskan untuk mengunggah",
"upload_button.label": "Tambahkan media",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Undo",
"upload_progress.label": "Mengunggah...",
"video.close": "Close video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Celar @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Mesaji",
"account.posts_with_replies": "Toots with replies",
"account.report": "Denuncar @{name}",
"account.requested": "Vartante aprobo",
"account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Nihiligar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Plusa komenti",
"report.submit": "Sendar",
"report.target": "Denuncante",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Tranar faligar por kargar",
"upload_button.label": "Adjuntar kontenajo",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfacar",
"upload_progress.label": "Kargante...",
"video.close": "Close video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Silenzia @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Segnala @{name}",
"account.requested": "In attesa di approvazione",
"account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Annulla",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Commenti aggiuntivi",
"report.submit": "Invia",
"report.target": "Invio la segnalazione",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Trascina per caricare",
"upload_button.label": "Aggiungi file multimediale",
"upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Annulla",
"upload_progress.label": "Sto caricando...",
"video.close": "Close video",

View File

@@ -14,11 +14,12 @@
"account.mute": "@{name}さんをミュート",
"account.mute_notifications": "@{name}さんからの通知を受け取らない",
"account.posts": "投稿",
"account.posts_with_replies": "トゥートと返信",
"account.report": "@{name}さんを通報",
"account.requested": "フォロー承認待ちです。クリックしてキャンセル",
"account.share": "@{name}さんのプロフィールを共有する",
"account.show_reblogs": "@{name}さんからのブーストを表示",
"account.unblock": "@{name}さんのブロック解除",
"account.unblock": "@{name}さんのブロック解除",
"account.unblock_domain": "{domain}を表示",
"account.unfollow": "フォロー解除",
"account.unmute": "@{name}さんのミュートを解除",
@@ -67,7 +68,7 @@
"confirmations.delete_list.confirm": "削除",
"confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
"confirmations.domain_block.confirm": "ドメイン全体を非表示",
"confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
"confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。",
"confirmations.mute.confirm": "ミュート",
"confirmations.mute.message": "本当に{name}さんをミュートしますか?",
"confirmations.unfollow.confirm": "フォロー解除",
@@ -200,13 +201,16 @@
"privacy.unlisted.long": "公開TLで表示しない",
"privacy.unlisted.short": "未収載",
"regeneration_indicator.label": "読み込み中…",
"regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
"regeneration_indicator.sublabel": "ホームタイムラインは準備中です",
"relative_time.days": "{number}日前",
"relative_time.hours": "{number}時間前",
"relative_time.just_now": "今",
"relative_time.minutes": "{number}分前",
"relative_time.seconds": "{number}秒前",
"reply_indicator.cancel": "キャンセル",
"report.forward": "{target} に転送する",
"report.forward_hint": "このアカウントは別のインスタンスに所属しています。通報内容を匿名で転送しますか?",
"report.hint": "通報内容はあなたのインスタンスのモデレーターへ送信されます。通報理由を入力してください。:",
"report.placeholder": "追加コメント",
"report.submit": "通報する",
"report.target": "{target}さんを通報する",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "トゥート",
"search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
"search_popout.tips.user": "ユーザー",
"search_results.accounts": "人々",
"search_results.hashtags": "ハッシュタグ",
"search_results.statuses": "トゥート",
"search_results.total": "{count, number}件の結果",
"standalone.public_title": "今こんな話をしています...",
"status.block": "@{name}さんをブロック",
@@ -252,6 +259,7 @@
"upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加",
"upload_form.description": "視覚障害者のための説明",
"upload_form.focus": "焦点",
"upload_form.undo": "やり直す",
"upload_progress.label": "アップロード中...",
"video.close": "動画を閉じる",

View File

@@ -14,6 +14,7 @@
"account.mute": "@{name} 뮤트",
"account.mute_notifications": "@{name}의 알림을 뮤트",
"account.posts": "게시물",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} 신고",
"account.requested": "승인 대기 중. 클릭해서 취소하기",
"account.share": "@{name}의 프로파일 공유",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}분 전",
"relative_time.seconds": "{number}초 전",
"reply_indicator.cancel": "취소",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "코멘트",
"report.submit": "신고하기",
"report.target": "문제가 된 사용자",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "툿",
"search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
"search_popout.tips.user": "유저",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number}건의 결과",
"standalone.public_title": "지금 이런 이야기를 하고 있습니다…",
"status.block": "@{name} 차단",
@@ -252,6 +259,7 @@
"upload_area.title": "드래그 & 드롭으로 업로드",
"upload_button.label": "미디어 추가",
"upload_form.description": "시각장애인을 위한 설명",
"upload_form.focus": "Crop",
"upload_form.undo": "재시도",
"upload_progress.label": "업로드 중...",
"video.close": "동영상 닫기",

View File

@@ -14,6 +14,7 @@
"account.mute": "Negeer @{name}",
"account.mute_notifications": "Negeer meldingen van @{name}",
"account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Rapporteer @{name}",
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
"account.share": "Profiel van @{name} delen",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Annuleren",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Extra opmerkingen",
"report.submit": "Verzenden",
"report.target": "Rapporteer {target}",
@@ -216,9 +220,12 @@
"search_popout.tips.status": "toot",
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
"search_popout.tips.user": "gebruiker",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
"standalone.public_title": "Een kijkje binnenin...",
"status.block": "Block @{name}",
"status.block": "Blokkeer @{name}",
"status.cannot_reblog": "Deze toot kan niet geboost worden",
"status.delete": "Verwijderen",
"status.embed": "Embed",
@@ -252,6 +259,7 @@
"upload_area.title": "Hierin slepen om te uploaden",
"upload_button.label": "Media toevoegen",
"upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
"upload_form.focus": "Crop",
"upload_form.undo": "Ongedaan maken",
"upload_progress.label": "Uploaden...",
"video.close": "Video sluiten",

View File

@@ -14,6 +14,7 @@
"account.mute": "Demp @{name}",
"account.mute_notifications": "Ignorer varsler fra @{name}",
"account.posts": "Innlegg",
"account.posts_with_replies": "Toots with replies",
"account.report": "Rapportér @{name}",
"account.requested": "Venter på godkjennelse",
"account.share": "Del @{name}s profil",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Avbryt",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Tilleggskommentarer",
"report.submit": "Send inn",
"report.target": "Rapporterer",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
"search_popout.tips.user": "bruker",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
"standalone.public_title": "En titt inni...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Dra og slipp for å laste opp",
"upload_button.label": "Legg til media",
"upload_form.description": "Beskriv for synshemmede",
"upload_form.focus": "Crop",
"upload_form.undo": "Angre",
"upload_progress.label": "Laster opp...",
"video.close": "Lukk video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Rescondre @{name}",
"account.mute_notifications": "Rescondre las notificacions de @{name}",
"account.posts": "Estatuts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Senhalar @{name}",
"account.requested": "Invitacion mandada. Clicatz per anullar",
"account.share": "Partejar lo perfil a @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "fa {number}min",
"relative_time.seconds": "fa {number}s",
"reply_indicator.cancel": "Anullar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Comentaris addicionals",
"report.submit": "Mandar",
"report.target": "Senhalar {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "estatut",
"search_popout.tips.text": "Lo tèxt brut tòrna escais, noms dutilizaire e etiquetas correspondents",
"search_popout.tips.user": "utilizaire",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
"standalone.public_title": "Una ulhada dedins…",
"status.block": "Blocar @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Lisatz e depausatz per mandar",
"upload_button.label": "Ajustar un mèdia",
"upload_form.description": "Descripcion pels mal vesents",
"upload_form.focus": "Crop",
"upload_form.undo": "Anullar",
"upload_progress.label": "Mandadís…",
"video.close": "Tampar la vidèo",

View File

@@ -8,12 +8,13 @@
"account.follows": "Śledzeni",
"account.follows_you": "Śledzi Cię",
"account.hide_reblogs": "Ukryj podbicia od @{name}",
"account.media": "Media",
"account.media": "Zawartość multimedialna",
"account.mention": "Wspomnij o @{name}",
"account.moved_to": "{name} przeniósł się do:",
"account.mute": "Wycisz @{name}",
"account.mute_notifications": "Wycisz powiadomienia o @{name}",
"account.posts": "Wpisy",
"account.posts_with_replies": "Wpisy z odpowiedziami",
"account.report": "Zgłoś @{name}",
"account.requested": "Oczekująca prośba, kliknij aby anulować",
"account.share": "Udostępnij profil @{name}",
@@ -94,7 +95,7 @@
"empty_column.home.public_timeline": "publiczna oś czasu",
"empty_column.list": "Nie ma nic na tej liście. Kiedy członkowie listy dodadzą nowe wpisy, pojawia się one tutaj.",
"empty_column.notifications": "Nie masz żadnych powiadomień. Rozpocznij interakcje z innymi użytkownikami.",
"empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić.",
"empty_column.public": "Tu nic nie ma! Napisz coś publicznie, lub dodaj ludzi z innych instancji, aby to wyświetlić",
"follow_request.authorize": "Autoryzuj",
"follow_request.reject": "Odrzuć",
"getting_started.appsshort": "Aplikacje",
@@ -129,17 +130,17 @@
"lightbox.next": "Następne",
"lightbox.previous": "Poprzednie",
"lists.account.add": "Dodaj do listy",
"lists.account.remove": "Remove from list",
"lists.account.remove": "Usunąć z listy",
"lists.delete": "Usuń listę",
"lists.edit": "Edytuj listę",
"lists.new.create": "Utwórz listę",
"lists.new.title_placeholder": "Wprowadź tytuł listy",
"lists.new.title_placeholder": "Wprowadź tytuł listy",
"lists.search": "Szukaj wśród osób które śledzisz",
"lists.subheading": "Twoje listy",
"loading_indicator.label": "Ładowanie…",
"media_gallery.toggle_visible": "Przełącz widoczność",
"missing_indicator.label": "Nie znaleziono",
"missing_indicator.sublabel": "This resource could not be found",
"missing_indicator.sublabel": "Nie można odnaleźć tego zasobu",
"mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
"navigation_bar.blocks": "Zablokowani użytkownicy",
"navigation_bar.community_timeline": "Lokalna oś czasu",
@@ -199,14 +200,17 @@
"privacy.public.short": "Publiczny",
"privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu",
"privacy.unlisted.short": "Niewidoczny",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
"regeneration_indicator.label": "Ładuję…",
"regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
"relative_time.days": "{number} dni",
"relative_time.hours": "{number} godz.",
"relative_time.just_now": "teraz",
"relative_time.minutes": "{number} min.",
"relative_time.seconds": "{number} s.",
"reply_indicator.cancel": "Anuluj",
"report.forward": "Przekaż na {target}",
"report.forward_hint": "To konto znajduje się na innej instancji. Czy chcesz wysłać anonimową kopię zgłoszenia rnież na nią?",
"report.hint": "Zgłoszenie zostanie wysłane moderatorom Twojej instancji. Poniżej możesz też umieścić wyjaśnieni dlaczego zgłaszasz to konto:",
"report.placeholder": "Dodatkowe komentarze",
"report.submit": "Wyślij",
"report.target": "Zgłaszanie {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "wpis",
"search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
"search_popout.tips.user": "użytkownik",
"search_results.accounts": "Ludzie",
"search_results.hashtags": "Hashtagi",
"search_results.statuses": "Wpisy",
"search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
"standalone.public_title": "Spojrzenie w głąb…",
"status.block": "Zablokuj @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Przeciągnij i upuść aby wysłać",
"upload_button.label": "Dodaj zawartość multimedialną",
"upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
"upload_form.focus": "Crop",
"upload_form.undo": "Cofnij",
"upload_progress.label": "Wysyłanie",
"video.close": "Zamknij film",

View File

@@ -14,6 +14,7 @@
"account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificações de @{name}",
"account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Denunciar @{name}",
"account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
"account.share": "Compartilhar perfil de @{name}",
@@ -90,7 +91,7 @@
"emoji_button.travel": "Viagens & Lugares",
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!",
"empty_column.hashtag": "Ainda não há qualquer conteúdo com essa hashtag.",
"empty_column.home": "Você ainda não segue usuário algo. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
"empty_column.home": "Você ainda não segue usuário algum. Visite a timeline {public} ou use o buscador para procurar e conhecer outros usuários.",
"empty_column.home.public_timeline": "global",
"empty_column.list": "Ainda não há nada nesta lista. Quando membros dessa lista fizerem novas postagens, elas aparecerão aqui.",
"empty_column.notifications": "Você ainda não possui notificações. Interaja com outros usuários para começar a conversar.",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar",
"report.forward": "Forward to {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "Comentários adicionais",
"report.submit": "Enviar",
"report.target": "Denunciar",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status",
"search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
"search_popout.tips.user": "usuário",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Dê uma espiada...",
"status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arraste e solte para enviar",
"upload_button.label": "Adicionar mídia",
"upload_form.description": "Descreva a imagem para deficientes visuais",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfazer",
"upload_progress.label": "Salvando...",
"video.close": "Fechar vídeo",

Some files were not shown because too many files have changed in this diff Show More