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,6 +9,10 @@ DB_USER=postgres
DB_NAME=postgres DB_NAME=postgres
DB_PASS= DB_PASS=
DB_PORT=5432 DB_PORT=5432
# Optional ElasticSearch configuration
# ES_ENABLED=true
# ES_HOST=localhost
# ES_PORT=9200
# Federation # Federation
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation. # Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
@@ -29,7 +33,6 @@ LOCAL_DOMAIN=example.com
# Application secrets # 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) # 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= SECRET_KEY_BASE=
OTP_SECRET= OTP_SECRET=
@@ -135,3 +138,73 @@ STREAMING_CLUSTER_NUM=1
# If you use Docker, you may want to assign UID/GID manually. # If you use Docker, you may want to assign UID/GID manually.
# UID=1000 # UID=1000
# GID=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 bundler: true
yarn: true yarn: true
directories: directories:
- node_modules - node_modules
- public/assets - public/assets
- public/packs-test - public/packs-test
- tmp/cache/babel-loader - tmp/cache/babel-loader
dist: trusty dist: trusty
sudo: false sudo: false
branches: branches:
only: only:
- master - master
notifications: notifications:
email: false email: false
@@ -23,21 +23,20 @@ env:
- RAILS_ENV=test - RAILS_ENV=test
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
- PARALLEL_TEST_PROCESSORS=2 - PARALLEL_TEST_PROCESSORS=2
- "PATH=$HOME:$PATH"
addons: addons:
postgresql: 9.4 postgresql: 9.4
apt: apt:
sources: sources:
- trusty-media - trusty-media
- sourceline: deb https://dl.yarnpkg.com/debian/ stable main - sourceline: deb https://dl.yarnpkg.com/debian/ stable main
key_url: https://dl.yarnpkg.com/debian/pubkey.gpg key_url: https://dl.yarnpkg.com/debian/pubkey.gpg
packages: packages:
- ffmpeg - ffmpeg
- libicu-dev - libicu-dev
- libprotobuf-dev - libprotobuf-dev
- protobuf-compiler - protobuf-compiler
- yarn - yarn
rvm: rvm:
- 2.4.2 - 2.4.2
@@ -53,7 +52,6 @@ install:
before_script: before_script:
- ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile - ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
script: script:
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec - 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" \ LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server" description="A GNU Social-compatible microblogging server"
ENV UID=991 GID=991 \ ARG UID=991
RAILS_SERVE_STATIC_FILES=true \ ARG GID=991
ENV RAILS_SERVE_STATIC_FILES=true \
RAILS_ENV=production NODE_ENV=production RAILS_ENV=production NODE_ENV=production
ARG YARN_VERSION=1.3.2 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 --pure-lockfile \
&& yarn cache clean && 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 COPY --chown=mastodon:mastodon . /mastodon
RUN chmod +x /usr/local/bin/run
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs 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 'puma', '~> 3.10'
gem 'rails', '~> 5.1.4' gem 'rails', '~> 5.1.4'
gem 'uglifier', '~> 3.2'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails', '~> 0.2'
gem 'pg', '~> 0.20' gem 'pg', '~> 0.20'
@@ -20,6 +19,7 @@ gem 'fog-local', '~> 0.4', require: false
gem 'fog-openstack', '~> 0.1', require: false gem 'fog-openstack', '~> 0.1', require: false
gem 'paperclip', '~> 5.1' gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder', '~> 0.6' gem 'paperclip-av-transcoder', '~> 0.6'
gem 'streamio-ffmpeg', '~> 3.0'
gem 'active_model_serializers', '~> 0.10' gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.5' gem 'addressable', '~> 2.5'
@@ -27,11 +27,20 @@ gem 'bootsnap'
gem 'browser' gem 'browser'
gem 'charlock_holmes', '~> 0.7.5' gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639' gem 'iso-639'
gem 'chewy', '~> 0.10', git: 'https://github.com/toptal/chewy.git'
gem 'cld3', '~> 3.2.0' gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.4' gem 'devise', '~> 4.4'
gem 'devise-two-factor', '~> 3.0' 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 'doorkeeper', '~> 4.2'
gem 'fast_blank', '~> 1.0' gem 'fast_blank', '~> 1.0'
gem 'fastimage'
gem 'goldfinger', '~> 2.1' gem 'goldfinger', '~> 2.1'
gem 'hiredis', '~> 0.6' gem 'hiredis', '~> 0.6'
gem 'redis-namespace', '~> 1.5' gem 'redis-namespace', '~> 1.5'
@@ -69,6 +78,8 @@ gem 'simple-navigation', '~> 4.0'
gem 'simple_form', '~> 3.4' gem 'simple_form', '~> 3.4'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
gem 'strong_migrations' gem 'strong_migrations'
gem 'tty-command'
gem 'tty-prompt'
gem 'twitter-text', '~> 1.14' gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2017' gem 'tzinfo-data', '~> 1.2017'
gem 'webpacker', '~> 3.0' gem 'webpacker', '~> 3.0'
@@ -85,6 +96,10 @@ group :development, :test do
gem 'rspec-rails', '~> 3.7' gem 'rspec-rails', '~> 3.7'
end end
group :production, :test do
gem 'private_address_check', '~> 0.4.1'
end
group :test do group :test do
gem 'capybara', '~> 2.15' gem 'capybara', '~> 2.15'
gem 'climate_control', '~> 0.2' gem 'climate_control', '~> 0.2'
@@ -105,6 +120,7 @@ group :development do
gem 'bullet', '~> 5.5' gem 'bullet', '~> 5.5'
gem 'letter_opener', '~> 1.4' gem 'letter_opener', '~> 1.4'
gem 'letter_opener_web', '~> 1.3' gem 'letter_opener_web', '~> 1.3'
gem 'memory_profiler'
gem 'rubocop', require: false gem 'rubocop', require: false
gem 'brakeman', '~> 4.0', require: false gem 'brakeman', '~> 4.0', require: false
gem 'bundler-audit', '~> 0.6', 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 GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
@@ -137,6 +146,9 @@ GEM
devise (~> 4.0) devise (~> 4.0)
railties (< 5.2) railties (< 5.2)
rotp (~> 2.0) rotp (~> 2.0)
devise_pam_authenticatable2 (8.0.1)
devise (>= 4.0.0)
rpam2 (~> 3.0)
diff-lcs (1.3) diff-lcs (1.3)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20170404)
@@ -151,16 +163,28 @@ GEM
json json
thread thread
thread_safe 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) encryptor (3.0.0)
equatable (0.5.0)
erubi (1.7.0) erubi (1.7.0)
et-orbi (1.0.8) et-orbi (1.0.8)
tzinfo tzinfo
excon (0.59.0) excon (0.59.0)
execjs (2.7.0)
fabrication (2.18.0) fabrication (2.18.0)
faker (1.8.4) faker (1.8.4)
i18n (~> 0.5) i18n (~> 0.5)
faraday (0.14.0)
multipart-post (>= 1.2, < 3)
fast_blank (1.0.0) fast_blank (1.0.0)
fastimage (2.1.1)
ffi (1.9.18) ffi (1.9.18)
fog-core (1.45.0) fog-core (1.45.0)
builder builder
@@ -198,8 +222,10 @@ GEM
hamster (3.0.0) hamster (3.0.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
hashdiff (0.3.7) hashdiff (0.3.7)
hashie (3.5.7)
highline (1.7.10) highline (1.7.10)
hiredis (0.6.1) hiredis (0.6.1)
hitimes (1.2.6)
hkdf (0.3.0) hkdf (0.3.0)
htmlentities (4.3.4) htmlentities (4.3.4)
http (3.0.0) http (3.0.0)
@@ -215,7 +241,7 @@ GEM
httplog (0.99.7) httplog (0.99.7)
colorize colorize
rack rack
i18n (0.9.1) i18n (0.9.3)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
i18n-tasks (0.9.19) i18n-tasks (0.9.19)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
@@ -274,6 +300,7 @@ GEM
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
mario-redis-lock (1.2.0) mario-redis-lock (1.2.0)
redis (~> 3, >= 3.0.5) redis (~> 3, >= 3.0.5)
memory_profiler (0.9.10)
method_source (0.9.0) method_source (0.9.0)
microformats (4.0.7) microformats (4.0.7)
json json
@@ -284,9 +311,12 @@ GEM
mimemagic (0.3.2) mimemagic (0.3.2)
mini_mime (1.0.0) mini_mime (1.0.0)
mini_portile2 (2.3.0) mini_portile2 (2.3.0)
minitest (5.10.3) minitest (5.11.3)
msgpack (1.1.0) msgpack (1.1.0)
multi_json (1.12.2) 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-scp (1.2.1)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (4.2.0) net-ssh (4.2.0)
@@ -301,13 +331,23 @@ GEM
sidekiq (>= 3.5.0) sidekiq (>= 3.5.0)
statsd-ruby (~> 1.2.0) statsd-ruby (~> 1.2.0)
oj (3.3.10) 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) orm_adapter (0.5.0)
ostatus2 (2.0.3) ostatus2 (2.0.3)
addressable (~> 2.5) addressable (~> 2.5)
http (~> 3.0) http (~> 3.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
ox (2.8.2) ox (2.8.2)
paperclip (5.1.0) paperclip (5.2.1)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
cocaine (~> 0.5.5) cocaine (~> 0.5.5)
@@ -321,6 +361,9 @@ GEM
parallel parallel
parser (2.4.0.2) parser (2.4.0.2)
ast (~> 2.3) ast (~> 2.3)
pastel (0.7.2)
equatable (~> 0.5.0)
tty-color (~> 0.4.0)
pg (0.21.0) pg (0.21.0)
pghero (1.7.0) pghero (1.7.0)
activerecord activerecord
@@ -333,6 +376,7 @@ GEM
premailer-rails (1.10.1) premailer-rails (1.10.1)
actionmailer (>= 3, < 6) actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.4.1)
pry (0.11.3) pry (0.11.3)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
@@ -420,6 +464,7 @@ GEM
actionpack (>= 4.2.0, < 5.3) actionpack (>= 4.2.0, < 5.3)
railties (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.3)
rotp (2.1.2) rotp (2.1.2)
rpam2 (3.1.0)
rqrcode (0.10.1) rqrcode (0.10.1)
chunky_png (~> 1.0) chunky_png (~> 1.0)
rspec-core (3.7.0) rspec-core (3.7.0)
@@ -451,6 +496,8 @@ GEM
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
ruby-oembed (0.12.0) ruby-oembed (0.12.0)
ruby-progressbar (1.9.0) ruby-progressbar (1.9.0)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
rufus-scheduler (3.4.2) rufus-scheduler (3.4.2)
et-orbi (~> 1.0) et-orbi (~> 1.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
@@ -503,6 +550,8 @@ GEM
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
statsd-ruby (1.2.1) statsd-ruby (1.2.1)
streamio-ffmpeg (3.0.2)
multi_json (~> 1.8)
strong_migrations (0.1.9) strong_migrations (0.1.9)
activerecord (>= 3.2.0) activerecord (>= 3.2.0)
temple (0.8.0) temple (0.8.0)
@@ -512,14 +561,29 @@ GEM
thread (0.2.2) thread (0.2.2)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.8) 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) twitter-text (1.14.7)
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.4) tzinfo (1.2.4)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2017.3) tzinfo-data (1.2017.3)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
uglifier (3.2.0)
execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.4) unf_ext (0.0.7.4)
@@ -541,6 +605,7 @@ GEM
websocket-driver (0.6.5) websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.3) websocket-extensions (0.1.3)
wisper (2.0.0)
xpath (2.1.0) xpath (2.1.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
@@ -566,15 +631,18 @@ DEPENDENCIES
capistrano-yarn (~> 2.0) capistrano-yarn (~> 2.0)
capybara (~> 2.15) capybara (~> 2.15)
charlock_holmes (~> 0.7.5) charlock_holmes (~> 0.7.5)
chewy (~> 0.10)!
cld3 (~> 3.2.0) cld3 (~> 3.2.0)
climate_control (~> 0.2) climate_control (~> 0.2)
devise (~> 4.4) devise (~> 4.4)
devise-two-factor (~> 3.0) devise-two-factor (~> 3.0)
devise_pam_authenticatable2 (~> 8.0)
doorkeeper (~> 4.2) doorkeeper (~> 4.2)
dotenv-rails (~> 2.2) dotenv-rails (~> 2.2)
fabrication (~> 2.18) fabrication (~> 2.18)
faker (~> 1.7) faker (~> 1.7)
fast_blank (~> 1.0) fast_blank (~> 1.0)
fastimage
fog-core (~> 1.45) fog-core (~> 1.45)
fog-local (~> 0.4) fog-local (~> 0.4)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
@@ -596,11 +664,16 @@ DEPENDENCIES
link_header (~> 0.0) link_header (~> 0.0)
lograge (~> 0.7) lograge (~> 0.7)
mario-redis-lock (~> 1.2) mario-redis-lock (~> 1.2)
memory_profiler
microformats (~> 4.0) microformats (~> 4.0)
mime-types (~> 3.1) mime-types (~> 3.1)
net-ldap (~> 0.10)
nokogiri (~> 1.8) nokogiri (~> 1.8)
nsa (~> 0.2) nsa (~> 0.2)
oj (~> 3.3) oj (~> 3.3)
omniauth (~> 1.2)
omniauth-cas (~> 1.1)
omniauth-saml (~> 1.10)
ostatus2 (~> 2.0) ostatus2 (~> 2.0)
ox (~> 2.8) ox (~> 2.8)
paperclip (~> 5.1) paperclip (~> 5.1)
@@ -610,6 +683,7 @@ DEPENDENCIES
pghero (~> 1.7) pghero (~> 1.7)
pkg-config (~> 1.2) pkg-config (~> 1.2)
premailer-rails premailer-rails
private_address_check (~> 0.4.1)
pry-rails (~> 0.3) pry-rails (~> 0.3)
puma (~> 3.10) puma (~> 3.10)
pundit (~> 1.1) pundit (~> 1.1)
@@ -640,10 +714,12 @@ DEPENDENCIES
simple_form (~> 3.4) simple_form (~> 3.4)
simplecov (~> 0.14) simplecov (~> 0.14)
sprockets-rails (~> 3.2) sprockets-rails (~> 3.2)
streamio-ffmpeg (~> 3.0)
strong_migrations strong_migrations
tty-command
tty-prompt
twitter-text (~> 1.14) twitter-text (~> 1.14)
tzinfo-data (~> 1.2017) tzinfo-data (~> 1.2017)
uglifier (~> 3.2)
webmock (~> 3.0) webmock (~> 3.0)
webpacker (~> 3.0) webpacker (~> 3.0)
webpush 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. **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 [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 \ libidn11-dev \
libprotobuf-dev \ libprotobuf-dev \
libreadline-dev \ libreadline-dev \
libpam0g-dev \
-y -y
# Install rvm # Install rvm
@@ -48,7 +49,7 @@ curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-instal
source /home/vagrant/.rvm/scripts/rvm source /home/vagrant/.rvm/scripts/rvm
# Install Ruby # Install Ruby
rvm install ruby-$RUBY_VERSION rvm reinstall ruby-$RUBY_VERSION --disable-binary
# Configure database # Configure database
sudo -u postgres createuser -U postgres vagrant -s sudo -u postgres createuser -U postgres vagrant -s
@@ -79,7 +80,7 @@ VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64" config.vm.box = "ubuntu/xenial64"
config.vm.provider :virtualbox do |vb| config.vm.provider :virtualbox do |vb|
vb.name = "mastodon" 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 def initial_state_params
{ {
settings: {}, settings: { known_fediverse: Setting.show_known_fediverse_at_about_page },
token: current_session&.token, token: current_session&.token,
} }
end end

View File

@@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class AccountsController < ApplicationController class AccountsController < ApplicationController
PAGE_SIZE = 20
include AccountControllerConcern include AccountControllerConcern
before_action :set_cache_headers before_action :set_cache_headers
@@ -16,13 +18,16 @@ class AccountsController < ApplicationController
end end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? @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) @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 end
format.atom do 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? })) render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.reject { |entry| entry.status.nil? }))
end end
@@ -69,13 +74,22 @@ class AccountsController < ApplicationController
@account = Account.find_local!(params[:username]) @account = Account.find_local!(params[:username])
end 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? 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? 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 else
short_account_url(@account, max_id: @statuses.last.id) short_account_url(@account, max_id: max_id, min_id: min_id)
end end
end end
@@ -86,4 +100,12 @@ class AccountsController < ApplicationController
def replies_requested? def replies_requested?
request.path.ends_with?('/with_replies') request.path.ends_with?('/with_replies')
end 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 end

View File

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

View File

@@ -1,10 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::OutboxesController < Api::BaseController class ActivityPub::OutboxesController < Api::BaseController
include SignatureVerification
before_action :set_account before_action :set_account
def show 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) @statuses = cache_collection(@statuses, Status)
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' 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 show_staff_badge
bootstrap_timeline_accounts bootstrap_timeline_accounts
thumbnail thumbnail
hero
min_invite_role min_invite_role
activity_api_enabled activity_api_enabled
peers_api_enabled peers_api_enabled
show_known_fediverse_at_about_page
).freeze ).freeze
BOOLEAN_SETTINGS = %w( BOOLEAN_SETTINGS = %w(
@@ -28,10 +30,12 @@ module Admin
show_staff_badge show_staff_badge
activity_api_enabled activity_api_enabled
peers_api_enabled peers_api_enabled
show_known_fediverse_at_about_page
).freeze ).freeze
UPLOAD_SETTINGS = %w( UPLOAD_SETTINGS = %w(
thumbnail thumbnail
hero
).freeze ).freeze
def edit def edit

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -13,9 +13,9 @@ class Api::V1::AccountsController < Api::BaseController
end end
def follow 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) render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
end end
@@ -26,7 +26,7 @@ class Api::V1::AccountsController < Api::BaseController
end end
def mute 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 render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
end end

View File

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

View File

@@ -13,14 +13,14 @@ class Api::V1::ReportsController < Api::BaseController
end end
def create def create
@report = current_account.reports.create!( @report = ReportService.new.call(
target_account: reported_account, current_account,
reported_account,
status_ids: reported_status_ids, 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 render json: @report, serializer: REST::ReportSerializer
end end
@@ -39,6 +39,6 @@ class Api::V1::ReportsController < Api::BaseController
end end
def report_params def report_params
params.permit(:account_id, :comment, status_ids: []) params.permit(:account_id, :comment, :forward, status_ids: [])
end end
end end

View File

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

View File

@@ -21,15 +21,23 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end end
def public_statuses def public_statuses
public_timeline_statuses.paginate_by_max_id( statuses = public_timeline_statuses.paginate_by_max_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id], params[:max_id],
params[:since_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 public_timeline_statuses def public_timeline_statuses
Status.as_public_timeline(current_account, params[:local]) Status.as_public_timeline(current_account, truthy_param?(:local))
end end
def insert_pagination_headers def insert_pagination_headers
@@ -37,7 +45,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.permit(:local, :limit).merge(core_params) params.permit(:local, :limit, :only_media).merge(core_params)
end end
def next_path def next_path

View File

@@ -29,16 +29,24 @@ class Api::V1::Timelines::TagController < Api::BaseController
if @tag.nil? if @tag.nil?
[] []
else else
tag_timeline_statuses.paginate_by_max_id( statuses = tag_timeline_statuses.paginate_by_max_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id], params[:max_id],
params[:since_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
end end
def tag_timeline_statuses def tag_timeline_statuses
Status.as_tag_timeline(@tag, current_account, params[:local]) Status.as_tag_timeline(@tag, current_account, truthy_param?(:local))
end end
def insert_pagination_headers def insert_pagination_headers
@@ -46,7 +54,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.permit(:local, :limit).merge(core_params) params.permit(:local, :limit, :only_media).merge(core_params)
end end
def next_path def next_path

View File

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

View File

@@ -2,4 +2,28 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth' 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 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 protected
def update_resource(resource, params)
params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
super
end
def build_resource(hash = nil) def build_resource(hash = nil)
super(hash) 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] prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
before_action :set_instance_presenter, only: [:new] 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 def create
super do |resource| super do |resource|
remember_me(resource) remember_me(resource)
@@ -28,7 +37,11 @@ class Auth::SessionsController < Devise::SessionsController
if session[:otp_user_id] if session[:otp_user_id]
User.find(session[:otp_user_id]) User.find(session[:otp_user_id])
elsif user_params[:email] 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
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) @follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
respond_to do |format| 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 format.json do
render json: collection_presenter, 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) @follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
respond_to do |format| 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 format.json do
render json: collection_presenter, render json: collection_presenter,

View File

@@ -3,20 +3,26 @@
class MediaController < ApplicationController class MediaController < ApplicationController
include Authorization include Authorization
before_action :verify_permitted_status before_action :set_media_attachment
before_action :verify_permitted_status!
def show 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 end
private private
def media_attachment def set_media_attachment
MediaAttachment.attached.find_by!(shortcode: params[:id]) @media_attachment = MediaAttachment.attached.find_by!(shortcode: params[:id] || params[:medium_id])
end end
def verify_permitted_status def verify_permitted_status!
authorize media_attachment.status, :show? authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code # Reraise in order to get a 404 instead of a 403 error code
raise ActiveRecord::RecordNotFound raise ActiveRecord::RecordNotFound

View File

@@ -1,11 +1,23 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ExportsController < ApplicationController class Settings::ExportsController < ApplicationController
include Authorization
layout 'admin' layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
def show 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
end end

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ class StreamEntriesController < ApplicationController
before_action :set_stream_entry before_action :set_stream_entry
before_action :set_link_headers before_action :set_link_headers
before_action :check_account_suspension before_action :check_account_suspension
before_action :set_cache_headers
def show def show
respond_to do |format| respond_to do |format|
@@ -19,6 +20,10 @@ class StreamEntriesController < ApplicationController
end end
format.atom do 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)) render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
end end
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) => { return (dispatch, getState) => {
dispatch(changeUploadComposeRequest()); 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)); dispatch(changeUploadComposeSuccess(response.data));
}).catch(error => { }).catch(error => {
dispatch(changeUploadComposeFail(id, 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_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE';
export function initReport(account, status) { export function initReport(account, status) {
return dispatch => { return dispatch => {
@@ -45,6 +46,7 @@ export function submitReport() {
account_id: getState().getIn(['reports', 'new', 'account_id']), account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']), status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment']), comment: getState().getIn(['reports', 'new', 'comment']),
forward: getState().getIn(['reports', 'new', 'forward']),
}).then(response => { }).then(response => {
dispatch(closeModal()); dispatch(closeModal());
dispatch(submitReportSuccess(response.data)); dispatch(submitReportSuccess(response.data));
@@ -78,3 +80,10 @@ export function changeReportComment(comment) {
comment, comment,
}; };
}; };
export function changeReportForward(forward) {
return {
type: REPORT_FORWARD_CHANGE,
forward,
};
};

View File

@@ -1,4 +1,5 @@
import api from '../api'; import api from '../api';
import { fetchRelationships } from './accounts';
export const SEARCH_CHANGE = 'SEARCH_CHANGE'; export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_CLEAR = 'SEARCH_CLEAR'; export const SEARCH_CLEAR = 'SEARCH_CLEAR';
@@ -38,6 +39,7 @@ export function submitSearch() {
}, },
}).then(response => { }).then(response => {
dispatch(fetchSearchSuccess(response.data)); dispatch(fetchSearchSuccess(response.data));
dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
}).catch(error => { }).catch(error => {
dispatch(fetchSearchFail(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 refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); 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 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 refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); 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 expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public'); export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); 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 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 expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);

View File

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

View File

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

View File

@@ -6,12 +6,32 @@ import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { isIOS } from '../is_mobile'; import { isIOS } from '../is_mobile';
import classNames from 'classnames'; import classNames from 'classnames';
import { autoPlayGif } from '../initial_state'; import { autoPlayGif, displaySensitiveMedia } from '../initial_state';
const messages = defineMessages({ const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, 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 { class Item extends React.PureComponent {
static contextTypes = { static contextTypes = {
@@ -24,6 +44,8 @@ class Item extends React.PureComponent {
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
containerWidth: PropTypes.number,
containerHeight: PropTypes.number,
}; };
static defaultProps = { static defaultProps = {
@@ -62,7 +84,7 @@ class Item extends React.PureComponent {
} }
render () { render () {
const { attachment, index, size, standalone } = this.props; const { attachment, index, size, standalone, containerWidth, containerHeight } = this.props;
let width = 50; let width = 50;
let height = 100; let height = 100;
@@ -116,16 +138,40 @@ class Item extends React.PureComponent {
let thumbnail = ''; let thumbnail = '';
if (attachment.get('type') === 'image') { 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 previewWidth = attachment.getIn(['meta', 'small', 'width']);
const originalUrl = attachment.get('url'); const originalUrl = attachment.get('url');
const originalWidth = attachment.getIn(['meta', 'original', 'width']); const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const originalHeight = attachment.getIn(['meta', 'original', 'height']);
const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number'; const hasSize = typeof originalWidth === 'number' && typeof previewWidth === 'number';
const srcSet = hasSize ? `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w` : null; 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 = ( thumbnail = (
<a <a
@@ -134,7 +180,14 @@ class Item extends React.PureComponent {
onClick={this.handleClick} onClick={this.handleClick}
target='_blank' 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> </a>
); );
} else if (attachment.get('type') === 'gifv') { } else if (attachment.get('type') === 'gifv') {
@@ -187,7 +240,7 @@ export default class MediaGallery extends React.PureComponent {
}; };
state = { state = {
visible: !this.props.sensitive, visible: !this.props.sensitive || displaySensitiveMedia,
}; };
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
@@ -205,7 +258,7 @@ export default class MediaGallery extends React.PureComponent {
} }
handleRef = (node) => { handleRef = (node) => {
if (node && this.isStandaloneEligible()) { if (node /*&& this.isStandaloneEligible()*/) {
// offsetWidth triggers a layout, so only calculate when we need to // offsetWidth triggers a layout, so only calculate when we need to
this.setState({ this.setState({
width: node.offsetWidth, width: node.offsetWidth,
@@ -227,15 +280,12 @@ export default class MediaGallery extends React.PureComponent {
const style = {}; const style = {};
if (this.isStandaloneEligible()) { if (this.isStandaloneEligible()) {
if (!visible && width) { if (width) {
// only need to forcibly set the height in "sensitive" mode
style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']); 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 { } else {
// crop the image
style.height = height; style.height = height;
} }
@@ -249,7 +299,7 @@ export default class MediaGallery extends React.PureComponent {
} }
children = ( 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__warning'>{warning}</span>
<span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span> <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</button> </button>
@@ -260,12 +310,12 @@ export default class MediaGallery extends React.PureComponent {
if (this.isStandaloneEligible()) { if (this.isStandaloneEligible()) {
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />; children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
} else { } 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 ( 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 })}> <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} /> <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
</div> </div>

View File

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

View File

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

View File

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

View File

@@ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent {
render () { render () {
const { media } = this.props; const { media } = this.props;
const status = media.get('status'); 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') { if (media.get('type') === 'gifv') {
content = <span className='media-gallery__gifv__label'>GIF</span>; content = <span className='media-gallery__gifv__label'>GIF</span>;
} }
if (!status.get('sensitive')) { if (!status.get('sensitive')) {
style = { backgroundImage: `url(${media.get('preview_url')})` }; style.backgroundImage = `url(${media.get('preview_url')})`;
style.backgroundPosition = `${x}% ${y}%`;
} }
return ( return (
<div className='account-gallery__item'> <div className='account-gallery__item'>
<Permalink <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}>
to={`/statuses/${status.get('id')}`}
href={status.get('url')}
style={style}
>
{content} {content}
</Permalink> </Permalink>
</div> </div>

View File

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

View File

@@ -6,6 +6,8 @@ import ActionBar from '../../account/components/action_bar';
import MissingIndicator from '../../../components/missing_indicator'; import MissingIndicator from '../../../components/missing_indicator';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import MovedNote from './moved_note'; import MovedNote from './moved_note';
import { FormattedMessage } from 'react-intl';
import { NavLink } from 'react-router-dom';
export default class Header extends ImmutablePureComponent { export default class Header extends ImmutablePureComponent {
@@ -91,6 +93,12 @@ export default class Header extends ImmutablePureComponent {
onBlockDomain={this.handleBlockDomain} onBlockDomain={this.handleBlockDomain}
onUnblockDomain={this.handleUnblockDomain} 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> </div>
); );
} }

View File

@@ -12,11 +12,15 @@ import ColumnBackButton from '../../components/column_back_button';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()), const path = withReplies ? `${accountId}:with_replies` : accountId;
isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']), 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) @connect(mapStateToProps)
export default class AccountTimeline extends ImmutablePureComponent { export default class AccountTimeline extends ImmutablePureComponent {
@@ -27,23 +31,24 @@ export default class AccountTimeline extends ImmutablePureComponent {
statusIds: ImmutablePropTypes.list, statusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
withReplies: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId)); 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) { 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(fetchAccount(nextProps.params.accountId));
this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId)); this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
} }
} }
handleScrollToBottom = () => { handleScrollToBottom = () => {
if (!this.props.isLoading && this.props.hasMore) { 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 Overlay from 'react-overlays/lib/Overlay';
import Motion from '../../ui/util/optional_motion'; import Motion from '../../ui/util/optional_motion';
import spring from 'react-motion/lib/spring'; import spring from 'react-motion/lib/spring';
import { searchEnabled } from '../../../initial_state';
const messages = defineMessages({ const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@@ -17,7 +18,7 @@ class SearchPopout extends React.PureComponent {
render () { render () {
const { style } = this.props; 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 ( return (
<div style={{ ...style, position: 'absolute', width: 285 }}> <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 }) }}> <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> <li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
</ul> </ul>
<FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' /> {extraInformation}
</div> </div>
)} )}
</Motion> </Motion>

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Upload from '../components/upload'; import Upload from '../components/upload';
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose'; import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
import { openModal } from '../../../actions/modal';
const mapStateToProps = (state, { id }) => ({ const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
@@ -13,7 +14,11 @@ const mapDispatchToProps = dispatch => ({
}, },
onDescriptionChange: (id, description) => { 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 { FormattedMessage } from 'react-intl';
import { me } from '../../../initial_state'; 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 => ({ const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']), 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 spring from 'react-motion/lib/spring';
import SearchResultsContainer from './containers/search_results_container'; import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose'; import { changeComposing } from '../../actions/compose';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
const messages = defineMessages({ const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, 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}> <div className='drawer__inner' onFocus={this.onFocus}>
<NavigationContainer onClose={this.onBlur} /> <NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer /> <ComposeFormContainer />
{multiColumn && <div className='mastodon' />} {multiColumn && (
<div className='drawer__inner__mastodon'>
<img alt='' src={elephantUIPlane} />
</div>
)}
</div> </div>
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> <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; 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 { export default class Card extends React.PureComponent {
static propTypes = { static propTypes = {
@@ -33,9 +66,16 @@ export default class Card extends React.PureComponent {
}; };
state = { state = {
width: 0, width: 280,
embedded: false,
}; };
componentWillReceiveProps (nextProps) {
if (this.props.card !== nextProps.card) {
this.setState({ embedded: false });
}
}
handlePhotoClick = () => { handlePhotoClick = () => {
const { card, onOpenMedia } = this.props; const { card, onOpenMedia } = this.props;
@@ -57,56 +97,14 @@ export default class Card extends React.PureComponent {
); );
}; };
renderLink () { handleEmbedClick = () => {
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 () {
const { card } = this.props; const { card } = this.props;
return ( if (card.get('type') === 'photo') {
<img this.handlePhotoClick();
className='status-card-photo' } else {
onClick={this.handlePhotoClick} this.setState({ embedded: true });
role='button' }
tabIndex='0'
src={card.get('embed_url')}
alt={card.get('title')}
width={card.get('width')}
height={card.get('height')}
/>
);
} }
setRef = c => { setRef = c => {
@@ -117,7 +115,7 @@ export default class Card extends React.PureComponent {
renderVideo () { renderVideo () {
const { card } = this.props; const { card } = this.props;
const content = { __html: card.get('html') }; const content = { __html: addAutoPlay(card.get('html')) };
const { width } = this.state; const { width } = this.state;
const ratio = card.get('width') / card.get('height'); const ratio = card.get('width') / card.get('height');
const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
@@ -125,7 +123,7 @@ export default class Card extends React.PureComponent {
return ( return (
<div <div
ref={this.setRef} ref={this.setRef}
className='status-card-video' className='status-card__image status-card-video'
dangerouslySetInnerHTML={content} dangerouslySetInnerHTML={content}
style={{ height }} style={{ height }}
/> />
@@ -133,23 +131,76 @@ export default class Card extends React.PureComponent {
} }
render () { render () {
const { card } = this.props; const { card, maxDescription } = this.props;
const { width, embedded } = this.state;
if (card === null) { if (card === null) {
return null; return null;
} }
switch(card.get('type')) { const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
case 'link': const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link';
return this.renderLink(); const className = classnames('status-card', { horizontal });
case 'photo': const interactive = card.get('type') !== 'link';
return this.renderPhoto(); 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>;
case 'video': const ratio = card.get('width') / card.get('height');
return this.renderVideo(); const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio);
case 'rich':
default: const description = (
return null; <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')} src={video.get('url')}
width={300} width={300}
height={150} height={150}
inline
onOpenVideo={this.handleOpenVideo} onOpenVideo={this.handleOpenVideo}
sensitive={status.get('sensitive')} sensitive={status.get('sensitive')}
/> />

View File

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

View File

@@ -6,6 +6,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeableViews from 'react-swipeable-views'; import ReactSwipeableViews from 'react-swipeable-views';
import { links, getIndex, getLink } from './tabs_bar'; import { links, getIndex, getLink } from './tabs_bar';
import { Link } from 'react-router-dom';
import BundleContainer from '../containers/bundle_container'; import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading'; import ColumnLoading from './column_loading';
@@ -152,11 +153,19 @@ export default class ColumnsArea extends ImmutablePureComponent {
this.pendingIndex = null; this.pendingIndex = null;
if (singleColumn) { if (singleColumn) {
return columnIndex !== -1 ? ( 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>;
<ReactSwipeableViews index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }}>
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)} {links.map(this.renderView)}
</ReactSwipeableViews> </ReactSwipeableViews>,
) : <div className='columns-area'>{children}</div>;
floatingActionButton,
] : [
<div className='columns-area'>{children}</div>,
floatingActionButton,
];
} }
return ( 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 VideoModal from './video_modal';
import BoostModal from './boost_modal'; import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal'; import ConfirmationModal from './confirmation_modal';
import FocalPointModal from './focal_point_modal';
import { import {
OnboardingModal, OnboardingModal,
MuteModal, MuteModal,
@@ -27,6 +28,7 @@ const MODAL_COMPONENTS = {
'ACTIONS': () => Promise.resolve({ default: ActionsModal }), 'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
'EMBED': EmbedModal, 'EMBED': EmbedModal,
'LIST_EDITOR': ListEditor, 'LIST_EDITOR': ListEditor,
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
}; };
export default class ModalRoot extends React.PureComponent { export default class ModalRoot extends React.PureComponent {

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { changeReportComment, submitReport } from '../../../actions/reports'; import { changeReportComment, changeReportForward, submitReport } from '../../../actions/reports';
import { refreshAccountTimeline } from '../../../actions/timelines'; import { refreshAccountTimeline } from '../../../actions/timelines';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -10,8 +10,11 @@ import StatusCheckBox from '../../report/containers/status_check_box_container';
import { OrderedSet } from 'immutable'; import { OrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Button from '../../../components/button'; import Button from '../../../components/button';
import Toggle from 'react-toggle';
import IconButton from '../../../components/icon_button';
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' }, placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
submit: { id: 'report.submit', defaultMessage: 'Submit' }, submit: { id: 'report.submit', defaultMessage: 'Submit' },
}); });
@@ -26,6 +29,7 @@ const makeMapStateToProps = () => {
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']), isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
account: getAccount(state, accountId), account: getAccount(state, accountId),
comment: state.getIn(['reports', 'new', 'comment']), 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'])), 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, account: ImmutablePropTypes.map,
statusIds: ImmutablePropTypes.orderedSet.isRequired, statusIds: ImmutablePropTypes.orderedSet.isRequired,
comment: PropTypes.string.isRequired, comment: PropTypes.string.isRequired,
forward: PropTypes.bool,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
handleCommentChange = (e) => { handleCommentChange = e => {
this.props.dispatch(changeReportComment(e.target.value)); this.props.dispatch(changeReportComment(e.target.value));
} }
handleForwardChange = e => {
this.props.dispatch(changeReportForward(e.target.checked));
}
handleSubmit = () => { handleSubmit = () => {
this.props.dispatch(submitReport()); this.props.dispatch(submitReport());
} }
@@ -65,26 +74,25 @@ export default class ReportModal extends ImmutablePureComponent {
} }
render () { render () {
const { account, comment, intl, statusIds, isSubmitting } = this.props; const { account, comment, intl, statusIds, isSubmitting, forward, onClose } = this.props;
if (!account) { if (!account) {
return null; return null;
} }
const domain = account.get('acct').split('@')[1];
return ( return (
<div className='modal-root__modal report-modal'> <div className='modal-root__modal report-modal'>
<div className='report-modal__target'> <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> }} /> <FormattedMessage id='report.target' defaultMessage='Report {target}' values={{ target: <strong>{account.get('acct')}</strong> }} />
</div> </div>
<div className='report-modal__container'> <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'> <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 <textarea
className='setting-text light' className='setting-text light'
placeholder={intl.formatMessage(messages.placeholder)} placeholder={intl.formatMessage(messages.placeholder)}
@@ -92,11 +100,26 @@ export default class ReportModal extends ImmutablePureComponent {
onChange={this.handleCommentChange} onChange={this.handleCommentChange}
disabled={isSubmitting} disabled={isSubmitting}
/> />
</div>
</div>
<div className='report-modal__action-bar'> {domain && (
<Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} /> <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>
</div> </div>
); );

View File

@@ -6,14 +6,13 @@ import { debounce } from 'lodash';
import { isUserTouching } from '../../../is_mobile'; import { isUserTouching } from '../../../is_mobile';
export const links = [ 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='/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 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' 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 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) { 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='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} 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/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} /> <WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} 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, component: PropTypes.func.isRequired,
content: PropTypes.node, content: PropTypes.node,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
} componentParams: PropTypes.object,
};
static defaultProps = {
componentParams: {},
};
renderComponent = ({ match }) => { renderComponent = ({ match }) => {
const { component, content, multiColumn } = this.props; const { component, content, multiColumn, componentParams } = this.props;
return ( return (
<BundleContainer fetchComponent={component} loading={this.renderLoading} error={this.renderError}> <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> </BundleContainer>
); );
} }

View File

@@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import classNames from 'classnames'; import classNames from 'classnames';
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen'; import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
import { displaySensitiveMedia } from '../../initial_state';
const messages = defineMessages({ const messages = defineMessages({
play: { id: 'video.play', defaultMessage: 'Play' }, play: { id: 'video.play', defaultMessage: 'Play' },
@@ -29,7 +30,7 @@ const formatTime = secondsNum => {
return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`; return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
}; };
const findElementPosition = el => { export const findElementPosition = el => {
let box; let box;
if (el.getBoundingClientRect && el.parentNode) { if (el.getBoundingClientRect && el.parentNode) {
@@ -60,7 +61,7 @@ const findElementPosition = el => {
}; };
}; };
const getPointerPosition = (el, event) => { export const getPointerPosition = (el, event) => {
const position = {}; const position = {};
const box = findElementPosition(el); const box = findElementPosition(el);
const boxW = el.offsetWidth; const boxW = el.offsetWidth;
@@ -76,7 +77,7 @@ const getPointerPosition = (el, event) => {
pageY = event.changedTouches[0].pageY; 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)); position.x = Math.max(0, Math.min(1, (pageX - boxX) / boxW));
return position; return position;
@@ -96,6 +97,7 @@ export default class Video extends React.PureComponent {
onOpenVideo: PropTypes.func, onOpenVideo: PropTypes.func,
onCloseVideo: PropTypes.func, onCloseVideo: PropTypes.func,
detailed: PropTypes.bool, detailed: PropTypes.bool,
inline: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@@ -104,14 +106,21 @@ export default class Video extends React.PureComponent {
duration: 0, duration: 0,
paused: true, paused: true,
dragging: false, dragging: false,
containerWidth: false,
fullscreen: false, fullscreen: false,
hovered: false, hovered: false,
muted: false, muted: false,
revealed: !this.props.sensitive, revealed: !this.props.sensitive || displaySensitiveMedia,
}; };
setPlayerRef = c => { setPlayerRef = c => {
this.player = c; this.player = c;
if (c) {
this.setState({
containerWidth: c.offsetWidth,
});
}
} }
setVideoRef = c => { setVideoRef = c => {
@@ -245,12 +254,23 @@ export default class Video extends React.PureComponent {
} }
render () { render () {
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props; const { preview, src, inline, startTime, onOpenVideo, onCloseVideo, intl, alt, detailed } = this.props;
const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state; const { containerWidth, currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = (currentTime / duration) * 100; 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 ( 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 <video
ref={this.setVideoRef} ref={this.setVideoRef}
src={src} src={src}
@@ -270,7 +290,7 @@ export default class Video extends React.PureComponent {
onProgress={this.handleProgress} 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__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> <span className='video-player__spoiler__subtitle'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
</button> </button>
@@ -289,10 +309,10 @@ export default class Video extends React.PureComponent {
<div className='video-player__buttons-bar'> <div className='video-player__buttons-bar'>
<div className='video-player__buttons left'> <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 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 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(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) && {(detailed || fullscreen) &&
<span> <span>
@@ -304,9 +324,9 @@ export default class Video extends React.PureComponent {
</div> </div>
<div className='video-player__buttons right'> <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>} {(!fullscreen && onOpenVideo) && <button type='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>} {onCloseVideo && <button type='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> <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> </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 reduceMotion = getMeta('reduce_motion');
export const autoPlayGif = getMeta('auto_play_gif'); export const autoPlayGif = getMeta('auto_play_gif');
export const displaySensitiveMedia = getMeta('display_sensitive_media');
export const unfollowModal = getMeta('unfollow_modal'); export const unfollowModal = getMeta('unfollow_modal');
export const boostModal = getMeta('boost_modal'); export const boostModal = getMeta('boost_modal');
export const deleteModal = getMeta('delete_modal'); export const deleteModal = getMeta('delete_modal');
export const me = getMeta('me'); export const me = getMeta('me');
export const searchEnabled = getMeta('search_enabled');
export default initialState; export default initialState;

View File

@@ -13,7 +13,8 @@
"account.moved_to": "{name} إنتقل إلى :", "account.moved_to": "{name} إنتقل إلى :",
"account.mute": "أكتم @{name}", "account.mute": "أكتم @{name}",
"account.mute_notifications": "كتم إخطارات @{name}", "account.mute_notifications": "كتم إخطارات @{name}",
"account.posts": "المشاركات", "account.posts": "التبويقات",
"account.posts_with_replies": "Toots with replies",
"account.report": "أبلغ عن @{name}", "account.report": "أبلغ عن @{name}",
"account.requested": "في انتظار الموافقة", "account.requested": "في انتظار الموافقة",
"account.share": "مشاركة @{name}'s profile", "account.share": "مشاركة @{name}'s profile",
@@ -50,7 +51,7 @@
"column_header.unpin": "فك التدبيس", "column_header.unpin": "فك التدبيس",
"column_subheading.navigation": "التصفح", "column_subheading.navigation": "التصفح",
"column_subheading.settings": "الإعدادات", "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": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
"compose_form.lock_disclaimer.lock": "مقفل", "compose_form.lock_disclaimer.lock": "مقفل",
"compose_form.placeholder": "فيمَ تفكّر؟", "compose_form.placeholder": "فيمَ تفكّر؟",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "إلغاء", "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.placeholder": "تعليقات إضافية",
"report.submit": "إرسال", "report.submit": "إرسال",
"report.target": "إبلاغ", "report.target": "إبلاغ",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "حالة", "search_popout.tips.status": "حالة",
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية", "search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
"search_popout.tips.user": "مستخدِم", "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}}", "search_results.total": "{count, number} {count, plural, one {result} و {results}}",
"standalone.public_title": "نظرة على ...", "standalone.public_title": "نظرة على ...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "إسحب ثم أفلت للرفع", "upload_area.title": "إسحب ثم أفلت للرفع",
"upload_button.label": "إضافة وسائط", "upload_button.label": "إضافة وسائط",
"upload_form.description": "وصف للمعاقين بصريا", "upload_form.description": "وصف للمعاقين بصريا",
"upload_form.focus": "Crop",
"upload_form.undo": "إلغاء", "upload_form.undo": "إلغاء",
"upload_progress.label": "يرفع...", "upload_progress.label": "يرفع...",
"video.close": "إغلاق الفيديو", "video.close": "إغلاق الفيديو",

View File

@@ -14,6 +14,7 @@
"account.mute": "Mute @{name}", "account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Публикации", "account.posts": "Публикации",
"account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}", "account.report": "Report @{name}",
"account.requested": "В очакване на одобрение", "account.requested": "В очакване на одобрение",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Отказ", "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.placeholder": "Additional comments",
"report.submit": "Submit", "report.submit": "Submit",
"report.target": "Reporting", "report.target": "Reporting",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...", "standalone.public_title": "A look inside...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Drag & drop to upload", "upload_area.title": "Drag & drop to upload",
"upload_button.label": "Добави медия", "upload_button.label": "Добави медия",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Отмяна", "upload_form.undo": "Отмяна",
"upload_progress.label": "Uploading...", "upload_progress.label": "Uploading...",
"video.close": "Close video", "video.close": "Close video",

View File

@@ -1,29 +1,30 @@
{ {
"account.block": "Bloquejar @{name}", "account.block": "Bloca @{name}",
"account.block_domain": "Amagar tot de {domain}", "account.block_domain": "Amaga-ho tot de {domain}",
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Edita el perfil",
"account.follow": "Seguir", "account.follow": "Segueix",
"account.followers": "Seguidors", "account.followers": "Seguidors",
"account.follows": "Seguint", "account.follows": "Seguint",
"account.follows_you": "et segueix", "account.follows_you": "Et segueix",
"account.hide_reblogs": "Amaga els impulsos de @{name}", "account.hide_reblogs": "Amaga els impulsos de @{name}",
"account.media": "Media", "account.media": "Media",
"account.mention": "Esmentar @{name}", "account.mention": "Esmentar @{name}",
"account.moved_to": "{name} s'ha mogut a:", "account.moved_to": "{name} s'ha mogut a:",
"account.mute": "Silenciar @{name}", "account.mute": "Silencia @{name}",
"account.mute_notifications": "Notificacions desactivades de @{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.report": "Informe @{name}",
"account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment", "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.show_reblogs": "Mostra els impulsos de @{name}",
"account.unblock": "Desbloquejar @{name}", "account.unblock": "Desbloca @{name}",
"account.unblock_domain": "Mostra {domain}", "account.unblock_domain": "Mostra {domain}",
"account.unfollow": "Deixar de seguir", "account.unfollow": "Deixa de seguir",
"account.unmute": "Treure silenci de @{name}", "account.unmute": "Treure silenci de @{name}",
"account.unmute_notifications": "Activar notificacions 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", "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.body": "S'ha produït un error en carregar aquest component.",
"bundle_column_error.retry": "Torna-ho a provar", "bundle_column_error.retry": "Torna-ho a provar",
@@ -31,7 +32,7 @@
"bundle_modal_error.close": "Tanca", "bundle_modal_error.close": "Tanca",
"bundle_modal_error.message": "S'ha produït un error en carregar aquest component.", "bundle_modal_error.message": "S'ha produït un error en carregar aquest component.",
"bundle_modal_error.retry": "Torna-ho a provar", "bundle_modal_error.retry": "Torna-ho a provar",
"column.blocks": "Usuaris bloquejats", "column.blocks": "Usuaris blocats",
"column.community": "Línia de temps local", "column.community": "Línia de temps local",
"column.favourites": "Favorits", "column.favourites": "Favorits",
"column.follow_requests": "Peticions per seguir-te", "column.follow_requests": "Peticions per seguir-te",
@@ -45,46 +46,46 @@
"column_header.hide_settings": "Amaga la configuració", "column_header.hide_settings": "Amaga la configuració",
"column_header.moveLeft_settings": "Mou la columna cap a l'esquerra", "column_header.moveLeft_settings": "Mou la columna cap a l'esquerra",
"column_header.moveRight_settings": "Mou la columna cap a la dreta", "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.show_settings": "Mostra la configuració",
"column_header.unpin": "Deslligar", "column_header.unpin": "No fixis",
"column_subheading.navigation": "Navegació", "column_subheading.navigation": "Navegació",
"column_subheading.settings": "Configuració", "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.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": "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.placeholder": "En què estàs pensant?",
"compose_form.publish": "Toot", "compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!", "compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marcar multimèdia com a sensible", "compose_form.sensitive": "Marca el contingut multimèdia com a sensible",
"compose_form.spoiler": "Amagar text darrera l'advertència", "compose_form.spoiler": "Amaga el text darrera darrere un avís",
"compose_form.spoiler_placeholder": "Escriu l'advertència aquí", "compose_form.spoiler_placeholder": "Escriu l'avís aquí",
"confirmation_modal.cancel": "Cancel·lar", "confirmation_modal.cancel": "Cancel·la",
"confirmations.block.confirm": "Bloquejar", "confirmations.block.confirm": "Bloca",
"confirmations.block.message": "Estàs segur que vols bloquejar {name}?", "confirmations.block.message": "Estàs segur que vols blocar {name}?",
"confirmations.delete.confirm": "Esborrar", "confirmations.delete.confirm": "Suprimeix",
"confirmations.delete.message": "Estàs segur que vols esborrar aquest estat?", "confirmations.delete.message": "Estàs segur que vols suprimir aquest estat?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Suprimeix",
"confirmations.delete_list.message": "Estàs segur que vols esborrar permanenment aquesta llista?", "confirmations.delete_list.message": "Estàs segur que vols suprimir permanentment aquesta llista?",
"confirmations.domain_block.confirm": "Amagar tot el domini", "confirmations.domain_block.confirm": "Amaga 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.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": "Silenciar", "confirmations.mute.confirm": "Silencia",
"confirmations.mute.message": "Estàs segur que vols silenciar {name}?", "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}?", "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.instructions": "Incrusta aquest estat al lloc web copiant el codi a continuació.",
"embed.preview": "Aquí tenim quin aspecte tindrá:", "embed.preview": "Aquí tenim quin aspecte tindrá:",
"emoji_button.activity": "Activitat", "emoji_button.activity": "Activitat",
"emoji_button.custom": "Personalitzat", "emoji_button.custom": "Personalitzat",
"emoji_button.flags": "Marques", "emoji_button.flags": "Banderes",
"emoji_button.food": "Menjar i Beure", "emoji_button.food": "Menjar i beure",
"emoji_button.label": "Inserir emoji", "emoji_button.label": "Insereix un emoji",
"emoji_button.nature": "Natura", "emoji_button.nature": "Natura",
"emoji_button.not_found": "Emojos no!! (╯°□°)╯︵ ┻━┻", "emoji_button.not_found": "Emojos no!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objectes", "emoji_button.objects": "Objectes",
"emoji_button.people": "Gent", "emoji_button.people": "Gent",
"emoji_button.recent": "Freqüentment utilitzat", "emoji_button.recent": "Usats freqüentment",
"emoji_button.search": "Cercar...", "emoji_button.search": "Cerca...",
"emoji_button.search_results": "Resultats de la cerca", "emoji_button.search_results": "Resultats de la cerca",
"emoji_button.symbols": "Símbols", "emoji_button.symbols": "Símbols",
"emoji_button.travel": "Viatges i Llocs", "emoji_button.travel": "Viatges i Llocs",
@@ -207,6 +208,9 @@
"relative_time.minutes": "fa {number} minuts", "relative_time.minutes": "fa {number} minuts",
"relative_time.seconds": "fa {number} segons", "relative_time.seconds": "fa {number} segons",
"reply_indicator.cancel": "Cancel·lar", "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.placeholder": "Comentaris addicionals",
"report.submit": "Enviar", "report.submit": "Enviar",
"report.target": "Informes", "report.target": "Informes",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "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.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_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}}", "search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
"standalone.public_title": "Una mirada a l'interior ...", "standalone.public_title": "Una mirada a l'interior ...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arrossega i deixa anar per carregar", "upload_area.title": "Arrossega i deixa anar per carregar",
"upload_button.label": "Afegir multimèdia", "upload_button.label": "Afegir multimèdia",
"upload_form.description": "Descriure els problemes visuals", "upload_form.description": "Descriure els problemes visuals",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfer", "upload_form.undo": "Desfer",
"upload_progress.label": "Pujant...", "upload_progress.label": "Pujant...",
"video.close": "Tancar el vídeo", "video.close": "Tancar el vídeo",

View File

@@ -14,6 +14,7 @@
"account.mute": "@{name} stummschalten", "account.mute": "@{name} stummschalten",
"account.mute_notifications": "Benachrichtigungen von @{name} verbergen", "account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
"account.posts": "Beiträge", "account.posts": "Beiträge",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} melden", "account.report": "@{name} melden",
"account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen", "account.requested": "Warte auf Erlaubnis. Klicke zum Abbrechen",
"account.share": "Profil von @{name} teilen", "account.share": "Profil von @{name} teilen",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Abbrechen", "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.placeholder": "Zusätzliche Kommentare",
"report.submit": "Absenden", "report.submit": "Absenden",
"report.target": "{target} melden", "report.target": "{target} melden",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
"standalone.public_title": "Ein kleiner Einblick …", "standalone.public_title": "Ein kleiner Einblick …",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Zum Hochladen hereinziehen", "upload_area.title": "Zum Hochladen hereinziehen",
"upload_button.label": "Mediendatei hinzufügen", "upload_button.label": "Mediendatei hinzufügen",
"upload_form.description": "Für Menschen mit Sehbehinderung beschreiben", "upload_form.description": "Für Menschen mit Sehbehinderung beschreiben",
"upload_form.focus": "Crop",
"upload_form.undo": "Entfernen", "upload_form.undo": "Entfernen",
"upload_progress.label": "Wird hochgeladen …", "upload_progress.label": "Wird hochgeladen …",
"video.close": "Video schließen", "video.close": "Video schließen",

View File

@@ -317,12 +317,20 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "Toots",
"id": "account.posts"
},
{
"defaultMessage": "Toots with replies",
"id": "account.posts_with_replies"
},
{ {
"defaultMessage": "Media", "defaultMessage": "Media",
"id": "account.media" "id": "account.media"
} }
], ],
"path": "app/javascript/mastodon/features/account_gallery/index.json" "path": "app/javascript/mastodon/features/account_timeline/components/header.json"
}, },
{ {
"descriptors": [ "descriptors": [
@@ -433,7 +441,7 @@
"id": "account.view_full_profile" "id": "account.view_full_profile"
}, },
{ {
"defaultMessage": "Posts", "defaultMessage": "Toots",
"id": "account.posts" "id": "account.posts"
}, },
{ {
@@ -650,6 +658,18 @@
}, },
{ {
"descriptors": [ "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}}", "defaultMessage": "{count, number} {count, plural, one {result} other {results}}",
"id": "search_results.total" "id": "search_results.total"
@@ -706,13 +726,17 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "Describe for the visually impaired",
"id": "upload_form.description"
},
{ {
"defaultMessage": "Undo", "defaultMessage": "Undo",
"id": "upload_form.undo" "id": "upload_form.undo"
}, },
{ {
"defaultMessage": "Describe for the visually impaired", "defaultMessage": "Crop",
"id": "upload_form.description" "id": "upload_form.focus"
} }
], ],
"path": "app/javascript/mastodon/features/compose/components/upload.json" "path": "app/javascript/mastodon/features/compose/components/upload.json"
@@ -1230,6 +1254,15 @@
], ],
"path": "app/javascript/mastodon/features/public_timeline/index.json" "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": [ "descriptors": [
{ {
@@ -1554,6 +1587,18 @@
{ {
"defaultMessage": "Report {target}", "defaultMessage": "Report {target}",
"id": "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" "path": "app/javascript/mastodon/features/ui/components/report_modal.json"

View File

@@ -13,7 +13,8 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}", "account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{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.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request", "account.requested": "Awaiting approval. Click to cancel follow request",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancel", "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.placeholder": "Additional comments",
"report.submit": "Submit", "report.submit": "Submit",
"report.target": "Reporting {target}", "report.target": "Reporting {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...", "standalone.public_title": "A look inside...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Drag & drop to upload", "upload_area.title": "Drag & drop to upload",
"upload_button.label": "Add media", "upload_button.label": "Add media",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Undo", "upload_form.undo": "Undo",
"upload_progress.label": "Uploading...", "upload_progress.label": "Uploading...",
"video.close": "Close video", "video.close": "Close video",

View File

@@ -1,236 +1,243 @@
{ {
"account.block": "Bloki @{name}", "account.block": "Bloki @{name}",
"account.block_domain": "Kaŝi ĉion el {domain}", "account.block_domain": "Kaŝi ĉion de {domain}",
"account.disclaimer_full": "La ĉi-subaj informoj povas ne plene reflekti la profilon de la uzanto.", "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
"account.edit_profile": "Redakti la profilon", "account.edit_profile": "Redakti profilon",
"account.follow": "Sekvi", "account.follow": "Sekvi",
"account.followers": "Sekvantoj", "account.followers": "Sekvantoj",
"account.follows": "Sekvatoj", "account.follows": "Sekvatoj",
"account.follows_you": "Sekvas vin", "account.follows_you": "Sekvas vin",
"account.hide_reblogs": "Maski diskonigitaĵojn de @{name}", "account.hide_reblogs": "Kaŝi diskonigojn de @{name}",
"account.media": "Sonbildaĵoj", "account.media": "Aŭdovidaĵoj",
"account.mention": "Mencii @{name}", "account.mention": "Mencii @{name}",
"account.moved_to": "{name} movis al:", "account.moved_to": "{name} moviĝis al:",
"account.mute": "Silentigi @{name}", "account.mute": "Silentigi @{name}",
"account.mute_notifications": "Silentigi sciigojn el @{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.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.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": "Malbloki @{name}",
"account.unblock_domain": "Malkaŝi {domain}", "account.unblock_domain": "Malkaŝi {domain}",
"account.unfollow": "Ne plus sekvi", "account.unfollow": "Ne plu sekvi",
"account.unmute": "Malsilentigi @{name}", "account.unmute": "Malsilentigi @{name}",
"account.unmute_notifications": "Malsilentigi sciigojn de @{name}", "account.unmute_notifications": "Malsilentigi sciigojn de @{name}",
"account.view_full_profile": "Vidi plenan profilon", "account.view_full_profile": "Vidi plenan profilon",
"boost_modal.combo": "La proksiman fojon, premu {combo} por pasigi", "boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
"bundle_column_error.body": "Io malfunkciis ŝargante tiun ĉi komponanton.", "bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",
"bundle_column_error.retry": "Bonvolu reprovi", "bundle_column_error.retry": "Bonvolu reprovi",
"bundle_column_error.title": "Reta eraro", "bundle_column_error.title": "Reta eraro",
"bundle_modal_error.close": "Fermi", "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", "bundle_modal_error.retry": "Bonvolu reprovi",
"column.blocks": "Blokitaj uzantoj", "column.blocks": "Blokitaj uzantoj",
"column.community": "Loka tempolinio", "column.community": "Loka tempolinio",
"column.favourites": "Favoritoj", "column.favourites": "Stelumoj",
"column.follow_requests": "Abonpetoj", "column.follow_requests": "Petoj de sekvado",
"column.home": "Hejmo", "column.home": "Hejmo",
"column.lists": "Listoj", "column.lists": "Listoj",
"column.mutes": "Silentigitaj uzantoj", "column.mutes": "Silentigitaj uzantoj",
"column.notifications": "Sciigoj", "column.notifications": "Sciigoj",
"column.pins": "Alpinglitaj pepoj", "column.pins": "Alpinglitaj mesaĝoj",
"column.public": "Fratara tempolinio", "column.public": "Fratara tempolinio",
"column_back_button.label": "Reveni", "column_back_button.label": "Reveni",
"column_header.hide_settings": "Kaŝi agordojn", "column_header.hide_settings": "Kaŝi agordojn",
"column_header.moveLeft_settings": "Movi kolumnon maldekstren", "column_header.moveLeft_settings": "Movi kolumnon maldekstren",
"column_header.moveRight_settings": "Movi kolumnon dekstren", "column_header.moveRight_settings": "Movi kolumnon dekstren",
"column_header.pin": "Alpingli", "column_header.pin": "Alpingli",
"column_header.show_settings": "Malkaŝi agordojn", "column_header.show_settings": "Montri agordojn",
"column_header.unpin": "Depingli", "column_header.unpin": "Depingli",
"column_subheading.navigation": "Navigado", "column_subheading.navigation": "Navigado",
"column_subheading.settings": "Agordoj", "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.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 ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.", "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.lock_disclaimer.lock": "ŝlosita",
"compose_form.placeholder": "Pri kio vi pensas?", "compose_form.placeholder": "Pri kio vi pensas?",
"compose_form.publish": "Hup", "compose_form.publish": "Hup",
"compose_form.publish_loud": "{publish}!", "compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Marki ke la enhavo estas tikla", "compose_form.sensitive": "Marki aŭdovidaĵon tikla",
"compose_form.spoiler": "Kaŝi la tekston malantaŭ averto", "compose_form.spoiler": "Kaŝi tekston malantaŭ averto",
"compose_form.spoiler_placeholder": "Skribu tie vian averton", "compose_form.spoiler_placeholder": "Skribu vian averton ĉi tie",
"confirmation_modal.cancel": "Malfari", "confirmation_modal.cancel": "Nuligi",
"confirmations.block.confirm": "Bloki", "confirmations.block.confirm": "Bloki",
"confirmations.block.message": "Ĉu vi konfirmas la blokadon de {name}?", "confirmations.block.message": "Ĉu vi certas, ke vi volas bloki {name}?",
"confirmations.delete.confirm": "Malaperigi", "confirmations.delete.confirm": "Forigi",
"confirmations.delete.message": "Ĉu vi konfirmas la malaperigon de tiun pepon?", "confirmations.delete.message": "Ĉu vi certas, ke vi volas forigi ĉi tiun mesaĝon?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Forigi",
"confirmations.delete_list.message": "Ĉu vi certas forviŝi ĉi tiun liston por ĉiam?", "confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
"confirmations.domain_block.confirm": "Kaŝi la tutan reton", "confirmations.domain_block.confirm": "Kaŝi la tutan domajnon",
"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.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.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.confirm": "Ne plu sekvi",
"confirmations.unfollow.message": "Ĉu vi volas ĉesi sekvi {name}?", "confirmations.unfollow.message": "Ĉu vi certas, ke vi volas ĉesi sekvi {name}?",
"embed.instructions": "Enmetu tiun statkonigon ĉe vian retejon kopiante la ĉi-suban kodon.", "embed.instructions": "Enkorpigu ĉi tiun mesaĝon en vian retejon per kopio de la suba kodo.",
"embed.preview": "Ĝi aperos tiel:", "embed.preview": "Ĝi aperos tiel:",
"emoji_button.activity": "Aktivecoj", "emoji_button.activity": "Agadoj",
"emoji_button.custom": "Personaj", "emoji_button.custom": "Propraj",
"emoji_button.flags": "Flagoj", "emoji_button.flags": "Flagoj",
"emoji_button.food": "Manĝi kaj trinki", "emoji_button.food": "Manĝi kaj trinki",
"emoji_button.label": "Enmeti mieneton", "emoji_button.label": "Enmeti emoĝion",
"emoji_button.nature": "Naturo", "emoji_button.nature": "Naturo",
"emoji_button.not_found": "Neniuj mienetoj!! (╯°□°)╯︵ ┻━┻", "emoji_button.not_found": "Neniu emoĝio!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objektoj", "emoji_button.objects": "oj",
"emoji_button.people": "Homoj", "emoji_button.people": "Homoj",
"emoji_button.recent": "Ofte uzataj", "emoji_button.recent": "Ofte uzataj",
"emoji_button.search": "Serĉo…", "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.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.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": "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.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.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 dume ne havas sciigojn. Interagi kun aliajn uzantojn por komenci la konversacion.", "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 instancoj por plenigi la publikan tempolinion", "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": "Akcepti", "follow_request.authorize": "Rajtigi",
"follow_request.reject": "Rifuzi", "follow_request.reject": "Rifuzi",
"getting_started.appsshort": "Aplikaĵoj", "getting_started.appsshort": "Aplikaĵoj",
"getting_started.faq": "Oftaj demandoj", "getting_started.faq": "Oftaj demandoj",
"getting_started.heading": "Por komenci", "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", "getting_started.userguide": "Gvidilo de uzo",
"home.column_settings.advanced": "Precizaj agordoj", "home.column_settings.advanced": "Precizaj agordoj",
"home.column_settings.basic": "Bazaj 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_reblogs": "Montri diskonigojn",
"home.column_settings.show_replies": "Montri respondojn", "home.column_settings.show_replies": "Montri respondojn",
"home.settings": "Agordoj de la kolumno", "home.settings": "Kolumnaj agordoj",
"keyboard_shortcuts.back": "reeniri", "keyboard_shortcuts.back": "por reveni",
"keyboard_shortcuts.boost": "diskonigi", "keyboard_shortcuts.boost": "por diskonigi",
"keyboard_shortcuts.column": "fokusigi statuson en unu el la columnoj", "keyboard_shortcuts.column": "por fokusigi mesaĝon en unu el la kolumnoj",
"keyboard_shortcuts.compose": "por fokusigi la redaktujon", "keyboard_shortcuts.compose": "por fokusigi la tekstujon",
"keyboard_shortcuts.description": "Description", "keyboard_shortcuts.description": "Priskribo",
"keyboard_shortcuts.down": "subenmovi en la listo", "keyboard_shortcuts.down": "por iri suben en la listo",
"keyboard_shortcuts.enter": "to open status", "keyboard_shortcuts.enter": "por malfermi mesaĝon",
"keyboard_shortcuts.favourite": "ŝatitaren", "keyboard_shortcuts.favourite": "por stelumi",
"keyboard_shortcuts.heading": "Keyboard Shortcuts", "keyboard_shortcuts.heading": "Klavaraj mallongigoj",
"keyboard_shortcuts.hotkey": "Rapidklavo", "keyboard_shortcuts.hotkey": "Rapidklavo",
"keyboard_shortcuts.legend": "por montri ĉi tiun legendon", "keyboard_shortcuts.legend": "por montri ĉi tiun noton",
"keyboard_shortcuts.mention": "por sciigi ties aŭtoron", "keyboard_shortcuts.mention": "por mencii la aŭtoron",
"keyboard_shortcuts.reply": "por respondi", "keyboard_shortcuts.reply": "por respondi",
"keyboard_shortcuts.search": "por fokusigi la serĉadon", "keyboard_shortcuts.search": "por fokusigi la serĉilon",
"keyboard_shortcuts.toot": "por ekredakti tute novan pepon", "keyboard_shortcuts.toot": "por komenci tute novan mesaĝon",
"keyboard_shortcuts.unfocus": "por malfokusigi la redaktujon aŭ la serĉilon", "keyboard_shortcuts.unfocus": "por malfokusigi la tekstujon aŭ la serĉilon",
"keyboard_shortcuts.up": "por suprenmovi en la listo", "keyboard_shortcuts.up": "por iri supren en la listo",
"lightbox.close": "Fermi", "lightbox.close": "Fermi",
"lightbox.next": "Malantaŭa", "lightbox.next": "Sekva",
"lightbox.previous": "Antaŭa", "lightbox.previous": "Antaŭa",
"lists.account.add": "Aldoni al la listo", "lists.account.add": "Aldoni al la listo",
"lists.account.remove": "Forviŝi de la listo", "lists.account.remove": "Forigi de la listo",
"lists.delete": "Delete list", "lists.delete": "Forigi la liston",
"lists.edit": "Redakti la liston", "lists.edit": "Redakti la liston",
"lists.new.create": "Aldoni liston", "lists.new.create": "Aldoni liston",
"lists.new.title_placeholder": "Titulo de la nova listo", "lists.new.title_placeholder": "Titolo de la nova listo",
"lists.search": "Serĉi el la homoj kiujn vi sekvas", "lists.search": "Serĉi inter la homoj, kiujn vi sekvas",
"lists.subheading": "Viaj listoj", "lists.subheading": "Viaj listoj",
"loading_indicator.label": "Ŝarganta…", "loading_indicator.label": "Ŝargado…",
"media_gallery.toggle_visible": "Baskuli videblecon", "media_gallery.toggle_visible": "Baskuligi videblecon",
"missing_indicator.label": "Ne trovita", "missing_indicator.label": "Ne trovita",
"missing_indicator.sublabel": "Ĉi tiu rimedo ne troviĝis", "missing_indicator.sublabel": "Ĉi tiu rimedo ne estis trovita",
"mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el tiu ĉi uzanto?", "mute_modal.hide_notifications": "Ĉu kaŝi sciigojn el ĉi tiu uzanto?",
"navigation_bar.blocks": "Blokitaj uzantoj", "navigation_bar.blocks": "Blokitaj uzantoj",
"navigation_bar.community_timeline": "Loka tempolinio", "navigation_bar.community_timeline": "Loka tempolinio",
"navigation_bar.edit_profile": "Redakti la profilon", "navigation_bar.edit_profile": "Redakti profilon",
"navigation_bar.favourites": "Favoritaj", "navigation_bar.favourites": "Stelumoj",
"navigation_bar.follow_requests": "Abonpetoj", "navigation_bar.follow_requests": "Petoj de sekvado",
"navigation_bar.info": "Plia informo", "navigation_bar.info": "Pri ĉiu tiu nodo",
"navigation_bar.keyboard_shortcuts": "Klavmallongigo", "navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
"navigation_bar.lists": "Listoj", "navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Elsaluti", "navigation_bar.logout": "Elsaluti",
"navigation_bar.mutes": "Silentigitaj uzantoj", "navigation_bar.mutes": "Silentigitaj uzantoj",
"navigation_bar.pins": "Alpinglitaj pepoj", "navigation_bar.pins": "Alpinglitaj mesaĝoj",
"navigation_bar.preferences": "Preferoj", "navigation_bar.preferences": "Preferoj",
"navigation_bar.public_timeline": "Fratara tempolinio", "navigation_bar.public_timeline": "Fratara tempolinio",
"notification.favourite": "{name} favoris vian mesaĝon", "notification.favourite": "{name} stelumis vian mesaĝon",
"notification.follow": "{name} sekvis vin", "notification.follow": "{name} eksekvis vin",
"notification.mention": "{name} menciis vin", "notification.mention": "{name} menciis vin",
"notification.reblog": "{name} diskonigis vian mesaĝon", "notification.reblog": "{name} diskonigis vian mesaĝon",
"notifications.clear": "Forviŝi la sciigojn", "notifications.clear": "Forviŝi sciigojn",
"notifications.clear_confirmation": "Ĉu vi certe volas malaperigi ĉiujn viajn sciigojn?", "notifications.clear_confirmation": "Ĉu vi certas, ke vi volas porĉiame forviŝi ĉiujn viajn sciigojn?",
"notifications.column_settings.alert": "Retumilaj atentigoj", "notifications.column_settings.alert": "Retumilaj sciigoj",
"notifications.column_settings.favourite": "Favoritoj:", "notifications.column_settings.favourite": "Stelumoj:",
"notifications.column_settings.follow": "Novaj sekvantoj:", "notifications.column_settings.follow": "Novaj sekvantoj:",
"notifications.column_settings.mention": "Mencioj:", "notifications.column_settings.mention": "Mencioj:",
"notifications.column_settings.push": "Puŝsciigoj", "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.reblog": "Diskonigoj:",
"notifications.column_settings.show": "Montri en kolono", "notifications.column_settings.show": "Montri en kolumno",
"notifications.column_settings.sound": "Eligi sonon", "notifications.column_settings.sound": "Eligi sonon",
"onboarding.done": "Farita", "onboarding.done": "Farita",
"onboarding.next": "Malantaŭa", "onboarding.next": "Sekva",
"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_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 enhavas la mesaĝojn de ĉiuj uzantoj, kiuj vi sekvas.", "onboarding.page_four.home": "La hejma tempolinio montras mesaĝojn de ĉiuj uzantoj, kiujn vi sekvas.",
"onboarding.page_four.notifications": "La sciiga kolumno informas vin kiam iu interagas kun vi.", "onboarding.page_four.notifications": "La sciiga kolumno montras 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.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 tuta uzantnomo", "onboarding.page_one.full_handle": "Via kompleta uzantnomo",
"onboarding.page_one.handle_hint": "Jen kion vi dirintus al viaj amikoj por serĉi.", "onboarding.page_one.handle_hint": "Jen kion vi petus al viaj amikoj serĉi.",
"onboarding.page_one.welcome": "Bonvenon al Mastodono!", "onboarding.page_one.welcome": "Bonvenon en Mastodon!",
"onboarding.page_six.admin": "Via instancestro estas {admin}.", "onboarding.page_six.admin": "Via noda administranto estas {admin}.",
"onboarding.page_six.almost_done": "Estas preskaŭ finita…", "onboarding.page_six.almost_done": "Preskaŭ finita…",
"onboarding.page_six.appetoot": "Bonan apepiton!", "onboarding.page_six.appetoot": "Saĝan mesaĝadon!",
"onboarding.page_six.apps_available": "{apps} estas elŝuteblaj por iOS, Androido kaj alioj.", "onboarding.page_six.apps_available": "{apps} estas disponeblaj por iOS, Android kaj aliaj platformoj.",
"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.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": "komunreguloj", "onboarding.page_six.guidelines": "komunumaj gvidlinioj",
"onboarding.page_six.read_guidelines": "Ni petas vin: ne forgesu legi la {guidelines}n de {domain}!", "onboarding.page_six.read_guidelines": "Bonvolu atenti pri la {guidelines} de {domain}!",
"onboarding.page_six.various_app": "telefon-aplikaĵoj", "onboarding.page_six.various_app": "telefonaj 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.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ĉ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_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 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.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": "Pasigi", "onboarding.skip": "Preterpasi",
"privacy.change": "Alĝustigi la privateco de la mesaĝo", "privacy.change": "Agordi mesaĝan privatecon",
"privacy.direct.long": "Vidigi nur al la menciitaj personoj", "privacy.direct.long": "Afiŝi nur al menciitaj uzantoj",
"privacy.direct.short": "Rekta", "privacy.direct.short": "Rekta",
"privacy.private.long": "Vidigi nur al viaj sekvantoj", "privacy.private.long": "Afiŝi nur al sekvantoj",
"privacy.private.short": "Nursekvanta", "privacy.private.short": "Nur por sekvantoj",
"privacy.public.long": "Vidigi en publikaj tempolinioj", "privacy.public.long": "Afiŝi en publikaj tempolinioj",
"privacy.public.short": "Publika", "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", "privacy.unlisted.short": "Nelistigita",
"regeneration_indicator.label": "Elŝultanta…", "regeneration_indicator.label": "Ŝargado…",
"regeneration_indicator.sublabel": "Via ĉefpaĝo estas preparanta!", "regeneration_indicator.sublabel": "Via hejma fluo pretiĝas!",
"relative_time.days": "{number}t", "relative_time.days": "{number}t",
"relative_time.hours": "{number}h", "relative_time.hours": "{number}h",
"relative_time.just_now": "nun", "relative_time.just_now": "nun",
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "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.placeholder": "Pliaj komentoj",
"report.submit": "Sendi", "report.submit": "Sendi",
"report.target": "Signalaĵo", "report.target": "Signali {target}",
"search.placeholder": "Serĉi", "search.placeholder": "Serĉi",
"search_popout.search_format": "Detala serĉo", "search_popout.search_format": "Detala serĉo",
"search_popout.tips.hashtag": "kradvorto", "search_popout.tips.hashtag": "kradvorto",
"search_popout.tips.status": "statkonigo", "search_popout.tips.status": "mesaĝoj",
"search_popout.tips.text": "Simpla teksto eligas la kongruajn afiŝnomojn, uznomojn kaj kradvortojn", "search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
"search_popout.tips.user": "uzanto", "search_popout.tips.user": "uzanto",
"search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}", "search_results.accounts": "People",
"standalone.public_title": "Rigardeti…", "search_results.hashtags": "Hashtags",
"status.block": "Block @{name}", "search_results.statuses": "Toots",
"status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi", "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.delete": "Forigi",
"status.embed": "Enmeti", "status.embed": "Enkorpigi",
"status.favourite": "Favori", "status.favourite": "Stelumi",
"status.load_more": "Ŝargi plie", "status.load_more": "Ŝargi pli",
"status.media_hidden": "Sonbildaĵo kaŝita", "status.media_hidden": "Aŭdovidaĵo kaŝita",
"status.mention": "Mencii @{name}", "status.mention": "Mencii @{name}",
"status.more": "Pli", "status.more": "Pli",
"status.mute": "Silentigi @{name}", "status.mute": "Silentigi @{name}",
"status.mute_conversation": "Silentigi konversacion", "status.mute_conversation": "Silentigi konversacion",
"status.open": "Disfaldi statkonigon", "status.open": "Grandigi ĉi tiun mesaĝon",
"status.pin": "Pingli al la profilo", "status.pin": "Alpingli en la profilo",
"status.reblog": "Diskonigi", "status.reblog": "Diskonigi",
"status.reblogged_by": "{name} diskonigis", "status.reblogged_by": "{name} diskonigis",
"status.reply": "Respondi", "status.reply": "Respondi",
@@ -239,28 +246,29 @@
"status.sensitive_toggle": "Alklaki por vidi", "status.sensitive_toggle": "Alklaki por vidi",
"status.sensitive_warning": "Tikla enhavo", "status.sensitive_warning": "Tikla enhavo",
"status.share": "Diskonigi", "status.share": "Diskonigi",
"status.show_less": "Refaldi", "status.show_less": "Malgrandigi",
"status.show_more": "Disfaldi", "status.show_more": "Grandigi",
"status.unmute_conversation": "Malsilentigi konversacion", "status.unmute_conversation": "Malsilentigi konversacion",
"status.unpin": "Depingli de profilo", "status.unpin": "Depingli de profilo",
"tabs_bar.compose": "Ekskribi", "tabs_bar.compose": "Ekskribi",
"tabs_bar.federated_timeline": "Federacia tempolinio", "tabs_bar.federated_timeline": "Fratara tempolinio",
"tabs_bar.home": "Hejmo", "tabs_bar.home": "Hejmo",
"tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.local_timeline": "Loka tempolinio",
"tabs_bar.notifications": "Sciigoj", "tabs_bar.notifications": "Sciigoj",
"ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.", "ui.beforeunload": "Via malneto perdiĝos se vi eliras de Mastodon.",
"upload_area.title": "Algliti por alŝuti", "upload_area.title": "Altreni kaj lasi por alŝuti",
"upload_button.label": "Aldoni sonbildaĵon", "upload_button.label": "Aldoni aŭdovidaĵon",
"upload_form.description": "Priskribi por la misvidantaj", "upload_form.description": "Priskribi por misvidantaj homoj",
"upload_form.focus": "Crop",
"upload_form.undo": "Malfari", "upload_form.undo": "Malfari",
"upload_progress.label": "Alŝutanta…", "upload_progress.label": "Alŝutado…",
"video.close": "Fermi videon", "video.close": "Fermi videon",
"video.exit_fullscreen": "Eliri el plenekrano", "video.exit_fullscreen": "Eksigi plenekrana",
"video.expand": "Vastigi videon", "video.expand": "Grandigi videon",
"video.fullscreen": "Igi plenekrane", "video.fullscreen": "Igi plenekrana",
"video.hide": "Kaŝi videon", "video.hide": "Kaŝi videon",
"video.mute": "Silentigi", "video.mute": "Silentigi",
"video.pause": "Paŭzi", "video.pause": "Paŭzi",
"video.play": "Legi", "video.play": "Ekigi",
"video.unmute": "Malsilentigi" "video.unmute": "Malsilentigi"
} }

View File

@@ -14,6 +14,7 @@
"account.mute": "Silenciar a @{name}", "account.mute": "Silenciar a @{name}",
"account.mute_notifications": "Silenciar notificaciones de @{name}", "account.mute_notifications": "Silenciar notificaciones de @{name}",
"account.posts": "Publicaciones", "account.posts": "Publicaciones",
"account.posts_with_replies": "Toots with replies",
"account.report": "Reportar a @{name}", "account.report": "Reportar a @{name}",
"account.requested": "Esperando aprobación", "account.requested": "Esperando aprobación",
"account.share": "Compartir el perfil de @{name}", "account.share": "Compartir el perfil de @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar", "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.placeholder": "Comentarios adicionales",
"report.submit": "Publicar", "report.submit": "Publicar",
"report.target": "Reportando", "report.target": "Reportando",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag", "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
"search_popout.tips.user": "usuario", "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}}", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Un pequeño vistazo...", "standalone.public_title": "Un pequeño vistazo...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arrastra y suelta para subir", "upload_area.title": "Arrastra y suelta para subir",
"upload_button.label": "Subir multimedia", "upload_button.label": "Subir multimedia",
"upload_form.description": "Describir para los usuarios con dificultad visual", "upload_form.description": "Describir para los usuarios con dificultad visual",
"upload_form.focus": "Crop",
"upload_form.undo": "Deshacer", "upload_form.undo": "Deshacer",
"upload_progress.label": "Subiendo…", "upload_progress.label": "Subiendo…",
"video.close": "Cerrar video", "video.close": "Cerrar video",

View File

@@ -14,6 +14,7 @@
"account.mute": "بی‌صدا کردن @{name}", "account.mute": "بی‌صدا کردن @{name}",
"account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}", "account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
"account.posts": "نوشته‌ها", "account.posts": "نوشته‌ها",
"account.posts_with_replies": "Toots with replies",
"account.report": "گزارش @{name}", "account.report": "گزارش @{name}",
"account.requested": "در انتظار پذیرش", "account.requested": "در انتظار پذیرش",
"account.share": "هم‌رسانی نمایهٔ @{name}", "account.share": "هم‌رسانی نمایهٔ @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "لغو", "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.placeholder": "توضیح اضافه",
"report.submit": "بفرست", "report.submit": "بفرست",
"report.target": "گزارش‌دادن", "report.target": "گزارش‌دادن",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "نوشته", "search_popout.tips.status": "نوشته",
"search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها", "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
"search_popout.tips.user": "کاربر", "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 {نتیجه}}", "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
"standalone.public_title": "نگاهی به کاربران این سرور...", "standalone.public_title": "نگاهی به کاربران این سرور...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "برای بارگذاری به این‌جا بکشید", "upload_area.title": "برای بارگذاری به این‌جا بکشید",
"upload_button.label": "افزودن تصویر", "upload_button.label": "افزودن تصویر",
"upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان", "upload_form.description": "نوشتهٔ توضیحی برای کم‌بینایان و نابینایان",
"upload_form.focus": "Crop",
"upload_form.undo": "واگردانی", "upload_form.undo": "واگردانی",
"upload_progress.label": "بارگذاری...", "upload_progress.label": "بارگذاری...",
"video.close": "بستن ویدیو", "video.close": "بستن ویدیو",

View File

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

View File

@@ -14,6 +14,7 @@
"account.mute": "Masquer @{name}", "account.mute": "Masquer @{name}",
"account.mute_notifications": "Ignorer les notifications de @{name}", "account.mute_notifications": "Ignorer les notifications de @{name}",
"account.posts": "Statuts", "account.posts": "Statuts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Signaler", "account.report": "Signaler",
"account.requested": "Invitation envoyée", "account.requested": "Invitation envoyée",
"account.share": "Partager le profil de @{name}", "account.share": "Partager le profil de @{name}",
@@ -163,7 +164,7 @@
"notifications.column_settings.alert": "Notifications locales", "notifications.column_settings.alert": "Notifications locales",
"notifications.column_settings.favourite": "Favoris :", "notifications.column_settings.favourite": "Favoris :",
"notifications.column_settings.follow": "Nouveaux⋅elles abonné⋅e·s :", "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": "Notifications push",
"notifications.column_settings.push_meta": "Cet appareil", "notifications.column_settings.push_meta": "Cet appareil",
"notifications.column_settings.reblog": "Partages:", "notifications.column_settings.reblog": "Partages:",
@@ -200,13 +201,16 @@
"privacy.unlisted.long": "Ne pas afficher dans les fils publics", "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
"privacy.unlisted.short": "Non-listé", "privacy.unlisted.short": "Non-listé",
"regeneration_indicator.label": "Chargement…", "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.days": "{number} j",
"relative_time.hours": "{number} h", "relative_time.hours": "{number} h",
"relative_time.just_now": "à linstant", "relative_time.just_now": "à linstant",
"relative_time.minutes": "{number} min", "relative_time.minutes": "{number} min",
"relative_time.seconds": "{number} s", "relative_time.seconds": "{number} s",
"reply_indicator.cancel": "Annuler", "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.placeholder": "Commentaires additionnels",
"report.submit": "Envoyer", "report.submit": "Envoyer",
"report.target": "Signalement", "report.target": "Signalement",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "statuts", "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.text": "Un texte simple renvoie les noms affichés, les noms dutilisateur⋅ice et les hashtags correspondants",
"search_popout.tips.user": "utilisateur⋅ice", "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}}", "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
"standalone.public_title": "Jeter un coup dœil…", "standalone.public_title": "Jeter un coup dœil…",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Glissez et déposez pour envoyer", "upload_area.title": "Glissez et déposez pour envoyer",
"upload_button.label": "Joindre un média", "upload_button.label": "Joindre un média",
"upload_form.description": "Décrire pour les malvoyants", "upload_form.description": "Décrire pour les malvoyants",
"upload_form.focus": "Crop",
"upload_form.undo": "Annuler", "upload_form.undo": "Annuler",
"upload_progress.label": "Envoi en cours…", "upload_progress.label": "Envoi en cours…",
"video.close": "Fermer la vidéo", "video.close": "Fermer la vidéo",

View File

@@ -13,7 +13,8 @@
"account.moved_to": "{name} marchou a:", "account.moved_to": "{name} marchou a:",
"account.mute": "Acalar @{name}", "account.mute": "Acalar @{name}",
"account.mute_notifications": "Acalar as notificacións de @{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.report": "Informar sobre @{name}",
"account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento", "account.requested": "Agardando aceptación. Pulse para cancelar a solicitude de seguimento",
"account.share": "Compartir o perfil de @{name}", "account.share": "Compartir o perfil de @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar", "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.placeholder": "Comentarios adicionais",
"report.submit": "Enviar", "report.submit": "Enviar",
"report.target": "Informar {target}", "report.target": "Informar {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "estado", "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.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
"search_popout.tips.user": "usuaria", "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}}", "search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
"standalone.public_title": "Ollada dentro...", "standalone.public_title": "Ollada dentro...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arrastre e solte para subir", "upload_area.title": "Arrastre e solte para subir",
"upload_button.label": "Engadir medios", "upload_button.label": "Engadir medios",
"upload_form.description": "Describa para deficientes visuais", "upload_form.description": "Describa para deficientes visuais",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfacer", "upload_form.undo": "Desfacer",
"upload_progress.label": "Subindo...", "upload_progress.label": "Subindo...",
"video.close": "Pechar video", "video.close": "Pechar video",

View File

@@ -14,6 +14,7 @@
"account.mute": "להשתיק את @{name}", "account.mute": "להשתיק את @{name}",
"account.mute_notifications": "להסתיר התראות מאת @{name}", "account.mute_notifications": "להסתיר התראות מאת @{name}",
"account.posts": "הודעות", "account.posts": "הודעות",
"account.posts_with_replies": "Toots with replies",
"account.report": "לדווח על @{name}", "account.report": "לדווח על @{name}",
"account.requested": "בהמתנה לאישור", "account.requested": "בהמתנה לאישור",
"account.share": "לשתף את אודות @{name}", "account.share": "לשתף את אודות @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "ביטול", "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.placeholder": "הערות נוספות",
"report.submit": "שליחה", "report.submit": "שליחה",
"report.target": "דיווח", "report.target": "דיווח",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים", "search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
"search_popout.tips.user": "משתמש(ת)", "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 {תוצאות}}", "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
"standalone.public_title": "הצצה פנימה...", "standalone.public_title": "הצצה פנימה...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "ניתן להעלות על ידי Drag & drop", "upload_area.title": "ניתן להעלות על ידי Drag & drop",
"upload_button.label": "הוספת מדיה", "upload_button.label": "הוספת מדיה",
"upload_form.description": "תיאור לכבדי ראיה", "upload_form.description": "תיאור לכבדי ראיה",
"upload_form.focus": "Crop",
"upload_form.undo": "ביטול", "upload_form.undo": "ביטול",
"upload_progress.label": "עולה...", "upload_progress.label": "עולה...",
"video.close": "סגירת וידאו", "video.close": "סגירת וידאו",

View File

@@ -14,6 +14,7 @@
"account.mute": "Utišaj @{name}", "account.mute": "Utišaj @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Postovi", "account.posts": "Postovi",
"account.posts_with_replies": "Toots with replies",
"account.report": "Prijavi @{name}", "account.report": "Prijavi @{name}",
"account.requested": "Čeka pristanak", "account.requested": "Čeka pristanak",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Otkaži", "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.placeholder": "Dodatni komentari",
"report.submit": "Pošalji", "report.submit": "Pošalji",
"report.target": "Prijavljivanje", "report.target": "Prijavljivanje",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...", "standalone.public_title": "A look inside...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Povuci i spusti kako bi uploadao", "upload_area.title": "Povuci i spusti kako bi uploadao",
"upload_button.label": "Dodaj media", "upload_button.label": "Dodaj media",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Poništi", "upload_form.undo": "Poništi",
"upload_progress.label": "Uploadam...", "upload_progress.label": "Uploadam...",
"video.close": "Close video", "video.close": "Close video",

View File

@@ -7,13 +7,14 @@
"account.followers": "Követők", "account.followers": "Követők",
"account.follows": "Követve", "account.follows": "Követve",
"account.follows_you": "Követnek téged", "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.media": "Média",
"account.mention": "@{name} említése", "account.mention": "@{name} említése",
"account.moved_to": "{name} átköltözött:", "account.moved_to": "{name} átköltözött:",
"account.mute": "@{name} némítása", "account.mute": "@{name} némítása",
"account.mute_notifications": "@{name} értesítések némítása", "account.mute_notifications": "@{name} értesítések némítása",
"account.posts": "Státuszok", "account.posts": "Státuszok",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} jelentése", "account.report": "@{name} jelentése",
"account.requested": "Engedélyre vár. Kattintson a követési kérés visszavonására", "account.requested": "Engedélyre vár. Kattintson a követési kérés visszavonására",
"account.share": "@{name} profiljának megosztása", "account.share": "@{name} profiljának megosztása",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Mégsem", "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.placeholder": "További kommentek",
"report.submit": "Submit", "report.submit": "Submit",
"report.target": "Reporting", "report.target": "Reporting",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "felhasználó", "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}}", "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "Betekintés...", "standalone.public_title": "Betekintés...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Húzza ide a feltöltéshez", "upload_area.title": "Húzza ide a feltöltéshez",
"upload_button.label": "Média hozzáadása", "upload_button.label": "Média hozzáadása",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Mégsem", "upload_form.undo": "Mégsem",
"upload_progress.label": "Uploading...", "upload_progress.label": "Uploading...",
"video.close": "Close video", "video.close": "Close video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Լռեցնել @{name}֊ին", "account.mute": "Լռեցնել @{name}֊ին",
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից", "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
"account.posts": "Գրառումներ", "account.posts": "Գրառումներ",
"account.posts_with_replies": "Toots with replies",
"account.report": "Բողոքել @{name}֊ից", "account.report": "Բողոքել @{name}֊ից",
"account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։", "account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
"account.share": "Կիսվել @{name}֊ի էջով", "account.share": "Կիսվել @{name}֊ի էջով",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}ր", "relative_time.minutes": "{number}ր",
"relative_time.seconds": "{number}վ", "relative_time.seconds": "{number}վ",
"reply_indicator.cancel": "Չեղարկել", "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.placeholder": "Լրացուցիչ մեկնաբանություններ",
"report.submit": "Ուղարկել", "report.submit": "Ուղարկել",
"report.target": "Բողոքել {target}֊ի մասին", "report.target": "Բողոքել {target}֊ի մասին",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "թութ", "search_popout.tips.status": "թութ",
"search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ", "search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
"search_popout.tips.user": "օգտատեր", "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}}", "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "Այս պահին…", "standalone.public_title": "Այս պահին…",
"status.block": "Արգելափակել @{name}֊ին", "status.block": "Արգելափակել @{name}֊ին",
@@ -252,6 +259,7 @@
"upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար", "upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
"upload_button.label": "Ավելացնել մեդիա", "upload_button.label": "Ավելացնել մեդիա",
"upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար", "upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
"upload_form.focus": "Crop",
"upload_form.undo": "Հետարկել", "upload_form.undo": "Հետարկել",
"upload_progress.label": "Վերբեռնվում է…", "upload_progress.label": "Վերբեռնվում է…",
"video.close": "Փակել տեսագրությունը", "video.close": "Փակել տեսագրությունը",

View File

@@ -14,6 +14,7 @@
"account.mute": "Bisukan @{name}", "account.mute": "Bisukan @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Postingan", "account.posts": "Postingan",
"account.posts_with_replies": "Toots with replies",
"account.report": "Laporkan @{name}", "account.report": "Laporkan @{name}",
"account.requested": "Menunggu persetujuan", "account.requested": "Menunggu persetujuan",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Batal", "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.placeholder": "Komentar tambahan",
"report.submit": "Kirim", "report.submit": "Kirim",
"report.target": "Melaporkan", "report.target": "Melaporkan",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
"standalone.public_title": "A look inside...", "standalone.public_title": "A look inside...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Seret & lepaskan untuk mengunggah", "upload_area.title": "Seret & lepaskan untuk mengunggah",
"upload_button.label": "Tambahkan media", "upload_button.label": "Tambahkan media",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Undo", "upload_form.undo": "Undo",
"upload_progress.label": "Mengunggah...", "upload_progress.label": "Mengunggah...",
"video.close": "Close video", "video.close": "Close video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Celar @{name}", "account.mute": "Celar @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Mesaji", "account.posts": "Mesaji",
"account.posts_with_replies": "Toots with replies",
"account.report": "Denuncar @{name}", "account.report": "Denuncar @{name}",
"account.requested": "Vartante aprobo", "account.requested": "Vartante aprobo",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Nihiligar", "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.placeholder": "Plusa komenti",
"report.submit": "Sendar", "report.submit": "Sendar",
"report.target": "Denuncante", "report.target": "Denuncante",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
"standalone.public_title": "A look inside...", "standalone.public_title": "A look inside...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Tranar faligar por kargar", "upload_area.title": "Tranar faligar por kargar",
"upload_button.label": "Adjuntar kontenajo", "upload_button.label": "Adjuntar kontenajo",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfacar", "upload_form.undo": "Desfacar",
"upload_progress.label": "Kargante...", "upload_progress.label": "Kargante...",
"video.close": "Close video", "video.close": "Close video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Silenzia @{name}", "account.mute": "Silenzia @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Posts", "account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Segnala @{name}", "account.report": "Segnala @{name}",
"account.requested": "In attesa di approvazione", "account.requested": "In attesa di approvazione",
"account.share": "Share @{name}'s profile", "account.share": "Share @{name}'s profile",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Annulla", "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.placeholder": "Commenti aggiuntivi",
"report.submit": "Invia", "report.submit": "Invia",
"report.target": "Invio la segnalazione", "report.target": "Invio la segnalazione",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user", "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}}", "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
"standalone.public_title": "A look inside...", "standalone.public_title": "A look inside...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Trascina per caricare", "upload_area.title": "Trascina per caricare",
"upload_button.label": "Aggiungi file multimediale", "upload_button.label": "Aggiungi file multimediale",
"upload_form.description": "Describe for the visually impaired", "upload_form.description": "Describe for the visually impaired",
"upload_form.focus": "Crop",
"upload_form.undo": "Annulla", "upload_form.undo": "Annulla",
"upload_progress.label": "Sto caricando...", "upload_progress.label": "Sto caricando...",
"video.close": "Close video", "video.close": "Close video",

View File

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

View File

@@ -14,6 +14,7 @@
"account.mute": "@{name} 뮤트", "account.mute": "@{name} 뮤트",
"account.mute_notifications": "@{name}의 알림을 뮤트", "account.mute_notifications": "@{name}의 알림을 뮤트",
"account.posts": "게시물", "account.posts": "게시물",
"account.posts_with_replies": "Toots with replies",
"account.report": "@{name} 신고", "account.report": "@{name} 신고",
"account.requested": "승인 대기 중. 클릭해서 취소하기", "account.requested": "승인 대기 중. 클릭해서 취소하기",
"account.share": "@{name}의 프로파일 공유", "account.share": "@{name}의 프로파일 공유",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}분 전", "relative_time.minutes": "{number}분 전",
"relative_time.seconds": "{number}초 전", "relative_time.seconds": "{number}초 전",
"reply_indicator.cancel": "취소", "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.placeholder": "코멘트",
"report.submit": "신고하기", "report.submit": "신고하기",
"report.target": "문제가 된 사용자", "report.target": "문제가 된 사용자",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "툿", "search_popout.tips.status": "툿",
"search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다", "search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
"search_popout.tips.user": "유저", "search_popout.tips.user": "유저",
"search_results.accounts": "People",
"search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots",
"search_results.total": "{count, number}건의 결과", "search_results.total": "{count, number}건의 결과",
"standalone.public_title": "지금 이런 이야기를 하고 있습니다…", "standalone.public_title": "지금 이런 이야기를 하고 있습니다…",
"status.block": "@{name} 차단", "status.block": "@{name} 차단",
@@ -252,6 +259,7 @@
"upload_area.title": "드래그 & 드롭으로 업로드", "upload_area.title": "드래그 & 드롭으로 업로드",
"upload_button.label": "미디어 추가", "upload_button.label": "미디어 추가",
"upload_form.description": "시각장애인을 위한 설명", "upload_form.description": "시각장애인을 위한 설명",
"upload_form.focus": "Crop",
"upload_form.undo": "재시도", "upload_form.undo": "재시도",
"upload_progress.label": "업로드 중...", "upload_progress.label": "업로드 중...",
"video.close": "동영상 닫기", "video.close": "동영상 닫기",

View File

@@ -14,6 +14,7 @@
"account.mute": "Negeer @{name}", "account.mute": "Negeer @{name}",
"account.mute_notifications": "Negeer meldingen van @{name}", "account.mute_notifications": "Negeer meldingen van @{name}",
"account.posts": "Toots", "account.posts": "Toots",
"account.posts_with_replies": "Toots with replies",
"account.report": "Rapporteer @{name}", "account.report": "Rapporteer @{name}",
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren", "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
"account.share": "Profiel van @{name} delen", "account.share": "Profiel van @{name} delen",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Annuleren", "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.placeholder": "Extra opmerkingen",
"report.submit": "Verzenden", "report.submit": "Verzenden",
"report.target": "Rapporteer {target}", "report.target": "Rapporteer {target}",
@@ -216,9 +220,12 @@
"search_popout.tips.status": "toot", "search_popout.tips.status": "toot",
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags", "search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
"search_popout.tips.user": "gebruiker", "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}}", "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
"standalone.public_title": "Een kijkje binnenin...", "standalone.public_title": "Een kijkje binnenin...",
"status.block": "Block @{name}", "status.block": "Blokkeer @{name}",
"status.cannot_reblog": "Deze toot kan niet geboost worden", "status.cannot_reblog": "Deze toot kan niet geboost worden",
"status.delete": "Verwijderen", "status.delete": "Verwijderen",
"status.embed": "Embed", "status.embed": "Embed",
@@ -252,6 +259,7 @@
"upload_area.title": "Hierin slepen om te uploaden", "upload_area.title": "Hierin slepen om te uploaden",
"upload_button.label": "Media toevoegen", "upload_button.label": "Media toevoegen",
"upload_form.description": "Omschrijf dit voor mensen met een visuele beperking", "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
"upload_form.focus": "Crop",
"upload_form.undo": "Ongedaan maken", "upload_form.undo": "Ongedaan maken",
"upload_progress.label": "Uploaden...", "upload_progress.label": "Uploaden...",
"video.close": "Video sluiten", "video.close": "Video sluiten",

View File

@@ -14,6 +14,7 @@
"account.mute": "Demp @{name}", "account.mute": "Demp @{name}",
"account.mute_notifications": "Ignorer varsler fra @{name}", "account.mute_notifications": "Ignorer varsler fra @{name}",
"account.posts": "Innlegg", "account.posts": "Innlegg",
"account.posts_with_replies": "Toots with replies",
"account.report": "Rapportér @{name}", "account.report": "Rapportér @{name}",
"account.requested": "Venter på godkjennelse", "account.requested": "Venter på godkjennelse",
"account.share": "Del @{name}s profil", "account.share": "Del @{name}s profil",
@@ -207,6 +208,9 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Avbryt", "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.placeholder": "Tilleggskommentarer",
"report.submit": "Send inn", "report.submit": "Send inn",
"report.target": "Rapporterer", "report.target": "Rapporterer",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger", "search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
"search_popout.tips.user": "bruker", "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}}", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
"standalone.public_title": "En titt inni...", "standalone.public_title": "En titt inni...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Dra og slipp for å laste opp", "upload_area.title": "Dra og slipp for å laste opp",
"upload_button.label": "Legg til media", "upload_button.label": "Legg til media",
"upload_form.description": "Beskriv for synshemmede", "upload_form.description": "Beskriv for synshemmede",
"upload_form.focus": "Crop",
"upload_form.undo": "Angre", "upload_form.undo": "Angre",
"upload_progress.label": "Laster opp...", "upload_progress.label": "Laster opp...",
"video.close": "Lukk video", "video.close": "Lukk video",

View File

@@ -14,6 +14,7 @@
"account.mute": "Rescondre @{name}", "account.mute": "Rescondre @{name}",
"account.mute_notifications": "Rescondre las notificacions de @{name}", "account.mute_notifications": "Rescondre las notificacions de @{name}",
"account.posts": "Estatuts", "account.posts": "Estatuts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Senhalar @{name}", "account.report": "Senhalar @{name}",
"account.requested": "Invitacion mandada. Clicatz per anullar", "account.requested": "Invitacion mandada. Clicatz per anullar",
"account.share": "Partejar lo perfil a @{name}", "account.share": "Partejar lo perfil a @{name}",
@@ -207,6 +208,9 @@
"relative_time.minutes": "fa {number}min", "relative_time.minutes": "fa {number}min",
"relative_time.seconds": "fa {number}s", "relative_time.seconds": "fa {number}s",
"reply_indicator.cancel": "Anullar", "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.placeholder": "Comentaris addicionals",
"report.submit": "Mandar", "report.submit": "Mandar",
"report.target": "Senhalar {target}", "report.target": "Senhalar {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "estatut", "search_popout.tips.status": "estatut",
"search_popout.tips.text": "Lo tèxt brut tòrna escais, noms dutilizaire e etiquetas correspondents", "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms dutilizaire e etiquetas correspondents",
"search_popout.tips.user": "utilizaire", "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}}", "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
"standalone.public_title": "Una ulhada dedins…", "standalone.public_title": "Una ulhada dedins…",
"status.block": "Blocar @{name}", "status.block": "Blocar @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Lisatz e depausatz per mandar", "upload_area.title": "Lisatz e depausatz per mandar",
"upload_button.label": "Ajustar un mèdia", "upload_button.label": "Ajustar un mèdia",
"upload_form.description": "Descripcion pels mal vesents", "upload_form.description": "Descripcion pels mal vesents",
"upload_form.focus": "Crop",
"upload_form.undo": "Anullar", "upload_form.undo": "Anullar",
"upload_progress.label": "Mandadís…", "upload_progress.label": "Mandadís…",
"video.close": "Tampar la vidèo", "video.close": "Tampar la vidèo",

View File

@@ -8,12 +8,13 @@
"account.follows": "Śledzeni", "account.follows": "Śledzeni",
"account.follows_you": "Śledzi Cię", "account.follows_you": "Śledzi Cię",
"account.hide_reblogs": "Ukryj podbicia od @{name}", "account.hide_reblogs": "Ukryj podbicia od @{name}",
"account.media": "Media", "account.media": "Zawartość multimedialna",
"account.mention": "Wspomnij o @{name}", "account.mention": "Wspomnij o @{name}",
"account.moved_to": "{name} przeniósł się do:", "account.moved_to": "{name} przeniósł się do:",
"account.mute": "Wycisz @{name}", "account.mute": "Wycisz @{name}",
"account.mute_notifications": "Wycisz powiadomienia o @{name}", "account.mute_notifications": "Wycisz powiadomienia o @{name}",
"account.posts": "Wpisy", "account.posts": "Wpisy",
"account.posts_with_replies": "Wpisy z odpowiedziami",
"account.report": "Zgłoś @{name}", "account.report": "Zgłoś @{name}",
"account.requested": "Oczekująca prośba, kliknij aby anulować", "account.requested": "Oczekująca prośba, kliknij aby anulować",
"account.share": "Udostępnij profil @{name}", "account.share": "Udostępnij profil @{name}",
@@ -94,7 +95,7 @@
"empty_column.home.public_timeline": "publiczna oś czasu", "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.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.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.authorize": "Autoryzuj",
"follow_request.reject": "Odrzuć", "follow_request.reject": "Odrzuć",
"getting_started.appsshort": "Aplikacje", "getting_started.appsshort": "Aplikacje",
@@ -129,17 +130,17 @@
"lightbox.next": "Następne", "lightbox.next": "Następne",
"lightbox.previous": "Poprzednie", "lightbox.previous": "Poprzednie",
"lists.account.add": "Dodaj do listy", "lists.account.add": "Dodaj do listy",
"lists.account.remove": "Remove from list", "lists.account.remove": "Usunąć z listy",
"lists.delete": "Usuń listę", "lists.delete": "Usuń listę",
"lists.edit": "Edytuj listę", "lists.edit": "Edytuj listę",
"lists.new.create": "Utwórz 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.search": "Szukaj wśród osób które śledzisz",
"lists.subheading": "Twoje listy", "lists.subheading": "Twoje listy",
"loading_indicator.label": "Ładowanie…", "loading_indicator.label": "Ładowanie…",
"media_gallery.toggle_visible": "Przełącz widoczność", "media_gallery.toggle_visible": "Przełącz widoczność",
"missing_indicator.label": "Nie znaleziono", "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?", "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?",
"navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.blocks": "Zablokowani użytkownicy",
"navigation_bar.community_timeline": "Lokalna oś czasu", "navigation_bar.community_timeline": "Lokalna oś czasu",
@@ -199,14 +200,17 @@
"privacy.public.short": "Publiczny", "privacy.public.short": "Publiczny",
"privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu", "privacy.unlisted.long": "Niewidoczny na publicznych osiach czasu",
"privacy.unlisted.short": "Niewidoczny", "privacy.unlisted.short": "Niewidoczny",
"regeneration_indicator.label": "Loading…", "regeneration_indicator.label": "Ładuję…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!", "regeneration_indicator.sublabel": "Twoja oś czasu jest przygotowywana!",
"relative_time.days": "{number} dni", "relative_time.days": "{number} dni",
"relative_time.hours": "{number} godz.", "relative_time.hours": "{number} godz.",
"relative_time.just_now": "teraz", "relative_time.just_now": "teraz",
"relative_time.minutes": "{number} min.", "relative_time.minutes": "{number} min.",
"relative_time.seconds": "{number} s.", "relative_time.seconds": "{number} s.",
"reply_indicator.cancel": "Anuluj", "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.placeholder": "Dodatkowe komentarze",
"report.submit": "Wyślij", "report.submit": "Wyślij",
"report.target": "Zgłaszanie {target}", "report.target": "Zgłaszanie {target}",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "wpis", "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.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
"search_popout.tips.user": "użytkownik", "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}}", "search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
"standalone.public_title": "Spojrzenie w głąb…", "standalone.public_title": "Spojrzenie w głąb…",
"status.block": "Zablokuj @{name}", "status.block": "Zablokuj @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Przeciągnij i upuść aby wysłać", "upload_area.title": "Przeciągnij i upuść aby wysłać",
"upload_button.label": "Dodaj zawartość multimedialną", "upload_button.label": "Dodaj zawartość multimedialną",
"upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących", "upload_form.description": "Wprowadź opis dla niewidomych i niedowidzących",
"upload_form.focus": "Crop",
"upload_form.undo": "Cofnij", "upload_form.undo": "Cofnij",
"upload_progress.label": "Wysyłanie", "upload_progress.label": "Wysyłanie",
"video.close": "Zamknij film", "video.close": "Zamknij film",

View File

@@ -14,6 +14,7 @@
"account.mute": "Silenciar @{name}", "account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificações de @{name}", "account.mute_notifications": "Silenciar notificações de @{name}",
"account.posts": "Posts", "account.posts": "Posts",
"account.posts_with_replies": "Toots with replies",
"account.report": "Denunciar @{name}", "account.report": "Denunciar @{name}",
"account.requested": "Aguardando aprovação. Clique para cancelar a solicitação", "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
"account.share": "Compartilhar perfil de @{name}", "account.share": "Compartilhar perfil de @{name}",
@@ -90,7 +91,7 @@
"emoji_button.travel": "Viagens & Lugares", "emoji_button.travel": "Viagens & Lugares",
"empty_column.community": "A timeline local está vazia. Escreva algo publicamente para começar!", "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.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.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.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.", "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.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar", "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.placeholder": "Comentários adicionais",
"report.submit": "Enviar", "report.submit": "Enviar",
"report.target": "Denunciar", "report.target": "Denunciar",
@@ -216,6 +220,9 @@
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes", "search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
"search_popout.tips.user": "usuário", "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}}", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Dê uma espiada...", "standalone.public_title": "Dê uma espiada...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@@ -252,6 +259,7 @@
"upload_area.title": "Arraste e solte para enviar", "upload_area.title": "Arraste e solte para enviar",
"upload_button.label": "Adicionar mídia", "upload_button.label": "Adicionar mídia",
"upload_form.description": "Descreva a imagem para deficientes visuais", "upload_form.description": "Descreva a imagem para deficientes visuais",
"upload_form.focus": "Crop",
"upload_form.undo": "Desfazer", "upload_form.undo": "Desfazer",
"upload_progress.label": "Salvando...", "upload_progress.label": "Salvando...",
"video.close": "Fechar vídeo", "video.close": "Fechar vídeo",

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