Compare commits

...

57 Commits

Author SHA1 Message Date
Eugen Rochko
7571c37c99 Bump version to 2.1.1 (#6164) 2018-01-04 16:40:26 +01:00
Yamagishi Kazutoshi
3c18964256 Fallback default thumbnail in instance status API (#6177) 2018-01-04 15:36:55 +01:00
Marcin Mikołajczak
c61dd918a2 i18n: Update Polish translation (#6176)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-01-04 23:15:29 +09:00
Eugen Rochko
02ba03d6db Send one Delete of Actor in ActivityPub when account is suspended (#6172) 2018-01-04 14:40:49 +01:00
ThibG
3bee0996c5 Make sure private toots remain private and do not end up in HTTP caches (#6175) 2018-01-04 14:39:38 +01:00
muan
89daeb43a8 Improve Traditional Chinese translation (#6166)
* Improve Traditional Chinese translations

* Sort alphabetically
2018-01-04 05:00:50 +01:00
Eugen Rochko
7d4f4f9aab Fix FetchAtomService not finding alternatives if there's a Link header (#6170)
without them, such as is the case with GNU social

Fixes the ability to find GNU social accounts via URL in search and
when using remote follow function
2018-01-04 04:56:04 +01:00
Akihiko Odaki
256c2b1de0 Rearrange items in Getting Started navigation (#6126)
Though the subsections are representing features such as navigation and
settings, they are categorized by the ways how they are implemented
(internal navigation or external links.) They are irrelevant and some
arrangements were confusing because of that. (It is nonsense that instance
information is in settings subsection, for example.)

This fixes the issue by rearranging.
2018-01-04 10:56:54 +09:00
Eugen Rochko
02e3e1ec09 Fix nil error in log_target_from_history helper (#6173) 2018-01-04 10:56:23 +09:00
Eugen Rochko
ff924f95bb Fix OpenSSL dependency in ostatus2 (#6174) 2018-01-04 10:56:00 +09:00
Eugen Rochko
c10f4bdb03 Cache JSON of immutable ActivityPub representations (#6171) 2018-01-04 01:21:38 +01:00
Quenty31
d907d4352e l10n OC language (#6169)
* new strings: hashtag+unlisted, mute, block

* Add confirmation step for email changes

* Add more instance stats APIs
2018-01-03 21:05:54 +01:00
ThibG
a8b51124ba Don't normalize URLs in toots (#6134)
* Don't normalize URLs in toots

URL normalization is ill-defined and may cause certain links to break.

* Change specs since we are not normalizing user-provided URLs
2018-01-03 20:51:33 +01:00
Akihiko Odaki
161c72d66d Allow to dereference Follow object for ActivityPub (#5772)
* Allow to dereference Follow object for ActivityPub

* Accept IRI as object representation for Accept activity
2018-01-03 18:08:57 +01:00
Marcin Mikołajczak
53d99ebf4f i18n: Update Polish translation (#6168)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-01-03 22:45:24 +09:00
Yamagishi Kazutoshi
1001922156 Add Japanese translations #5997, #6003, #6004, #6071, #6099, #6125 and #6132 (#6167)
* yarn manage:translations

* Add Japanese translation for #5997

* Add Japanese translation for #6003

* Add Japanese translation for #6004

* Add Japanese translation for #6071

* Add Japanese translation for #6099

* Add Japanese translation for #6125

* Add Japanese translation for #6132
2018-01-03 21:00:39 +09:00
ThibG
99f962ba73 Allow HTTP caching of json view of public statuses (#6115)
* Allow HTTP caching of json view of public statuses

HTML views are not cached as they can contain private statuses as well

* Disable session cookies for ActivityPub json rendering of public toots
2018-01-03 04:57:57 +01:00
Akihiko Odaki
2471796d75 Set background to the navigation of Getting Started column (#6163)
The background of the navigation matters because its scrollbar is
transparent.
2018-01-03 04:39:14 +01:00
puckipedia
545095b3ce [!] Sanitize incoming classlist properly (#6162)
* Sanitize classlist properly

* Actually properly sanitize every class after the first

* Improve Formatter spec to check for multiple classes and non-space whitespace
2018-01-03 03:54:08 +01:00
Eugen Rochko
d319b3dbe4 Update moved-to property when it's removed too (#6160)
* Fix #6140 - Update moved-to property when it's removed too

* Remove trailing whitespace
2018-01-03 00:38:20 +01:00
Eugen Rochko
d60fd87e01 Don't leave behind husk of remotely-deleted profile (#6159)
There's no reason for an Account record to persist after Delete->Actor is received. SuspendAccountService is necessary to make sure deleted toots get sent over streaming API properly and home feeds get cleaned up. By removing Account record, we can ensure that if in the future the account is restored remotely (or username reused), it can start with a clean slate.
2018-01-03 00:38:02 +01:00
Noiob
94230fe565 Fix newlines-to-spaces functionality (#6158)
yay for regexes, amirite
2018-01-02 19:35:24 +01:00
Patrick Figel
04ecf44c2f Add confirmation step for email changes (#6071)
* Add confirmation step for email changes

This adds a confirmation step for email changes of existing users.
Like the initial account confirmation, a confirmation link is sent
to the new address.

Additionally, a notification is sent to the existing address when
the change is initiated. This message includes instruction to reset
the password immediately or to contact the instance admin if the
change was not initiated by the account owner.

Fixes #3871

* Add review fixes
2018-01-02 16:55:00 +01:00
ThibG
b6af88192f Display a warning when composing unlisted toots with something looking like a hashtag (#6132) 2018-01-02 14:24:52 +01:00
Eugen Rochko
1419f656e2 Fix stats expiring too quickly because of variable mistake (#6155) 2018-01-02 14:02:53 +01:00
Akihiko Odaki
3ba7cde38d Rename key to path in actions and reducers for settings (#6105) 2018-01-02 13:50:54 +01:00
Otakan
ce854ed506 delete X-UA-Compatible (#6068)
* delete X-UA-Compatible

* undo

* restore
2018-01-02 13:38:12 +01:00
Branko Kokanovic
21b9da6418 Adding Serbian latin translations (#6146)
Serbian latin (sr-Latn) is generated automatically from Serbian (sr) translation. Also changed some wording in original (Serbian) translation.
2018-01-02 20:39:12 +09:00
Akihiko Odaki
764f876953 Use const instead of let for constant (#6106) 2018-01-02 13:28:49 +09:00
Akihiko Odaki
2c1ed5f872 Show mastodon on modal (#6129) 2018-01-02 05:07:56 +01:00
Branko Kokanovic
7d376e41be Adding Serbian translation (#6133)
* Adding Serbian translation

* i18n-tasks normalize
2017-12-31 17:28:20 +09:00
Jeong Arm
f4b80e6511 Translate Korean (#6131)
Relates to #6125, #6099
2017-12-30 02:44:19 +01:00
beatrix
a56c4742d3 keep the same filters and page when doing custom emojo stuff (fixes #6112) (#6114) 2017-12-30 02:43:43 +01:00
Eugen Rochko
38fc1b498d Add more instance stats APIs (#6125)
* Add GET /api/v1/instance/peers API to reveal known domains

* Add GET /api/v1/instance/activity API

* Make new APIs disableable, exclude private statuses from activity stats

* Fix code style issue

* Fix week timestamps
2017-12-29 19:52:04 +01:00
MitarashiDango
511c6f9625 bug fix (WebPush does not work) (#6120) 2017-12-28 16:20:34 +01:00
ThibG
868568d1c1 Make host_meta/webfinger replies cacheable (fixes #6100) (#6101)
* Make host_meta/webfinger replies cacheable (fixes #6100)

Drop common code for handling users and sessions as webfinger queries
are very basic, public APIs.

Also explicitly mark results as cacheable with “expires_in”.

* Add “Vary: Accept” header for caching since content-negociation is used
2017-12-27 18:21:12 +01:00
Akihiko Odaki
65f30f65a2 Move the mastodon on Getting Started column to drawer column (#6109)
Getting Started column obtained many links, and it became much taller.
Because of its height, Getting Started column required long scrolling on
devices with small screen, such as 4 inch phones and 10 inch laptops.

This change moves the mastodon which took large space on the column to
drawer column. The drawer column has only the compose form and has more
space.
2017-12-27 03:31:30 +01:00
Akihiko Odaki
e0ef7f9d79 Fix XML oEmbed support discovery (#6104) 2017-12-27 03:29:49 +01:00
beatrix
127bfda521 add ruby-progressbar to gemfile (fixes #6110) (#6111) 2017-12-26 18:43:52 +01:00
takayamaki
1494509468 more faster index on notifications table (#6108) 2017-12-26 17:56:31 +01:00
Chris
1e5d1fa5c8 Add mute, block, conversation mute actions to detailed status dropdown menu (#6099)
* removed references to hideOnMobile in column_link and getting_started

* add mute, block, conversationMute actions to detailed status dropdown (fixes #1226)

* remove unused withDismiss in detailed status
2017-12-25 20:56:05 +01:00
MitarashiDango
a3b369337f Additional prop name change. (#6098) 2017-12-26 00:14:06 +09:00
Yamagishi Kazutoshi
43c37a4768 Add supported Node.js version to package.json (#6096) 2017-12-25 15:02:07 +01:00
Eugen Rochko
cafe27fb29 Add rake task to check and purge accounts that are missing in origin (#6085)
* Add rake task to check and purge accounts that are missing in origin

* Add progress bar and --force options to mastodon:maintenance:purge_removed_accounts
2017-12-24 16:14:33 +01:00
Neetshin
7e6214b869 Add validation for onMuteNotifications (#6092)
* Add aria-autocomplete='list' in Textaria

ref: https://www.w3.org/TR/wai-aria-1.1/#aria-autocomplete

* Make detect empty string brefore assign upload description

* Change code elements in keyboard-shortcuts component to kbd

* Add validation for onMuteNotifications
2017-12-24 17:18:45 +09:00
Nolan Lawson
a8eb0bf44f Reduce motion for boost animation (#5871)
* Reduce motion for boost animation

Fixes #5833

* Fix ternary expression
2017-12-24 04:48:31 +01:00
Akihiko Odaki
35fdf561be Refactor web_push_subscription (#6047)
* Remove onSave method in mapped properties for column_settings

* Make web_push_subscription.register an action
2017-12-24 04:47:35 +01:00
Chris
081956742c removed references to hideOnMobile in column_link and getting_started (#6082)
* removed references to hideOnMobile in column_link and getting_started

* move keyboard shortcuts back below blocked users
2017-12-24 04:47:02 +01:00
cpsdqs
8528fd89d2 Move dropdown transform origin to top edge (#6091) 2017-12-24 00:53:03 +01:00
nightpool
9592b5e31e enforce LOCAL_HTTPS=true in production (#6061)
* enforce https in production

* note changes in production env sample

* typo fix
2017-12-22 02:17:59 +01:00
ThibG
cea98e0c12 Reduce the number of synchronous resolves when posting toots (#6075) 2017-12-22 02:15:08 +01:00
ThibG
6eb60260b1 Display deleted users' role as “Suspended” (#6080)
Deleted users are technically suspended, but the code displaying their status
in the admin interface was broken and displayed a javascript object holding
translations of the possible user roles instead.
2017-12-22 02:14:17 +01:00
Akihiko Odaki
81d29e4126 Rename settingKey in setting_toggle to settingPath (#6046) 2017-12-20 16:19:59 +09:00
Neetshin
c11a52d888 Replace <code> to <kbd> in KeyboardShortcuts component (#6049)
* Add aria-autocomplete='list' in Textaria

ref: https://www.w3.org/TR/wai-aria-1.1/#aria-autocomplete

* Make detect empty string brefore assign upload description

* Change code elements in keyboard-shortcuts component to kbd
2017-12-20 11:46:25 +09:00
Jeong Arm
e52293482e Update Korean translation (#6050)
* Update Korean translation

* Translate Korean for javascript

* Add missing translations on simple_form
2017-12-20 11:45:50 +09:00
Peter
f38e6a14f2 Add Slovak translation (#6052)
* Add Slovak translation

* Slovak translation: i18n-normalize
2017-12-17 11:26:42 +09:00
Daigo 3 Dango
a434d9c0cc Remove period from the version number (#6039)
2.1.0. -> 2.1.0
2017-12-15 21:38:25 +01:00
188 changed files with 4849 additions and 551 deletions

View File

@@ -11,10 +11,11 @@ DB_PASS=
DB_PORT=5432
# Federation
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
# Note: Changing LOCAL_DOMAIN at a later time will cause unwanted side effects, including breaking all existing federation.
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=example.com
LOCAL_HTTPS=true
# Changing LOCAL_HTTPS in production is no longer supported. (Mastodon will always serve https:// links)
# Use this only if you need to run mastodon on a different domain than the one used for federation.
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md

View File

@@ -28,7 +28,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639'
gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.2'
gem 'devise', '~> 4.3'
gem 'devise-two-factor', '~> 3.0'
gem 'doorkeeper', '~> 4.2'
gem 'fast_blank', '~> 1.0'
@@ -58,6 +58,7 @@ gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 0.10'
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
gem 'ruby-progressbar', '~> 1.4'
gem 'sanitize', '~> 4.4'
gem 'sidekiq', '~> 5.0'
gem 'sidekiq-scheduler', '~> 2.1'

View File

@@ -299,13 +299,11 @@ GEM
sidekiq (>= 3.5.0)
statsd-ruby (~> 1.2.0)
oj (3.3.9)
openssl (2.0.6)
orm_adapter (0.5.0)
ostatus2 (2.0.1)
ostatus2 (2.0.2)
addressable (~> 2.4)
http (~> 2.0)
nokogiri (~> 1.6)
openssl (~> 2.0)
ox (2.8.2)
paperclip (5.1.0)
activemodel (>= 4.2.0)
@@ -561,7 +559,7 @@ DEPENDENCIES
charlock_holmes (~> 0.7.5)
cld3 (~> 3.2.0)
climate_control (~> 0.2)
devise (~> 4.2)
devise (~> 4.3)
devise-two-factor (~> 3.0)
doorkeeper (~> 4.2)
dotenv-rails (~> 2.2)
@@ -621,6 +619,7 @@ DEPENDENCIES
rspec-sidekiq (~> 3.0)
rubocop
ruby-oembed (~> 0.12)
ruby-progressbar (~> 1.4)
sanitize (~> 4.4)
scss_lint (~> 0.55)
sidekiq (~> 5.0)
@@ -643,4 +642,4 @@ RUBY VERSION
ruby 2.4.2p198
BUNDLED WITH
1.16.0
1.16.1

View File

@@ -2,7 +2,8 @@
class AccountsController < ApplicationController
include AccountControllerConcern
include SignatureVerification
before_action :set_cache_headers
def show
respond_to do |format|
@@ -26,10 +27,11 @@ class AccountsController < ApplicationController
end
format.json do
render json: @account,
serializer: ActivityPub::ActorSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
skip_session!
render_cached_json(['activitypub', 'actor', @account.cache_key], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter)
end
end
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
class ActivityPub::FollowsController < Api::BaseController
include SignatureVerification
def show
render json: follow_request,
serializer: ActivityPub::FollowSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
end
private
def follow_request
FollowRequest.includes(:account).references(:account).find_by!(
id: params.require(:id),
accounts: { domain: nil, username: params.require(:account_username) },
target_account: signed_request_account
)
end
end

View File

@@ -3,6 +3,7 @@
module Admin
class CustomEmojisController < BaseController
before_action :set_custom_emoji, except: [:index, :new, :create]
before_action :set_filter_params
def index
authorize :custom_emoji, :index?
@@ -32,23 +33,26 @@ module Admin
if @custom_emoji.update(resource_params)
log_action :update, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
flash[:notice] = I18n.t('admin.custom_emojis.updated_msg')
else
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg')
flash[:alert] = I18n.t('admin.custom_emojis.update_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def destroy
authorize @custom_emoji, :destroy?
@custom_emoji.destroy!
log_action :destroy, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
flash[:notice] = I18n.t('admin.custom_emojis.destroyed_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def copy
authorize @custom_emoji, :copy?
emoji = CustomEmoji.find_or_initialize_by(domain: nil, shortcode: @custom_emoji.shortcode)
emoji = CustomEmoji.find_or_initialize_by(domain: nil,
shortcode: @custom_emoji.shortcode)
emoji.image = @custom_emoji.image
if emoji.save
@@ -58,21 +62,23 @@ module Admin
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
end
redirect_to admin_custom_emojis_path(page: params[:page])
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def enable
authorize @custom_emoji, :enable?
@custom_emoji.update!(disabled: false)
log_action :enable, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
flash[:notice] = I18n.t('admin.custom_emojis.enabled_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
def disable
authorize @custom_emoji, :disable?
@custom_emoji.update!(disabled: true)
log_action :disable, @custom_emoji
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
flash[:notice] = I18n.t('admin.custom_emojis.disabled_msg')
redirect_to admin_custom_emojis_path(page: params[:page], **@filter_params)
end
private
@@ -81,6 +87,10 @@ module Admin
@custom_emoji = CustomEmoji.find(params[:id])
end
def set_filter_params
@filter_params = filter_params.to_hash.symbolize_keys
end
def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
end

View File

@@ -17,6 +17,8 @@ module Admin
bootstrap_timeline_accounts
thumbnail
min_invite_role
activity_api_enabled
peers_api_enabled
).freeze
BOOLEAN_SETTINGS = %w(
@@ -24,6 +26,8 @@ module Admin
open_deletion
timeline_preview
show_staff_badge
activity_api_enabled
peers_api_enabled
).freeze
UPLOAD_SETTINGS = %w(

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
respond_to :json
def show
render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
end
private
def activity
weeks = []
12.times do |i|
day = i.weeks.ago.to_date
week_id = day.cweek
week = Date.commercial(day.cwyear, week_id)
weeks << {
week: week.to_time.to_i.to_s,
statuses: Redis.current.get("activity:statuses:local:#{week_id}") || 0,
logins: Redis.current.pfcount("activity:logins:#{week_id}"),
registrations: Redis.current.get("activity:accounts:local:#{week_id}") || 0,
}
end
weeks
end
def require_enabled_api!
head 404 unless Setting.activity_api_enabled
end
end

View File

@@ -0,0 +1,17 @@
# frozen_string_literal: true
class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
respond_to :json
def index
render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
end
private
def require_enabled_api!
head 404 unless Setting.peers_api_enabled
end
end

View File

@@ -121,4 +121,26 @@ class ApplicationController < ActionController::Base
end
end
end
def render_cached_json(cache_key, **options)
options[:expires_in] ||= 3.minutes
options[:public] ||= true
cache_key = cache_key.join(':') if cache_key.is_a?(Enumerable)
content_type = options.delete(:content_type) || 'application/json'
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
yield.to_json
end
expires_in options[:expires_in], public: options[:public]
render json: data, content_type: content_type
end
def set_cache_headers
response.headers['Vary'] = 'Accept'
end
def skip_session!
request.session_options[:skip] = true
end
end

View File

@@ -2,10 +2,4 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth'
def show
super do |user|
BootstrapTimelineWorker.perform_async(user.account_id) if user.errors.empty?
end
end
end

View File

@@ -37,6 +37,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
new_user_session_path
end
def after_update_path_for(_resource)
edit_user_registration_path
end
def check_enabled_registrations
redirect_to root_path if single_user_mode? || !allowed_registrations?
end

View File

@@ -4,6 +4,7 @@ class AuthorizeFollowsController < ApplicationController
layout 'modal'
before_action :authenticate_user!
before_action :set_body_classes
def show
@account = located_account || render(:error)
@@ -58,4 +59,8 @@ class AuthorizeFollowsController < ApplicationController
def acct_params
params.fetch(:acct, '')
end
def set_body_classes
@body_classes = 'modal-layout'
end
end

View File

@@ -17,6 +17,7 @@ module UserTrackingConcern
# Mark as signed-in today
current_user.update_tracked_fields!(request)
ActivityTracker.record('activity:logins', current_user.id)
# Regenerate feed if needed
regenerate_feed! if user_needs_feed_update?

View File

@@ -2,14 +2,16 @@
class EmojisController < ApplicationController
before_action :set_emoji
before_action :set_cache_headers
def show
respond_to do |format|
format.json do
render json: @emoji,
serializer: ActivityPub::EmojiSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
skip_session!
render_cached_json(['activitypub', 'emoji', @emoji.cache_key], content_type: 'application/activity+json') do
ActiveModelSerializers::SerializableResource.new(@emoji, serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter)
end
end
end
end

View File

@@ -38,4 +38,8 @@ class RemoteFollowController < ApplicationController
def suspended_account?
@account.suspended?
end
def set_body_classes
@body_classes = 'modal-layout'
end
end

View File

@@ -25,6 +25,6 @@ class SharesController < ApplicationController
end
def set_body_classes
@body_classes = 'compose-standalone'
@body_classes = 'modal-layout compose-standalone'
end
end

View File

@@ -10,6 +10,7 @@ class StatusesController < ApplicationController
before_action :set_link_headers
before_action :check_account_suspension
before_action :redirect_to_original, only: [:show]
before_action :set_cache_headers
def show
respond_to do |format|
@@ -21,19 +22,21 @@ class StatusesController < ApplicationController
end
format.json do
render json: @status,
serializer: ActivityPub::NoteSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
skip_session! unless @stream_entry.hidden?
render_cached_json(['activitypub', 'note', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter)
end
end
end
end
def activity
render json: @status,
serializer: ActivityPub::ActivitySerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
skip_session!
render_cached_json(['activitypub', 'activity', @status.cache_key], content_type: 'application/activity+json', public: !@stream_entry.hidden?) do
ActiveModelSerializers::SerializableResource.new(@status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter)
end
end
def embed

View File

@@ -1,15 +1,19 @@
# frozen_string_literal: true
module WellKnown
class HostMetaController < ApplicationController
class HostMetaController < ActionController::Base
include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' }
def show
@webfinger_template = "#{webfinger_url}?resource={uri}"
respond_to do |format|
format.xml { render content_type: 'application/xrd+xml' }
end
expires_in(3.days, public: true)
end
end
end

View File

@@ -1,9 +1,11 @@
# frozen_string_literal: true
module WellKnown
class WebfingerController < ApplicationController
class WebfingerController < ActionController::Base
include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' }
def show
@account = Account.find_local!(username_from_resource)
@@ -16,6 +18,8 @@ module WellKnown
render content_type: 'application/xrd+xml'
end
end
expires_in(3.days, public: true)
rescue ActiveRecord::RecordNotFound
head 404
end

View File

@@ -34,7 +34,7 @@ module Admin::ActionLogsHelper
link_to attributes['domain'], "https://#{attributes['domain']}"
when 'Status'
tmp_status = Status.new(attributes)
link_to tmp_status.account.acct, TagManager.instance.url_for(tmp_status)
link_to tmp_status.account&.acct || "##{tmp_status.account_id}", TagManager.instance.url_for(tmp_status)
end
end

View File

@@ -4,6 +4,7 @@ module RoutingHelper
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include Webpacker::Helper
included do
def default_url_options
@@ -17,6 +18,10 @@ module RoutingHelper
URI.join(root_url, source).to_s
end
def full_pack_url(source, **options)
full_asset_url(asset_pack_path(source, options))
end
private
def use_storage?

View File

@@ -28,6 +28,9 @@ module SettingsHelper
pt: 'Português',
'pt-BR': 'Português do Brasil',
ru: 'Русский',
sk: 'Slovensky',
sr: 'Српски',
'sr-Latn': 'Srpski (latinica)',
sv: 'Svenska',
th: 'ภาษาไทย',
tr: 'Türkçe',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@@ -31,7 +31,7 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
const unescapeHTML = (html) => {
const wrapper = document.createElement('div');
html = html.replace(/<br \/>|<br>|\n/, ' ');
html = html.replace(/<br \/>|<br>|\n/g, ' ');
wrapper.innerHTML = html;
return wrapper.textContent;
};

View File

@@ -1,57 +0,0 @@
import axios from 'axios';
import { pushNotificationsSetting } from '../settings';
export const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION';
export const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION';
export const ALERTS_CHANGE = 'PUSH_NOTIFICATIONS_ALERTS_CHANGE';
export function setBrowserSupport (value) {
return {
type: SET_BROWSER_SUPPORT,
value,
};
}
export function setSubscription (subscription) {
return {
type: SET_SUBSCRIPTION,
subscription,
};
}
export function clearSubscription () {
return {
type: CLEAR_SUBSCRIPTION,
};
}
export function changeAlerts(key, value) {
return dispatch => {
dispatch({
type: ALERTS_CHANGE,
key,
value,
});
dispatch(saveSettings());
};
}
export function saveSettings() {
return (_, getState) => {
const state = getState().get('push_notifications');
const subscription = state.get('subscription');
const alerts = state.get('alerts');
const data = { alerts };
axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
data,
}).then(() => {
const me = getState().getIn(['meta', 'me']);
if (me) {
pushNotificationsSetting.set(me, data);
}
});
};
}

View File

@@ -0,0 +1,23 @@
import {
SET_BROWSER_SUPPORT,
SET_SUBSCRIPTION,
CLEAR_SUBSCRIPTION,
SET_ALERTS,
setAlerts,
} from './setter';
import { register, saveSettings } from './registerer';
export {
SET_BROWSER_SUPPORT,
SET_SUBSCRIPTION,
CLEAR_SUBSCRIPTION,
SET_ALERTS,
register,
};
export function changeAlerts(path, value) {
return dispatch => {
dispatch(setAlerts(path, value));
dispatch(saveSettings());
};
}

View File

@@ -0,0 +1,149 @@
import axios from 'axios';
import { pushNotificationsSetting } from '../../settings';
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
// Taken from https://www.npmjs.com/package/web-push
const urlBase64ToUint8Array = (base64String) => {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
const getRegistration = () => navigator.serviceWorker.ready;
const getPushSubscription = (registration) =>
registration.pushManager.getSubscription()
.then(subscription => ({ registration, subscription }));
const subscribe = (registration) =>
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(getApplicationServerKey()),
});
const unsubscribe = ({ registration, subscription }) =>
subscription ? subscription.unsubscribe().then(() => registration) : registration;
const sendSubscriptionToBackend = (subscription, me) => {
const params = { subscription };
if (me) {
const data = pushNotificationsSetting.get(me);
if (data) {
params.data = data;
}
}
return axios.post('/api/web/push_subscriptions', params).then(response => response.data);
};
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype);
export function register () {
return (dispatch, getState) => {
dispatch(setBrowserSupport(supportsPushNotifications));
const me = getState().getIn(['meta', 'me']);
if (me && !pushNotificationsSetting.get(me)) {
const alerts = getState().getIn(['push_notifications', 'alerts']);
if (alerts) {
pushNotificationsSetting.set(me, { alerts: alerts });
}
}
if (supportsPushNotifications) {
if (!getApplicationServerKey()) {
console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.');
return;
}
getRegistration()
.then(getPushSubscription)
.then(({ registration, subscription }) => {
if (subscription !== null) {
// We have a subscription, check if it is still valid
const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey)).toString();
const subscriptionServerKey = urlBase64ToUint8Array(getApplicationServerKey()).toString();
const serverEndpoint = getState().getIn(['push_notifications', 'subscription', 'endpoint']);
// If the VAPID public key did not change and the endpoint corresponds
// to the endpoint saved in the backend, the subscription is valid
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) {
return subscription;
} else {
// Something went wrong, try to subscribe again
return unsubscribe({ registration, subscription }).then(subscribe).then(
subscription => sendSubscriptionToBackend(subscription, me));
}
}
// No subscription, try to subscribe
return subscribe(registration).then(
subscription => sendSubscriptionToBackend(subscription, me));
})
.then(subscription => {
// If we got a PushSubscription (and not a subscription object from the backend)
// it means that the backend subscription is valid (and was set during hydration)
if (!(subscription instanceof PushSubscription)) {
dispatch(setSubscription(subscription));
if (me) {
pushNotificationsSetting.set(me, { alerts: subscription.alerts });
}
}
})
.catch(error => {
if (error.code === 20 && error.name === 'AbortError') {
console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.');
} else if (error.code === 5 && error.name === 'InvalidCharacterError') {
console.error('The VAPID public key seems to be invalid:', getApplicationServerKey());
}
// Clear alerts and hide UI settings
dispatch(clearSubscription());
if (me) {
pushNotificationsSetting.remove(me);
}
try {
getRegistration()
.then(getPushSubscription)
.then(unsubscribe);
} catch (e) {
}
});
} else {
console.warn('Your browser does not support Web Push Notifications.');
}
};
}
export function saveSettings() {
return (_, getState) => {
const state = getState().get('push_notifications');
const subscription = state.get('subscription');
const alerts = state.get('alerts');
const data = { alerts };
axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
data,
}).then(() => {
const me = getState().getIn(['meta', 'me']);
if (me) {
pushNotificationsSetting.set(me, data);
}
});
};
}

View File

@@ -0,0 +1,34 @@
export const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION';
export const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION';
export const SET_ALERTS = 'PUSH_NOTIFICATIONS_SET_ALERTS';
export function setBrowserSupport (value) {
return {
type: SET_BROWSER_SUPPORT,
value,
};
}
export function setSubscription (subscription) {
return {
type: SET_SUBSCRIPTION,
subscription,
};
}
export function clearSubscription () {
return {
type: CLEAR_SUBSCRIPTION,
};
}
export function setAlerts (path, value) {
return dispatch => {
dispatch({
type: SET_ALERTS,
path,
value,
});
};
}

View File

@@ -4,11 +4,11 @@ import { debounce } from 'lodash';
export const SETTING_CHANGE = 'SETTING_CHANGE';
export const SETTING_SAVE = 'SETTING_SAVE';
export function changeSetting(key, value) {
export function changeSetting(path, value) {
return dispatch => {
dispatch({
type: SETTING_CHANGE,
key,
path,
value,
});
@@ -21,7 +21,7 @@ const debouncedSave = debounce((dispatch, getState) => {
return;
}
const data = getState().get('settings').filter((_, key) => key !== 'saved').toJS();
const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS();
axios.put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE }));
}, 5000, { trailing: true });

View File

@@ -27,6 +27,7 @@ export default class Account extends ImmutablePureComponent {
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
onMuteNotifications: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
};

View File

@@ -5,20 +5,27 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { me } from '../../../initial_state';
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
const mapStateToProps = state => ({
needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
hashtagWarning: state.getIn(['compose', 'privacy']) !== 'public' && APPROX_HASHTAG_RE.test(state.getIn(['compose', 'text'])),
});
const WarningWrapper = ({ needsLockWarning }) => {
const WarningWrapper = ({ needsLockWarning, hashtagWarning }) => {
if (needsLockWarning) {
return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
}
if (hashtagWarning) {
return <Warning message={<FormattedMessage id='compose_form.hashtag_warning' defaultMessage="This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag." />} />;
}
return null;
};
WarningWrapper.propTypes = {
needsLockWarning: PropTypes.bool,
hashtagWarning: PropTypes.bool,
};
export default connect(mapStateToProps)(WarningWrapper);

View File

@@ -94,6 +94,7 @@ export default class Compose extends React.PureComponent {
<div className='drawer__inner' onFocus={this.onFocus}>
<NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer />
<div className='mastodon' />
</div>
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>

View File

@@ -48,7 +48,7 @@ export default class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, columns, multiColumn } = this.props;
let navItems = [];
const navItems = [];
if (multiColumn) {
if (!columns.find(item => item.get('id') === 'HOME')) {
@@ -68,21 +68,20 @@ export default class GettingStarted extends ImmutablePureComponent {
}
}
navItems = navItems.concat([
navItems.push(
<ColumnLink key='4' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='5' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />,
<ColumnLink key='9' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />,
]);
<ColumnLink key='5' icon='bars' text={intl.formatMessage(messages.lists)} to='/lists' />
);
if (myAccount.get('locked')) {
navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />);
}
navItems = navItems.concat([
<ColumnLink key='7' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />,
<ColumnLink key='8' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />,
<ColumnLink key='10' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' hideOnMobile />,
]);
if (multiColumn) {
navItems.push(<ColumnLink key='7' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />);
}
navItems.push(<ColumnLink key='8' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />);
return (
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
@@ -90,24 +89,24 @@ export default class GettingStarted extends ImmutablePureComponent {
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
{navItems}
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
<ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
<ColumnLink icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />
<ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
<ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
<ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
</div>
<div className='getting-started__footer scrollable optionally-scrollable'>
<div className='static-content getting-started'>
<p>
<a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
</p>
<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> }}
/>
</p>
</div>
<div className='static-content getting-started'>
<p>
<a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
</p>
<p>
<FormattedMessage
id='getting_started.open_source_notice'
defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}.'
values={{ github: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>tootsuite/mastodon</a> }}
/>
</p>
</div>
</Column>
);

View File

@@ -27,11 +27,11 @@ export default class ColumnSettings extends React.PureComponent {
<span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='home_timeline' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
<SettingToggle prefix='home_timeline' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
</div>
<div className='column-settings__row'>
<SettingToggle prefix='home_timeline' settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
<SettingToggle prefix='home_timeline' settings={settings} settingPath={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
</div>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>

View File

@@ -33,59 +33,59 @@ export default class KeyboardShortcuts extends ImmutablePureComponent {
</thead>
<tbody>
<tr>
<td><code>r</code></td>
<td><kbd>r</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.reply' defaultMessage='to reply' /></td>
</tr>
<tr>
<td><code>m</code></td>
<td><kbd>m</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.mention' defaultMessage='to mention author' /></td>
</tr>
<tr>
<td><code>f</code></td>
<td><kbd>f</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favourite' /></td>
</tr>
<tr>
<td><code>b</code></td>
<td><kbd>b</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.boost' defaultMessage='to boost' /></td>
</tr>
<tr>
<td><code>enter</code></td>
<td><kbd>enter</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
</tr>
<tr>
<td><code>up</code></td>
<td><kbd>up</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
</tr>
<tr>
<td><code>down</code></td>
<td><kbd>down</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.down' defaultMessage='to move down in the list' /></td>
</tr>
<tr>
<td><code>1</code>-<code>9</code></td>
<td><kbd>1</kbd>-<kbd>9</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.column' defaultMessage='to focus a status in one of the columns' /></td>
</tr>
<tr>
<td><code>n</code></td>
<td><kbd>n</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.compose' defaultMessage='to focus the compose textarea' /></td>
</tr>
<tr>
<td><code>alt</code>+<code>n</code></td>
<td><kbd>alt</kbd>+<kbd>n</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new toot' /></td>
</tr>
<tr>
<td><code>backspace</code></td>
<td><kbd>backspace</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.back' defaultMessage='to navigate back' /></td>
</tr>
<tr>
<td><code>s</code></td>
<td><kbd>s</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to focus search' /></td>
</tr>
<tr>
<td><code>esc</code></td>
<td><kbd>esc</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
</tr>
<tr>
<td><code>?</code></td>
<td><kbd>?</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></td>
</tr>
</tbody>

View File

@@ -11,12 +11,11 @@ export default class ColumnSettings extends React.PureComponent {
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
};
onPushChange = (key, checked) => {
this.props.onChange(['push', ...key], checked);
onPushChange = (path, checked) => {
this.props.onChange(['push', ...path], checked);
}
render () {
@@ -40,10 +39,10 @@ export default class ColumnSettings extends React.PureComponent {
<span id='notifications-follow' className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
</div>
@@ -51,10 +50,10 @@ export default class ColumnSettings extends React.PureComponent {
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favourite']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div>
</div>
@@ -62,10 +61,10 @@ export default class ColumnSettings extends React.PureComponent {
<span id='notifications-mention' className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
</div>
@@ -73,10 +72,10 @@ export default class ColumnSettings extends React.PureComponent {
<span id='notifications-reblog' className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
<div className='column-settings__row'>
<SettingToggle prefix='notifications_desktop' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingKey={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'reblog']} meta={pushMeta} onChange={this.onPushChange} label={pushStr} />}
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div>
</div>
</div>

View File

@@ -8,23 +8,23 @@ export default class SettingToggle extends React.PureComponent {
static propTypes = {
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired,
settingKey: PropTypes.array.isRequired,
settingPath: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
meta: PropTypes.node,
onChange: PropTypes.func.isRequired,
}
onChange = ({ target }) => {
this.props.onChange(this.props.settingKey, target.checked);
this.props.onChange(this.props.settingPath, target.checked);
}
render () {
const { prefix, settings, settingKey, label, meta } = this.props;
const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
const { prefix, settings, settingPath, label, meta } = this.props;
const id = ['setting-toggle', prefix, ...settingPath].filter(Boolean).join('-');
return (
<div className='setting-toggle'>
<Toggle id={id} checked={settings.getIn(settingKey)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
<Toggle id={id} checked={settings.getIn(settingPath)} onChange={this.onChange} onKeyDown={this.onKeyDown} />
<label htmlFor={id} className='setting-toggle__label'>{label}</label>
{meta && <span className='setting-meta__label'>{meta}</span>}
</div>

View File

@@ -1,9 +1,9 @@
import { connect } from 'react-redux';
import { defineMessages, injectIntl } from 'react-intl';
import ColumnSettings from '../components/column_settings';
import { changeSetting, saveSettings } from '../../../actions/settings';
import { changeSetting } from '../../../actions/settings';
import { clearNotifications } from '../../../actions/notifications';
import { changeAlerts as changePushNotifications, saveSettings as savePushNotificationSettings } from '../../../actions/push_notifications';
import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications';
import { openModal } from '../../../actions/modal';
const messages = defineMessages({
@@ -18,19 +18,14 @@ const mapStateToProps = state => ({
const mapDispatchToProps = (dispatch, { intl }) => ({
onChange (key, checked) {
if (key[0] === 'push') {
dispatch(changePushNotifications(key.slice(1), checked));
onChange (path, checked) {
if (path[0] === 'push') {
dispatch(changePushNotifications(path.slice(1), checked));
} else {
dispatch(changeSetting(['notifications', ...key], checked));
dispatch(changeSetting(['notifications', ...path], checked));
}
},
onSave () {
dispatch(saveSettings());
dispatch(savePushNotificationSettings());
},
onClear () {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.clearMessage),

View File

@@ -13,6 +13,10 @@ const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
mute: { id: 'status.mute', defaultMessage: 'Mute @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
block: { id: 'status.block', defaultMessage: 'Block @{name}' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
share: { id: 'status.share', defaultMessage: 'Share' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
@@ -34,6 +38,9 @@ export default class ActionBar extends React.PureComponent {
onFavourite: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired,
onMute: PropTypes.func,
onMuteConversation: PropTypes.func,
onBlock: PropTypes.func,
onReport: PropTypes.func,
onPin: PropTypes.func,
onEmbed: PropTypes.func,
@@ -60,6 +67,18 @@ export default class ActionBar extends React.PureComponent {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
}
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status);
}
handleBlockClick = () => {
this.props.onBlock(this.props.status.get('account'));
}
handleReport = () => {
this.props.onReport(this.props.status);
}
@@ -83,6 +102,7 @@ export default class ActionBar extends React.PureComponent {
const { status, intl } = this.props;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
const mutingConversation = status.get('muted');
let menu = [];
@@ -95,10 +115,15 @@ export default class ActionBar extends React.PureComponent {
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
}
menu.push(null);
menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
}

View File

@@ -20,14 +20,16 @@ import {
replyCompose,
mentionCompose,
} from '../../actions/compose';
import { deleteStatus } from '../../actions/statuses';
import { blockAccount } from '../../actions/accounts';
import { muteStatus, unmuteStatus, deleteStatus } from '../../actions/statuses';
import { initMuteModal } from '../../actions/mutes';
import { initReport } from '../../actions/reports';
import { makeGetStatus } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll-4';
import ColumnBackButton from '../../components/column_back_button';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
import { boostModal, deleteModal } from '../../initial_state';
@@ -36,6 +38,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
});
const makeMapStateToProps = () => {
@@ -148,6 +151,28 @@ export default class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('VIDEO', { media, time }));
}
handleMuteClick = (account) => {
this.props.dispatch(initMuteModal(account));
}
handleConversationMuteClick = (status) => {
if (status.get('muted')) {
this.props.dispatch(unmuteStatus(status.get('id')));
} else {
this.props.dispatch(muteStatus(status.get('id')));
}
}
handleBlockClick = (account) => {
const { dispatch, intl } = this.props;
dispatch(openModal('CONFIRM', {
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
}));
}
handleReport = (status) => {
this.props.dispatch(initReport(status.get('account'), status));
}
@@ -321,6 +346,9 @@ export default class Status extends ImmutablePureComponent {
onReblog={this.handleReblogClick}
onDelete={this.handleDeleteClick}
onMention={this.handleMentionClick}
onMute={this.handleMuteClick}
onMuteConversation={this.handleConversationMuteClick}
onBlock={this.handleBlockClick}
onReport={this.handleReport}
onPin={this.handlePin}
onEmbed={this.handleEmbed}

View File

@@ -26,7 +26,6 @@ ColumnLink.propTypes = {
to: PropTypes.string,
href: PropTypes.string,
method: PropTypes.string,
hideOnMobile: PropTypes.bool,
};
export default ColumnLink;

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "فك التدبيس",
"column_subheading.navigation": "التصفح",
"column_subheading.settings": "الإعدادات",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
"compose_form.lock_disclaimer.lock": "مقفل",
"compose_form.placeholder": "فيمَ تفكّر؟",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "مستخدِم",
"search_results.total": "{count, number} {count, plural, one {result} و {results}}",
"standalone.public_title": "نظرة على ...",
"status.block": "Block @{name}",
"status.cannot_reblog": "تعذرت ترقية هذا المنشور",
"status.delete": "إحذف",
"status.embed": "إدماج",
@@ -221,6 +223,7 @@
"status.media_hidden": "الصورة مستترة",
"status.mention": "أذكُر @{name}",
"status.more": "المزيد",
"status.mute": "Mute @{name}",
"status.mute_conversation": "كتم المحادثة",
"status.open": "وسع هذه المشاركة",
"status.pin": "تدبيس على الملف الشخصي",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "Какво си мислиш?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Изтриване",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media hidden",
"status.mention": "Споменаване",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Deslligar",
"column_subheading.navigation": "Navegació",
"column_subheading.settings": "Configuració",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "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.placeholder": "En què estàs pensant?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "usuari",
"search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
"standalone.public_title": "Una mirada a l'interior ...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Aquesta publicació no pot ser retootejada",
"status.delete": "Esborrar",
"status.embed": "Incrustar",
@@ -221,6 +223,7 @@
"status.media_hidden": "Multimèdia amagat",
"status.mention": "Esmentar @{name}",
"status.more": "Més",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Silenciar conversació",
"status.open": "Ampliar aquest estat",
"status.pin": "Fixat en el perfil",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Lösen",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Einstellungen",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Dein Profil ist nicht {locked}. Wer dir folgen will, kann das jederzeit tun und dann auch deine privaten Beiträge sehen.",
"compose_form.lock_disclaimer.lock": "gesperrt",
"compose_form.placeholder": "Worüber möchtest du schreiben?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
"standalone.public_title": "Ein kleiner Einblick …",
"status.block": "Block @{name}",
"status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
"status.delete": "Löschen",
"status.embed": "Einbetten",
@@ -221,6 +223,7 @@
"status.media_hidden": "Medien versteckt",
"status.mention": "@{name} erwähnen",
"status.more": "Mehr",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Thread stummschalten",
"status.open": "Diesen Beitrag öffnen",
"status.pin": "Im Profil anheften",

View File

@@ -727,6 +727,10 @@
{
"defaultMessage": "locked",
"id": "compose_form.lock_disclaimer.lock"
},
{
"defaultMessage": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"id": "compose_form.hashtag_warning"
}
],
"path": "app/javascript/mastodon/features/compose/containers/warning_container.json"
@@ -1053,7 +1057,7 @@
"id": "lists.delete"
},
{
"defaultMessage": "There is nothing in this list yet.",
"defaultMessage": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"id": "empty_column.list"
}
],
@@ -1244,6 +1248,22 @@
"defaultMessage": "Favourite",
"id": "status.favourite"
},
{
"defaultMessage": "Mute @{name}",
"id": "status.mute"
},
{
"defaultMessage": "Mute conversation",
"id": "status.mute_conversation"
},
{
"defaultMessage": "Unmute conversation",
"id": "status.unmute_conversation"
},
{
"defaultMessage": "Block @{name}",
"id": "status.block"
},
{
"defaultMessage": "Report @{name}",
"id": "status.report"
@@ -1276,6 +1296,14 @@
{
"defaultMessage": "Are you sure you want to delete this status?",
"id": "confirmations.delete.message"
},
{
"defaultMessage": "Block",
"id": "confirmations.block.confirm"
},
{
"defaultMessage": "Are you sure you want to block {name}?",
"id": "confirmations.block.message"
}
],
"path": "app/javascript/mastodon/features/status/index.json"

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What is on your mind?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Delete",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Depingli",
"column_subheading.navigation": "Navigado",
"column_subheading.settings": "Agordoj",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Via konta ne estas ŝlosita. Iu ajn povas sekvi vin por vidi viajn privatajn pepojn.",
"compose_form.lock_disclaimer.lock": "ŝlosita",
"compose_form.placeholder": "Pri kio vi pensas?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "uzanto",
"search_results.total": "{count, number} {count, plural, one {rezultato} other {rezultatoj}}",
"standalone.public_title": "Rigardeti…",
"status.block": "Block @{name}",
"status.cannot_reblog": "Tiun publikaĵon oni ne povas diskonigi",
"status.delete": "Forigi",
"status.embed": "Enmeti",
@@ -221,6 +223,7 @@
"status.media_hidden": "Sonbildaĵo kaŝita",
"status.mention": "Mencii @{name}",
"status.more": "Pli",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Silentigi konversacion",
"status.open": "Disfaldi statkonigon",
"status.pin": "Pingli al la profilo",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Dejar de fijar",
"column_subheading.navigation": "Navegación",
"column_subheading.settings": "Ajustes",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Tu cuenta no está bloqueada. Todos pueden seguirte para ver tus toots solo para seguidores.",
"compose_form.lock_disclaimer.lock": "bloqueado",
"compose_form.placeholder": "¿En qué estás pensando?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "usuario",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Un pequeño vistazo...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Este toot no puede retootearse",
"status.delete": "Borrar",
"status.embed": "Incrustado",
@@ -221,6 +223,7 @@
"status.media_hidden": "Contenido multimedia oculto",
"status.mention": "Mencionar",
"status.more": "Más",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir estado",
"status.pin": "Fijar",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "رهاکردن",
"column_subheading.navigation": "گشت و گذار",
"column_subheading.settings": "تنظیمات",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "حساب شما {locked} نیست. هر کسی می‌تواند پیگیر شما شود و نوشته‌های ویژهٔ پیگیران شما را ببیند.",
"compose_form.lock_disclaimer.lock": "قفل",
"compose_form.placeholder": "تازه چه خبر؟",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "کاربر",
"search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
"standalone.public_title": "نگاهی به کاربران این سرور...",
"status.block": "Block @{name}",
"status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید",
"status.delete": "پاک‌کردن",
"status.embed": "جاگذاری",
@@ -221,6 +223,7 @@
"status.media_hidden": "تصویر پنهان شده",
"status.mention": "نام‌بردن از @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "بی‌صداکردن گفتگو",
"status.open": "این نوشته را باز کن",
"status.pin": "نوشتهٔ ثابت نمایه",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "Mitä sinulla on mielessä?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Poista",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media hidden",
"status.mention": "Mainitse @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Retirer",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Paramètres",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Votre compte nest pas {locked}. Tout le monde peut vous suivre et voir vos pouets privés.",
"compose_form.lock_disclaimer.lock": "verrouillé",
"compose_form.placeholder": "Quavez-vous en tête?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "utilisateur⋅ice",
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
"standalone.public_title": "Jeter un coup dœil…",
"status.block": "Block @{name}",
"status.cannot_reblog": "Cette publication ne peut être boostée",
"status.delete": "Effacer",
"status.embed": "Intégrer",
@@ -221,6 +223,7 @@
"status.media_hidden": "Média caché",
"status.mention": "Mentionner",
"status.more": "Plus",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Masquer la conversation",
"status.open": "Déplier ce statut",
"status.pin": "Épingler sur le profil",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Soltar",
"column_subheading.navigation": "Navegación",
"column_subheading.settings": "Axustes",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "A súa conta non está {locked}. Calquera pode seguila para ver as súas mensaxes só-para-seguidoras.",
"compose_form.lock_disclaimer.lock": "bloqueado",
"compose_form.placeholder": "A qué andas?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "usuaria",
"search_results.total": "{count, number} {count,plural,one {result} outros {results}}",
"standalone.public_title": "Ollada dentro...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Esta mensaxe non pode ser promocionada",
"status.delete": "Eliminar",
"status.embed": "Incrustar",
@@ -221,6 +223,7 @@
"status.media_hidden": "Medios ocultos",
"status.mention": "Mencionar @{name}",
"status.more": "Máis",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Acalar conversa",
"status.open": "Expandir este estado",
"status.pin": "Fixar no perfil",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "שחרור קיבוע",
"column_subheading.navigation": "ניווט",
"column_subheading.settings": "אפשרויות",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.",
"compose_form.lock_disclaimer.lock": "נעול",
"compose_form.placeholder": "מה עובר לך בראש?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "משתמש(ת)",
"search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
"standalone.public_title": "הצצה פנימה...",
"status.block": "Block @{name}",
"status.cannot_reblog": "לא ניתן להדהד הודעה זו",
"status.delete": "מחיקה",
"status.embed": "הטמעה",
@@ -221,6 +223,7 @@
"status.media_hidden": "מדיה מוסתרת",
"status.mention": "פניה אל @{name}",
"status.more": "עוד",
"status.mute": "Mute @{name}",
"status.mute_conversation": "השתקת שיחה",
"status.open": "הרחבת הודעה",
"status.pin": "לקבע באודות",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigacija",
"column_subheading.settings": "Postavke",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Tvoj račun nije {locked}. Svatko te može slijediti kako bi vidio postove namijenjene samo tvojim sljedbenicima.",
"compose_form.lock_disclaimer.lock": "zaključan",
"compose_form.placeholder": "Što ti je na umu?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Ovaj post ne može biti boostan",
"status.delete": "Obriši",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Sakriven media sadržaj",
"status.mention": "Spomeni @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Utišaj razgovor",
"status.open": "Proširi ovaj status",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "Mire gondolsz?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Törlés",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media hidden",
"status.mention": "Említés",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigasi",
"column_subheading.settings": "Pengaturan",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
"compose_form.lock_disclaimer.lock": "dikunci",
"compose_form.placeholder": "Apa yang ada di pikiran anda?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Hapus",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media disembunyikan",
"status.mention": "Balasan @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Tampilkan status ini",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "Quo esas en tua spirito?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Efacar",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Kontenajo celita",
"status.mention": "Mencionar @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Detaligar ca mesajo",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "A cosa stai pensando?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Elimina",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Allegato nascosto",
"status.mention": "Nomina @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Espandi questo post",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "ピン留めを外す",
"column_subheading.navigation": "ナビゲーション",
"column_subheading.settings": "設定",
"compose_form.hashtag_warning": "このトゥートは未収載なのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。",
"compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
"compose_form.lock_disclaimer.lock": "非公開",
"compose_form.placeholder": "今なにしてる?",
@@ -91,7 +92,7 @@
"empty_column.hashtag": "このハッシュタグはまだ使われていません。",
"empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
"empty_column.home.public_timeline": "連合タイムライン",
"empty_column.list": "このリストにはまだなにもありません。",
"empty_column.list": "このリストにはまだなにもありません。このリストのメンバーが新しいトゥートをするとここに表示されます。",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう",
"follow_request.authorize": "許可",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "ユーザー",
"search_results.total": "{count, number}件の結果",
"standalone.public_title": "今こんな話をしています...",
"status.block": "@{name}をブロック",
"status.cannot_reblog": "この投稿はブーストできません",
"status.delete": "削除",
"status.embed": "埋め込み",
@@ -221,6 +223,7 @@
"status.media_hidden": "非表示のメディア",
"status.mention": "返信",
"status.more": "もっと見る",
"status.mute": "@{name}をミュート",
"status.mute_conversation": "会話をミュート",
"status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示",

View File

@@ -1,55 +1,56 @@
{
"account.block": "차단",
"account.block_domain": "{domain} 전체를 숨김",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.",
"account.edit_profile": "프로필 편집",
"account.follow": "팔로우",
"account.followers": "팔로워",
"account.follows": "팔로우",
"account.follows_you": "날 팔로우합니다",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.hide_reblogs": "@{name}의 부스트를 숨기기",
"account.media": "미디어",
"account.mention": "답장",
"account.moved_to": "{name} has moved to:",
"account.moved_to": "{name}는 계정을 이동했습니다:",
"account.mute": "뮤트",
"account.mute_notifications": "Mute notifications from @{name}",
"account.mute_notifications": "@{name}의 알림을 뮤트",
"account.posts": "포스트",
"account.report": "신고",
"account.requested": "승인 대기 중",
"account.share": "Share @{name}'s profile",
"account.show_reblogs": "Show boosts from @{name}",
"account.share": "@{name}의 프로파일 공유",
"account.show_reblogs": "@{name}의 부스트 보기",
"account.unblock": "차단 해제",
"account.unblock_domain": "{domain} 숨김 해제",
"account.unfollow": "팔로우 해제",
"account.unmute": "뮤트 해제",
"account.unmute_notifications": "Unmute notifications from @{name}",
"account.unmute_notifications": "@{name}의 알림 뮤트 해제",
"account.view_full_profile": "전체 프로필 보기",
"boost_modal.combo": "다음부터 {combo}를 누르면 이 과정을 건너뛸 수 있습니다.",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",
"bundle_column_error.retry": "다시 시도",
"bundle_column_error.title": "네트워크 에러",
"bundle_modal_error.close": "닫기",
"bundle_modal_error.message": "Something went wrong while loading this component.",
"bundle_modal_error.retry": "Try again",
"bundle_modal_error.retry": "다시 시도",
"column.blocks": "차단 중인 사용자",
"column.community": "로컬 타임라인",
"column.favourites": "즐겨찾기",
"column.follow_requests": "팔로우 요청",
"column.home": "홈",
"column.lists": "Lists",
"column.lists": "리스트",
"column.mutes": "뮤트 중인 사용자",
"column.notifications": "알림",
"column.pins": "고정된 툿",
"column.public": "연합 타임라인",
"column_back_button.label": "돌아가기",
"column_header.hide_settings": "Hide settings",
"column_header.moveLeft_settings": "Move column to the left",
"column_header.moveRight_settings": "Move column to the right",
"column_header.hide_settings": "설정 숨기기",
"column_header.moveLeft_settings": "왼쪽으로 이동",
"column_header.moveRight_settings": "오른쪽으로 이동",
"column_header.pin": "고정하기",
"column_header.show_settings": "Show settings",
"column_header.show_settings": "설정 보이기",
"column_header.unpin": "고정 해제",
"column_subheading.navigation": "내비게이션",
"column_subheading.settings": "설정",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "이 계정은 {locked}로 설정 되어 있지 않습니다. 누구나 이 계정을 팔로우 할 수 있으며, 팔로워 공개의 포스팅을 볼 수 있습니다.",
"compose_form.lock_disclaimer.lock": "비공개",
"compose_form.placeholder": "지금 무엇을 하고 있나요?",
@@ -63,35 +64,35 @@
"confirmations.block.message": "정말로 {name}를 차단하시겠습니까?",
"confirmations.delete.confirm": "삭제",
"confirmations.delete.message": "정말로 삭제하시겠습니까?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.confirm": "삭제",
"confirmations.delete_list.message": "정말로 이 리스트를 삭제하시겠습니까?",
"confirmations.domain_block.confirm": "도메인 전체를 숨김",
"confirmations.domain_block.message": "정말로 {domain} 전체를 숨기시겠습니까? 대부분의 경우 개별 차단이나 뮤트로 충분합니다.",
"confirmations.mute.confirm": "뮤트",
"confirmations.mute.message": "정말로 {name}를 뮤트하시겠습니까?",
"confirmations.unfollow.confirm": "Unfollow",
"confirmations.unfollow.message": "Are you sure you want to unfollow {name}?",
"confirmations.unfollow.confirm": "언팔로우",
"confirmations.unfollow.message": "정말로 {name}를 언팔로우하시겠습니까?",
"embed.instructions": "아래의 코드를 복사하여 대화를 원하는 곳으로 공유하세요.",
"embed.preview": "다음과 같이 표시됩니다:",
"emoji_button.activity": "활동",
"emoji_button.custom": "Custom",
"emoji_button.custom": "커스텀",
"emoji_button.flags": "국기",
"emoji_button.food": "음식",
"emoji_button.label": "emoji를 추가",
"emoji_button.nature": "자연",
"emoji_button.not_found": "No emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.not_found": "없어!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "물건",
"emoji_button.people": "사람들",
"emoji_button.recent": "Frequently used",
"emoji_button.recent": "자주 사용 됨",
"emoji_button.search": "검색...",
"emoji_button.search_results": "Search results",
"emoji_button.search_results": "검색 결과",
"emoji_button.symbols": "기호",
"emoji_button.travel": "여행과 장소",
"empty_column.community": "로컬 타임라인에 아무 것도 없습니다. 아무거나 적어 보세요!",
"empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
"empty_column.home": "아직 아무도 팔로우 하고 있지 않습니다. {public}를 보러 가거나, 검색하여 다른 사용자를 찾아 보세요.",
"empty_column.home.public_timeline": "연합 타임라인",
"empty_column.list": "There is nothing in this list yet.",
"empty_column.list": "리스트에 아직 아무 것도 없습니다.",
"empty_column.notifications": "아직 알림이 없습니다. 다른 사람과 대화를 시작해 보세요!",
"empty_column.public": "여기엔 아직 아무 것도 없습니다! 공개적으로 무언가 포스팅하거나, 다른 인스턴스 유저를 팔로우 해서 가득 채워보세요!",
"follow_request.authorize": "허가",
@@ -107,46 +108,46 @@
"home.column_settings.show_reblogs": "부스트 표시",
"home.column_settings.show_replies": "답글 표시",
"home.settings": "컬럼 설정",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"keyboard_shortcuts.back": "뒤로가기",
"keyboard_shortcuts.boost": "부스트",
"keyboard_shortcuts.column": "해당 열에 포커스",
"keyboard_shortcuts.compose": "작성창으로 포커스",
"keyboard_shortcuts.description": "설명",
"keyboard_shortcuts.down": "리스트에서 아래로 이동",
"keyboard_shortcuts.enter": "열기",
"keyboard_shortcuts.favourite": "관심글 지정",
"keyboard_shortcuts.heading": "키보드 단축키",
"keyboard_shortcuts.hotkey": "핫키",
"keyboard_shortcuts.legend": "이 도움말 표시",
"keyboard_shortcuts.mention": "멘션",
"keyboard_shortcuts.reply": "답장",
"keyboard_shortcuts.search": "검색창에 포커스",
"keyboard_shortcuts.toot": "새 툿 작성",
"keyboard_shortcuts.unfocus": "작성창에서 포커스 해제",
"keyboard_shortcuts.up": "리스트에서 위로 이동",
"lightbox.close": "닫기",
"lightbox.next": "Next",
"lightbox.previous": "Previous",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lightbox.next": "다음",
"lightbox.previous": "이전",
"lists.account.add": "리스트에 추가",
"lists.account.remove": "리스트에서 제거",
"lists.delete": "리스트 삭제",
"lists.edit": "리스트 편집",
"lists.new.create": "리스트 추가",
"lists.new.title_placeholder": "새 리스트의 이름",
"lists.search": "팔로우 중인 사람들 중에서 찾기",
"lists.subheading": "Your lists",
"loading_indicator.label": "불러오는 중...",
"media_gallery.toggle_visible": "표시 전환",
"missing_indicator.label": "찾을 수 없습니다",
"mute_modal.hide_notifications": "Hide notifications from this user?",
"mute_modal.hide_notifications": "이 사용자로부터의 알림을 뮤트하시겠습니까?",
"navigation_bar.blocks": "차단한 사용자",
"navigation_bar.community_timeline": "로컬 타임라인",
"navigation_bar.edit_profile": "프로필 편집",
"navigation_bar.favourites": "즐겨찾기",
"navigation_bar.follow_requests": "팔로우 요청",
"navigation_bar.info": "이 인스턴스에 대해서",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
"navigation_bar.lists": "Lists",
"navigation_bar.keyboard_shortcuts": "키보드 단축키",
"navigation_bar.lists": "리스트",
"navigation_bar.logout": "로그아웃",
"navigation_bar.mutes": "뮤트 중인 사용자",
"navigation_bar.pins": "고정된 툿",
@@ -162,8 +163,8 @@
"notifications.column_settings.favourite": "즐겨찾기",
"notifications.column_settings.follow": "새 팔로워",
"notifications.column_settings.mention": "답글",
"notifications.column_settings.push": "Push notifications",
"notifications.column_settings.push_meta": "This device",
"notifications.column_settings.push": "푸시 알림",
"notifications.column_settings.push_meta": "이 장치",
"notifications.column_settings.reblog": "부스트",
"notifications.column_settings.show": "컬럼에 표시",
"notifications.column_settings.sound": "효과음 재생",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "유저",
"search_results.total": "{count, number}건의 결과",
"standalone.public_title": "A look inside...",
"status.block": "@{name} 차단",
"status.cannot_reblog": "이 포스트는 부스트 할 수 없습니다",
"status.delete": "삭제",
"status.embed": "공유하기",
@@ -220,7 +222,8 @@
"status.load_more": "더 보기",
"status.media_hidden": "미디어 숨겨짐",
"status.mention": "답장",
"status.more": "More",
"status.more": "자세히",
"status.mute": "@{name} 뮤트",
"status.mute_conversation": "이 대화를 뮤트",
"status.open": "상세 정보 표시",
"status.pin": "고정",
@@ -231,7 +234,7 @@
"status.report": "신고",
"status.sensitive_toggle": "클릭해서 표시하기",
"status.sensitive_warning": "민감한 미디어",
"status.share": "Share",
"status.share": "공유",
"status.show_less": "숨기기",
"status.show_more": "더 보기",
"status.unmute_conversation": "이 대화의 뮤트 해제하기",
@@ -241,19 +244,19 @@
"tabs_bar.home": "홈",
"tabs_bar.local_timeline": "로컬",
"tabs_bar.notifications": "알림",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"ui.beforeunload": "지금 나가면 저장되지 않은 항목을 잃게 됩니다.",
"upload_area.title": "드래그 & 드롭으로 업로드",
"upload_button.label": "미디어 추가",
"upload_form.description": "Describe for the visually impaired",
"upload_form.undo": "재시도",
"upload_progress.label": "업로드 중...",
"video.close": "Close video",
"video.exit_fullscreen": "Exit full screen",
"video.expand": "Expand video",
"video.fullscreen": "Full screen",
"video.hide": "Hide video",
"video.mute": "Mute sound",
"video.pause": "Pause",
"video.play": "Play",
"video.unmute": "Unmute sound"
"video.close": "동영상 닫기",
"video.exit_fullscreen": "전체화면 나가기",
"video.expand": "동영상 확장",
"video.fullscreen": "전체화면",
"video.hide": "동영상 숨기기",
"video.mute": "음소거",
"video.pause": "일시정지",
"video.play": "재생",
"video.unmute": "음소거 해제"
}

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Losmaken",
"column_subheading.navigation": "Navigatie",
"column_subheading.settings": "Instellingen",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en toots zien die je alleen aan volgers hebt gericht.",
"compose_form.lock_disclaimer.lock": "besloten",
"compose_form.placeholder": "Wat wil je kwijt?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "gebruiker",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
"standalone.public_title": "Een kijkje binnenin...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Deze toot kan niet geboost worden",
"status.delete": "Verwijderen",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media verborgen",
"status.mention": "Vermeld @{name}",
"status.more": "Meer",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Negeer conversatie",
"status.open": "Toot volledig tonen",
"status.pin": "Aan profielpagina vastmaken",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Løsne",
"column_subheading.navigation": "Navigasjon",
"column_subheading.settings": "Innstillinger",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Din konto er ikke {locked}. Hvem som helst kan følge deg og se dine private poster.",
"compose_form.lock_disclaimer.lock": "låst",
"compose_form.placeholder": "Hva har du på hjertet?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Denne posten kan ikke fremheves",
"status.delete": "Slett",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media skjult",
"status.mention": "Nevn @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Demp samtale",
"status.open": "Utvid denne statusen",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Despenjar",
"column_subheading.navigation": "Navigacion",
"column_subheading.settings": "Paramètres",
"compose_form.hashtag_warning": "Aqueste tut serà pas ligat a cap etiqueta estant ques pas listat. Òm pas cercar que los tuts publics per etiqueta.",
"compose_form.lock_disclaimer": "Vòstre compte es pas {locked}. Tot lo mond pòt vos sègre e veire los estatuts reservats als seguidors.",
"compose_form.lock_disclaimer.lock": "clavat",
"compose_form.placeholder": "A de qué pensatz?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "utilizaire",
"search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
"standalone.public_title": "Una ulhada dedins…",
"status.block": "Blocar @{name}",
"status.cannot_reblog": "Aqueste estatut pòt pas èsser partejat",
"status.delete": "Escafar",
"status.embed": "Embarcar",
@@ -221,6 +223,7 @@
"status.media_hidden": "Mèdia rescondut",
"status.mention": "Mencionar",
"status.more": "Mai",
"status.mute": "Rescondre @{name}",
"status.mute_conversation": "Rescondre la conversacion",
"status.open": "Desplegar aqueste estatut",
"status.pin": "Penjar al perfil",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Cofnij przypięcie",
"column_subheading.navigation": "Nawigacja",
"column_subheading.settings": "Ustawienia",
"compose_form.hashtag_warning": "Ten wpis nie będzie widoczny pod podanymi hashtagami, ponieważ jest oznaczony jako niewidoczny. Tylko publiczne wpisy mogą zostać znalezione z użyciem hashtagów.",
"compose_form.lock_disclaimer": "Twoje konto nie jest {locked}. Każdy, kto Cię śledzi, może wyświetlać Twoje wpisy przeznaczone tylko dla śledzących.",
"compose_form.lock_disclaimer.lock": "zablokowane",
"compose_form.placeholder": "Co Ci chodzi po głowie?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "użytkownik",
"search_results.total": "{count, number} {count, plural, one {wynik} few {wyniki} many {wyników} more {wyników}}",
"standalone.public_title": "Spojrzenie w głąb…",
"status.block": "Zablokuj @{name}",
"status.cannot_reblog": "Ten wpis nie może zostać podbity",
"status.delete": "Usuń",
"status.embed": "Osadź",
@@ -221,6 +223,7 @@
"status.media_hidden": "Zawartość multimedialna ukryta",
"status.mention": "Wspomnij o @{name}",
"status.more": "Więcej",
"status.mute": "Wycisz @{name}",
"status.mute_conversation": "Wycisz konwersację",
"status.open": "Rozszerz ten wpis",
"status.pin": "Przypnij do profilu",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Desafixar",
"column_subheading.navigation": "Navegação",
"column_subheading.settings": "Configurações",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "A sua conta não está {locked}. Qualquer pessoa pode te seguir e visualizar postagens direcionadas a apenas seguidores.",
"compose_form.lock_disclaimer.lock": "trancada",
"compose_form.placeholder": "No que você está pensando?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "usuário",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Dê uma espiada...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Esta postagem não pode ser compartilhada",
"status.delete": "Excluir",
"status.embed": "Incorporar",
@@ -221,6 +223,7 @@
"status.media_hidden": "Mídia escondida",
"status.mention": "Mencionar @{name}",
"status.more": "Mais",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir",
"status.pin": "Fixar no perfil",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Remover fixar",
"column_subheading.navigation": "Navegação",
"column_subheading.settings": "Preferências",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "A tua conta não está {locked}. Qualquer pessoa pode seguir-te e ver as publicações direcionadas apenas a seguidores.",
"compose_form.lock_disclaimer.lock": "bloqueada",
"compose_form.placeholder": "Em que estás a pensar?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "utilizador",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
"standalone.public_title": "Espreitar lá dentro...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Este post não pode ser partilhado",
"status.delete": "Eliminar",
"status.embed": "Incorporar",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media escondida",
"status.mention": "Mencionar @{name}",
"status.more": "Mais",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Открепить",
"column_subheading.navigation": "Навигация",
"column_subheading.settings": "Настройки",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Ваш аккаунт не {locked}. Любой человек может подписаться на Вас и просматривать посты для подписчиков.",
"compose_form.lock_disclaimer.lock": "закрыт",
"compose_form.placeholder": "О чем Вы думаете?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "пользователь",
"search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
"standalone.public_title": "Прямо сейчас",
"status.block": "Block @{name}",
"status.cannot_reblog": "Этот статус не может быть продвинут",
"status.delete": "Удалить",
"status.embed": "Встроить",
@@ -221,6 +223,7 @@
"status.media_hidden": "Медиаконтент скрыт",
"status.mention": "Упомянуть @{name}",
"status.more": "Больше",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Заглушить тред",
"status.open": "Развернуть статус",
"status.pin": "Закрепить в профиле",

View File

@@ -0,0 +1,262 @@
{
"account.block": "Blokovať @{name}",
"account.block_domain": "Blokovať všetko z {domain}",
"account.disclaimer_full": "Inofrmácie nižšie nemusia reflektovať použivateľský účet kompletne.",
"account.edit_profile": "Upraviť profil",
"account.follow": "Sledovať",
"account.followers": "Sledujúci",
"account.follows": "Sledovaní",
"account.follows_you": "Sleduje teba",
"account.hide_reblogs": "Hide boosts from @{name}",
"account.media": "Média",
"account.mention": "Napísať @{name}",
"account.moved_to": "{name} has moved to:",
"account.mute": "Ignorovať @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.posts": "Správ",
"account.report": "Nahlásiť @{name}",
"account.requested": "Čaká na schválenie. Klikni na zrušenie žiadosti",
"account.share": "Zdieľať @{name} profil",
"account.show_reblogs": "Show boosts from @{name}",
"account.unblock": "Odblokovať @{name}",
"account.unblock_domain": "Prestať blokovať {domain}",
"account.unfollow": "Prestať nasledovať",
"account.unmute": "Prestať ignorovať @{name}",
"account.unmute_notifications": "Unmute notifications from @{name}",
"account.view_full_profile": "Pozri celý profil",
"boost_modal.combo": "Nabudúce môžeš kliknúť {combo} a preskočiť",
"bundle_column_error.body": "Nastala chyba pri načítaní tohto komponentu.",
"bundle_column_error.retry": "Skús znova",
"bundle_column_error.title": "Chyba siete",
"bundle_modal_error.close": "Zatvoriť",
"bundle_modal_error.message": "Nastala chyba pri načítaní tohto komponentu.",
"bundle_modal_error.retry": "Skúsiť znova",
"column.blocks": "Blokovaní používatelia",
"column.community": "Lokálna časová os",
"column.favourites": "Obľúbené",
"column.follow_requests": "Žiadosti",
"column.home": "Moja časová os",
"column.lists": "Lists",
"column.mutes": "Ignorovaní používatelia",
"column.notifications": "Notifikácie",
"column.pins": "Pripnuté toots",
"column.public": "Federovaná časová os",
"column_back_button.label": "Späť",
"column_header.hide_settings": "Skryť nastavenia",
"column_header.moveLeft_settings": "Presunúť stĺpec doľava",
"column_header.moveRight_settings": "Presunúť stĺpec doprava",
"column_header.pin": "Pripnúť",
"column_header.show_settings": "Ukázať nastavenia",
"column_header.unpin": "Odopnúť",
"column_subheading.navigation": "Navigácia",
"column_subheading.settings": "Nastavenia",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Tvoj účet nie je zamknutý. Ktokoľvek ťa môže nasledovať a vidieť tvoje správy pre sledujúcich.",
"compose_form.lock_disclaimer.lock": "zamknutý",
"compose_form.placeholder": "Čo máš na mysli?",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Označ súbor ako chúlostivý",
"compose_form.spoiler": "Skryť text za varovanie",
"compose_form.spoiler_placeholder": "Napíš sem tvoje varovanie",
"confirmation_modal.cancel": "Zrušiť",
"confirmations.block.confirm": "Blokovať",
"confirmations.block.message": "Naozaj chceš blokovať {name}?",
"confirmations.delete.confirm": "Zmazať",
"confirmations.delete.message": "Naozaj chceš zmazať túto správu?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Skryť celú doménu",
"confirmations.domain_block.message": "Si si naozaj istý, že chceš blokovať celú {domain}? Vo väčšine prípadov stačí blokovať alebo ignorovať daných používateľov.",
"confirmations.mute.confirm": "Ignoruj",
"confirmations.mute.message": "Naozaj chceš ignorovať {name}?",
"confirmations.unfollow.confirm": "Nesledovať",
"confirmations.unfollow.message": "Naozaj chceš prestať sledovať {name}?",
"embed.instructions": "Skopíruj kód nižšie a ridaj tento status na tvoju web stránku.",
"embed.preview": "Tu je ukážka ako to bude vyzerať:",
"emoji_button.activity": "Aktivity",
"emoji_button.custom": "Vlastné",
"emoji_button.flags": "Vlajky",
"emoji_button.food": "Jedlá a nápoje",
"emoji_button.label": "Vlož emoji",
"emoji_button.nature": "Zvieratká",
"emoji_button.not_found": "Nenájdené",
"emoji_button.objects": "Predmety",
"emoji_button.people": "Ľudia",
"emoji_button.recent": "Často používané",
"emoji_button.search": "Hľadaj...",
"emoji_button.search_results": "Nájdené",
"emoji_button.symbols": "Symboly",
"emoji_button.travel": "Cestovanie a miesta",
"empty_column.community": "Lokálna časová os je prázdna. Napíš niečo aby sa to začalo hýbať!",
"empty_column.hashtag": "Ešte nič nie je v tomto hashtag-u.",
"empty_column.home": "Ešte nesleduješ nikoho. Pre začiatok pozri {public} alebo použi vyhľadávanie aby si našiel ostatných používateľov.",
"empty_column.home.public_timeline": "verejnú časovú os",
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
"empty_column.notifications": "Nemáš žiadne notifikácie. Napíš niekomu, nasleduj niekoho alebo komunikuj s ostatnými.",
"empty_column.public": "Ešte tu nič nie je. Napíš niečo verejne alebo začni sledovať používateľov z iných Mastodon serverov aby tu niečo bolo",
"follow_request.authorize": "Potvrdiť",
"follow_request.reject": "Odmietnúť",
"getting_started.appsshort": "Aplikácie",
"getting_started.faq": "FAQ",
"getting_started.heading": "Začíname",
"getting_started.open_source_notice": "Mastodon má otvorený kód. Reportovať chyby alebo prispievať vlastným kódom môžeš na GitHube v {github}.",
"getting_started.userguide": "Používateľská príručka",
"home.column_settings.advanced": "Rozšírené",
"home.column_settings.basic": "Základné",
"home.column_settings.filter_regex": "Filtrovať použitím regulárnych výrazov",
"home.column_settings.show_reblogs": "Zobraziť boosts",
"home.column_settings.show_replies": "Zobraziť odpovede",
"home.settings": "Nastavenia stĺpcov",
"keyboard_shortcuts.back": "to navigate back",
"keyboard_shortcuts.boost": "to boost",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.down": "to move down in the list",
"keyboard_shortcuts.enter": "to open status",
"keyboard_shortcuts.favourite": "to favourite",
"keyboard_shortcuts.heading": "Keyboard Shortcuts",
"keyboard_shortcuts.hotkey": "Hotkey",
"keyboard_shortcuts.legend": "to display this legend",
"keyboard_shortcuts.mention": "to mention author",
"keyboard_shortcuts.reply": "to reply",
"keyboard_shortcuts.search": "to focus search",
"keyboard_shortcuts.toot": "to start a brand new toot",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list",
"lightbox.close": "Zavrieť",
"lightbox.next": "Ďalší",
"lightbox.previous": "Predchádzajúci",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"loading_indicator.label": "Nahrávam...",
"media_gallery.toggle_visible": "Zapnúť/Vypnúť viditeľnosť",
"missing_indicator.label": "Nenájdené",
"mute_modal.hide_notifications": "Hide notifications from this user?",
"navigation_bar.blocks": "Blokovaní používatelia",
"navigation_bar.community_timeline": "Lokálna časová os",
"navigation_bar.edit_profile": "Upraviť profil",
"navigation_bar.favourites": "Obľúbené",
"navigation_bar.follow_requests": "Žiadosti",
"navigation_bar.info": "O tomto Mastodon serveri",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Odhlásiť",
"navigation_bar.mutes": "Ignorovaní používatelia",
"navigation_bar.pins": "Pripnuté toots",
"navigation_bar.preferences": "Možnosti",
"navigation_bar.public_timeline": "Federovaná časová os",
"notification.favourite": "{name} sa páči tvoj status",
"notification.follow": "{name} ťa začal(a) sledovať",
"notification.mention": "{name} ťa zmienil",
"notification.reblog": "{name} re-tootol tvoj status",
"notifications.clear": "Vymazať notifikácie",
"notifications.clear_confirmation": "Naozaj chceš vymazať všetky tvoje notifikácie?",
"notifications.column_settings.alert": "Bublinové notifikácie",
"notifications.column_settings.favourite": "Obľúbené:",
"notifications.column_settings.follow": "Nový nasledujúci:",
"notifications.column_settings.mention": "Zmienenia:",
"notifications.column_settings.push": "Push notifikácie",
"notifications.column_settings.push_meta": "Toto zariadenie",
"notifications.column_settings.reblog": "Re-toots:",
"notifications.column_settings.show": "Zobraziť v stĺpci",
"notifications.column_settings.sound": "Prehrať zvuk",
"onboarding.done": "Koniec",
"onboarding.next": "Ďalej",
"onboarding.page_five.public_timelines": "Lokálna časová os zobrazuje verejné správy od všetkých na {domain}. Federovaná časová os zobrazuje verejné správy od všetkých ľudí ktoré {domain} nasleduje. Tieto sú takzvané Verejné Časové Osi, výborná možnosť ako nájsť a spoznať nových ľudí.",
"onboarding.page_four.home": "Domovská časová os zobrazí správy od ľudí ktorých sleduješ.",
"onboarding.page_four.notifications": "Stĺpec s notifikáciami zobrazí keď budeš s niekým komunikovať.",
"onboarding.page_one.federation": "Mastodon je sieť nezávislých serverov spojením ktorých vzniká jedna veľká federovaná sociálna sieť.",
"onboarding.page_one.handle": "Ty si na {domain}, takže tvoje celý nickname je {handle}",
"onboarding.page_one.welcome": "Vitajte v Mastodon!",
"onboarding.page_six.admin": "Správca tohto servera je {admin}.",
"onboarding.page_six.almost_done": "Takmer hotovo...",
"onboarding.page_six.appetoot": "Bon Appetoot!",
"onboarding.page_six.apps_available": "Aplikácie {apps} sú dostupné na pre iOS, Android and ďalšie platformy.",
"onboarding.page_six.github": "Mastodon je free open-source software. Chyby, nové funkcie alebo prispievať svojím kódom mǒžeš na {github}.",
"onboarding.page_six.guidelines": "pravidlá komunity",
"onboarding.page_six.read_guidelines": "Prosím prečítajte si {domain} pravidlá {guidelines}!",
"onboarding.page_six.various_app": "mobilné applikácie",
"onboarding.page_three.profile": "Uprav svoj profile a zmeň svoj avatar, bio a meno ktoré bude zobrazené. V nastaveniach nájdeš ďalšie možnosti.",
"onboarding.page_three.search": "Použi vyhľadávacie políčko na nájdenie ľudí a hashtagov, ako napríklad {slovensko}, {slovakia} alebo {pivo}. Na nájdenie človeka ktorý je registrovaný na inom Mastodon serveri použi jeho celý nickname.",
"onboarding.page_two.compose": "Správy píš zo stĺpca na komponovanie. Môžeš nahrávať obrázky, meniť nastavenia súkromia správ a pridávať varovania ikonkami nižšie.",
"onboarding.skip": "Preskočiť",
"privacy.change": "Zmeň viditeľnosť statusu",
"privacy.direct.long": "Pošli priamo iba spomenutým používateľom",
"privacy.direct.short": "Súkromne",
"privacy.private.long": "Pošli iba sledujúcim",
"privacy.private.short": "Iba sledujúci",
"privacy.public.long": "Pošli všetkým",
"privacy.public.short": "Verejne",
"privacy.unlisted.long": "Neposielať verejne",
"privacy.unlisted.short": "Nie je v zozname",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "now",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Zrušiť",
"report.placeholder": "Ďalšie komentáre",
"report.submit": "Poslať",
"report.target": "Reportovať {target}",
"search.placeholder": "Hľadaj",
"search_popout.search_format": "Advanced search format",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
"search_popout.tips.user": "user",
"search_results.total": "{count, number} nájdených",
"standalone.public_title": "Čo tam nájdeš...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Tento príspevok nemôže byť re-tootnutý",
"status.delete": "Zmazať",
"status.embed": "Embed",
"status.favourite": "Páči sa mi",
"status.load_more": "Zobraziť viac",
"status.media_hidden": "Skryté médiá",
"status.mention": "Napísať @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Ignorovať konverzáciu",
"status.open": "Otvoriť",
"status.pin": "Pripnúť na profil",
"status.reblog": "Re-toot",
"status.reblogged_by": "{name} re-tootol",
"status.reply": "Odpovedať",
"status.replyAll": "Odpovedať všetkým",
"status.report": "Nahlásiť @{name}",
"status.sensitive_toggle": "Klikni pre zobrazenie",
"status.sensitive_warning": "Chúlostivý obsah",
"status.share": "Zdieľať",
"status.show_less": "Zobraziť menej",
"status.show_more": "Zobraziť viac",
"status.unmute_conversation": "Prestať ignorovať konverzáciu",
"status.unpin": "Odopnúť z profilu",
"tabs_bar.compose": "Napísať",
"tabs_bar.federated_timeline": "Federovaná",
"tabs_bar.home": "Domov",
"tabs_bar.local_timeline": "Local",
"tabs_bar.notifications": "Notifikácie",
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
"upload_area.title": "Ťahaj a pusti pre nahratie",
"upload_button.label": "Pridať",
"upload_form.description": "Describe for the visually impaired",
"upload_form.undo": "Späť",
"upload_progress.label": "Nahrávam...",
"video.close": "Zavrieť video",
"video.exit_fullscreen": "Vpnúť zobrazenie na celú obrazovku",
"video.expand": "Zväčšiť video",
"video.fullscreen": "Zapnúť zobrazenie na celú obrazovku",
"video.hide": "Skryť video",
"video.mute": "Vypnúť zvuk",
"video.pause": "Pauza",
"video.play": "Prehrať",
"video.unmute": "Zapnúť zvuk"
}

View File

@@ -0,0 +1,259 @@
{
"account.block": "Blokiraj korisnika @{name}",
"account.block_domain": "Sakrij sve sa domena {domain}",
"account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.",
"account.edit_profile": "Izmeni profil",
"account.follow": "Zaprati",
"account.followers": "Pratioca",
"account.follows": "Prati",
"account.follows_you": "Prati Vas",
"account.hide_reblogs": "Sakrij podrške koje daje korisnika @{name}",
"account.media": "Mediji",
"account.mention": "Pomeni korisnika @{name}",
"account.moved_to": "{name} se pomerio na:",
"account.mute": "Mutiraj @{name}",
"account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
"account.posts": "Statusa",
"account.report": "Prijavi @{name}",
"account.requested": "Čekam odobrenje. Kliknite da poništite zahtev za praćenje",
"account.share": "Podeli profil korisnika @{name}",
"account.show_reblogs": "Prikaži podrške od korisnika @{name}",
"account.unblock": "Odblokiraj korisnika @{name}",
"account.unblock_domain": "Odblokiraj domen {domain}",
"account.unfollow": "Otprati",
"account.unmute": "Odmutiraj @{name}",
"account.unmute_notifications": "Uključi nazad obaveštenja od korisnika @{name}",
"account.view_full_profile": "Vidi ceo profil",
"boost_modal.combo": "Možete pritisnuti {combo} da preskočite ovo sledeći put",
"bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.",
"bundle_column_error.retry": "Pokušajte ponovo",
"bundle_column_error.title": "Mrežna greška",
"bundle_modal_error.close": "Zatvori",
"bundle_modal_error.message": "Nešto nije bilo u redu pri učitavanju ove komponente.",
"bundle_modal_error.retry": "Pokušajte ponovo",
"column.blocks": "Blokirani korisnici",
"column.community": "Lokalna lajna",
"column.favourites": "Omiljeni",
"column.follow_requests": "Zahtevi za praćenje",
"column.home": "Početna",
"column.lists": "Liste",
"column.mutes": "Mutirani korisnici",
"column.notifications": "Obaveštenja",
"column.pins": "Prikačeni tutovi",
"column.public": "Združena lajna",
"column_back_button.label": "Nazad",
"column_header.hide_settings": "Sakrij postavke",
"column_header.moveLeft_settings": "Pomeri kolonu ulevo",
"column_header.moveRight_settings": "Pomeri kolonu udesno",
"column_header.pin": "Prikači",
"column_header.show_settings": "Prikaži postavke",
"column_header.unpin": "Otkači",
"column_subheading.navigation": "Navigacija",
"column_subheading.settings": "Postavke",
"compose_form.lock_disclaimer": "Vaš nalog nije {locked}. Svako može da Vas zaprati i da vidi objave namenjene samo Vašim pratiocima.",
"compose_form.lock_disclaimer.lock": "zaključan",
"compose_form.placeholder": "Šta Vam je na umu?",
"compose_form.publish": "Tutni",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Obeleži multimediju kao osetljivu",
"compose_form.spoiler": "Sakrij tekst ispod upozorenja",
"compose_form.spoiler_placeholder": "Ovde upišite upozorenje",
"confirmation_modal.cancel": "Poništi",
"confirmations.block.confirm": "Blokiraj",
"confirmations.block.message": "Da li ste sigurni da želite da blokirate korisnika {name}?",
"confirmations.delete.confirm": "Obriši",
"confirmations.delete.message": "Da li ste sigurni da želite obrišete ovaj status?",
"confirmations.delete_list.confirm": "Obriši",
"confirmations.delete_list.message": "Da li ste sigurni da želite da bespovratno obrišete ovu listu?",
"confirmations.domain_block.confirm": "Sakrij ceo domen",
"confirmations.domain_block.message": "Da li ste stvarno, stvarno sigurno da želite da blokirate ceo domen {domain}? U većini slučajeva, par dobrih blokiranja ili mutiranja su dovoljna i preporučljiva.",
"confirmations.mute.confirm": "Mutiraj",
"confirmations.mute.message": "Da li stvarno želite da mutirate korisnika {name}?",
"confirmations.unfollow.confirm": "Otprati",
"confirmations.unfollow.message": "Da li ste sigurni da želite da otpratite korisnika {name}?",
"embed.instructions": "Ugradi ovaj status na Vaš veb sajt kopiranjem koda ispod.",
"embed.preview": "Ovako će da izgleda:",
"emoji_button.activity": "Aktivnost",
"emoji_button.custom": "Proizvoljno",
"emoji_button.flags": "Zastave",
"emoji_button.food": "Hrana & piće",
"emoji_button.label": "Ubaci smajli",
"emoji_button.nature": "Priroda",
"emoji_button.not_found": "Nema smajlija!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Objekti",
"emoji_button.people": "Ljudi",
"emoji_button.recent": "Najčešće korišćeni",
"emoji_button.search": "Pretraga...",
"emoji_button.search_results": "Rezultati pretrage",
"emoji_button.symbols": "Simboli",
"emoji_button.travel": "Putovanja & mesta",
"empty_column.community": "Lokalna lajna je prazna. Napišite nešto javno da lajna produva!",
"empty_column.hashtag": "Trenutno nema ništa na ovom heštegu.",
"empty_column.home": "Vaša lajna je prazna! Posetite {public} ili koristite pretragu da počnete i upoznajete nove ljude.",
"empty_column.home.public_timeline": "javna lajna",
"empty_column.list": "U ovoj listi još nema ničega. Kada članovi liste objave nove statuse, oni će se pojavljivati ovde.",
"empty_column.notifications": "Trenutno nemate obaveštenja. Družite se malo da započnete razgovore.",
"empty_column.public": "Ovde nema ničega! Napišite nešto javno, ili nađite korisnike sa drugih instanci koje ćete zapratiti da popunite ovu prazninu",
"follow_request.authorize": "Odobri",
"follow_request.reject": "Odbij",
"getting_started.appsshort": "Aplikacije",
"getting_started.faq": "ČPP",
"getting_started.heading": "Da počnete",
"getting_started.open_source_notice": "Mastodont je softver otvorenog koda. Možete mu doprineti ili prijaviti probleme preko GitHub-a na {github}.",
"getting_started.userguide": "Korisničko uputstvo",
"home.column_settings.advanced": "Napredno",
"home.column_settings.basic": "Osnovno",
"home.column_settings.filter_regex": "Filtriraj regularnim izrazima",
"home.column_settings.show_reblogs": "Prikaži i podržavanja",
"home.column_settings.show_replies": "Prikaži odgovore",
"home.settings": "Postavke kolone",
"keyboard_shortcuts.back": "da odete nazad",
"keyboard_shortcuts.boost": "da podržite",
"keyboard_shortcuts.column": "da se prebacite na status u jednoj od kolona",
"keyboard_shortcuts.compose": "da se prebacite na pisanje novog tuta",
"keyboard_shortcuts.description": "Opis",
"keyboard_shortcuts.down": "da se pomerite na dole u listi",
"keyboard_shortcuts.enter": "da otvorite status",
"keyboard_shortcuts.favourite": "da označite kao omiljeno",
"keyboard_shortcuts.heading": "Prečice na tastaturi",
"keyboard_shortcuts.hotkey": "Prečica",
"keyboard_shortcuts.legend": "da prikažete ovaj podsetnik",
"keyboard_shortcuts.mention": "da pomenete autora",
"keyboard_shortcuts.reply": "da odgovorite",
"keyboard_shortcuts.search": "da se prebacite na pretragu",
"keyboard_shortcuts.toot": "da započnete skroz novi tut",
"keyboard_shortcuts.unfocus": "da ne budete više na pretrazi/pravljenju novog tuta",
"keyboard_shortcuts.up": "da se pomerite na gore u listi",
"lightbox.close": "Zatvori",
"lightbox.next": "Sledeći",
"lightbox.previous": "Prethodni",
"lists.account.add": "Dodaj na listu",
"lists.account.remove": "Ukloni sa liste",
"lists.delete": "Obriši listu",
"lists.edit": "Izmeni listu",
"lists.new.create": "Dodaj listu",
"lists.new.title_placeholder": "Naslov nove liste",
"lists.search": "Pretraži među ljudima koje pratite",
"lists.subheading": "Vaše liste",
"loading_indicator.label": "Učitavam...",
"media_gallery.toggle_visible": "Uključi/isključi vidljivost",
"missing_indicator.label": "Nije pronađeno",
"mute_modal.hide_notifications": "Sakrij obaveštenja od ovog korisnika?",
"navigation_bar.blocks": "Blokirani korisnici",
"navigation_bar.community_timeline": "Lokalna lajna",
"navigation_bar.edit_profile": "Izmeni profil",
"navigation_bar.favourites": "Omiljeni",
"navigation_bar.follow_requests": "Zahtevi za praćenje",
"navigation_bar.info": "O ovoj instanci",
"navigation_bar.keyboard_shortcuts": "Prečice na tastaturi",
"navigation_bar.lists": "Liste",
"navigation_bar.logout": "Odjava",
"navigation_bar.mutes": "Mutirani korisnici",
"navigation_bar.pins": "Prikačeni tutovi",
"navigation_bar.preferences": "Podešavanja",
"navigation_bar.public_timeline": "Združena lajna",
"notification.favourite": "{name} je stavio Vaš status kao omiljeni",
"notification.follow": "{name} Vas je zapratio",
"notification.mention": "{name} Vas je pomenuo",
"notification.reblog": "{name} je podržao(la) Vaš status",
"notifications.clear": "Očisti obaveštenja",
"notifications.clear_confirmation": "Da li ste sigurno da trajno želite da očistite Vaša obaveštenja?",
"notifications.column_settings.alert": "Obaveštenja na radnoj površini",
"notifications.column_settings.favourite": "Omiljeni:",
"notifications.column_settings.follow": "Novi pratioci:",
"notifications.column_settings.mention": "Pominjanja:",
"notifications.column_settings.push": "Guraj obaveštenja",
"notifications.column_settings.push_meta": "Ovaj uređaj",
"notifications.column_settings.reblog": "Podrški:",
"notifications.column_settings.show": "Prikaži u koloni",
"notifications.column_settings.sound": "Puštaj zvuk",
"onboarding.done": "Gotovo",
"onboarding.next": "Sledeće",
"onboarding.page_five.public_timelines": "Lokalna lajna prikazuje sve javne statuse od svih na domenu {domain}. Združena lajna prikazuje javne statuse od svih ljudi koje prate korisnici sa domena {domain}. Ovo su javne lajne, sjajan način da otkrijete nove ljude.",
"onboarding.page_four.home": "Početna lajna prikazuje statuse ljudi koje Vi pratite.",
"onboarding.page_four.notifications": "Kolona sa obaveštenjima Vam prikazuje kada neko priča sa Vama.",
"onboarding.page_one.federation": "Mastodont je mreža nezavisnih servera koji se uvezuju da naprave jednu veću društvenu mrežu. Ove servere zovemo instancama.",
"onboarding.page_one.handle": "Vi ste na domenu {domain}, pa je Vaša puna identifikacija {handle}",
"onboarding.page_one.welcome": "Dobrodošli na Mastodont!",
"onboarding.page_six.admin": "Administrator Vaše instance je {admin}.",
"onboarding.page_six.almost_done": "Još malo, pa gotovo...",
"onboarding.page_six.appetoot": "Prijatutno!",
"onboarding.page_six.apps_available": "Postoje {apps} dostupne za iOS, Android i druge platforme.",
"onboarding.page_six.github": "Mastodont je slobodan softver otvorenog koda. Možete prijavljivati greške, potraživati nove funckionalnosti, ili učestvujući u programiranju. Naš izvorni kod je ovde: {github}.",
"onboarding.page_six.guidelines": "smernice zajednice",
"onboarding.page_six.read_guidelines": "Pročitejte {guidelines} domena {domain}!",
"onboarding.page_six.various_app": "mobilne aplikacije",
"onboarding.page_three.profile": "Izmenite profil da promenite avatar, biografiju i ime za prikaz. Tamo ćete naći i ostala podešavanja.",
"onboarding.page_three.search": "Korisite pretragu da nađete ljude i gledate heštegove, kao što su {illustration} i {introductions}. Da nađete osobu koja nije na ovoj instanci, koristite njenu punu identifikaciju.",
"onboarding.page_two.compose": "Pišite statuse iz prve kolone. Možete otpremati slike, menjati podešavanja privatnosti, i dodavati upozorenja za osetljiv sadržaj preko ikonica ispod.",
"onboarding.skip": "Preskoči",
"privacy.change": "Podesi status privatnosti",
"privacy.direct.long": "Objavi samo korisnicima koji su pomenuti",
"privacy.direct.short": "Direktno",
"privacy.private.long": "Objavi samo pratiocima",
"privacy.private.short": "Samo za pratioce",
"privacy.public.long": "Objavi na javnoj lajni",
"privacy.public.short": "Javno",
"privacy.unlisted.long": "Ne objavljuj na javnim lajnama",
"privacy.unlisted.short": "Neizlistano",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "sada",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Poništi",
"report.placeholder": "Dodatni komentari",
"report.submit": "Pošalji",
"report.target": "Prijavljujem {target}",
"search.placeholder": "Pretraga",
"search_popout.search_format": "Napredni format pretrage",
"search_popout.tips.hashtag": "hešteg",
"search_popout.tips.status": "status",
"search_popout.tips.text": "Traženjem običnog teksta ćete dobiti sva pronađena imena, sva korisnička imena i sve nađene heštegove",
"search_popout.tips.user": "korisnik",
"search_results.total": "{count, number} {count, plural, one {rezultat} few {rezultata} other {rezultata}}",
"standalone.public_title": "Pogled iznutra...",
"status.cannot_reblog": "Ovaj status ne može da se podrži",
"status.delete": "Obriši",
"status.embed": "Ugradi na sajt",
"status.favourite": "Omiljeno",
"status.load_more": "Učitaj još",
"status.media_hidden": "Multimedija sakrivena",
"status.mention": "Pomeni korisnika @{name}",
"status.more": "Još",
"status.mute_conversation": "Mutiraj prepisku",
"status.open": "Proširi ovaj status",
"status.pin": "Prikači na profil",
"status.reblog": "Podrži",
"status.reblogged_by": "{name} podržao(la)",
"status.reply": "Odgovori",
"status.replyAll": "Odgovori na diskusiju",
"status.report": "Prijavi korisnika @{name}",
"status.sensitive_toggle": "Kliknite da vidite",
"status.sensitive_warning": "Osetljiv sadržaj",
"status.share": "Podeli",
"status.show_less": "Prikaži manje",
"status.show_more": "Prikaži više",
"status.unmute_conversation": "Uključi prepisku",
"status.unpin": "Otkači sa profila",
"tabs_bar.compose": "Napiši",
"tabs_bar.federated_timeline": "Združeno",
"tabs_bar.home": "Početna",
"tabs_bar.local_timeline": "Lokalno",
"tabs_bar.notifications": "Obaveštenja",
"ui.beforeunload": "Ako napustite Mastodont, izgubićete napisani nacrt.",
"upload_area.title": "Prevucite ovde da otpremite",
"upload_button.label": "Dodaj multimediju",
"upload_form.description": "Opiši za slabovide osobe",
"upload_form.undo": "Opozovi",
"upload_progress.label": "Otpremam...",
"video.close": "Zatvori video",
"video.exit_fullscreen": "Napusti ceo ekran",
"video.expand": "Proširi video",
"video.fullscreen": "Ceo ekran",
"video.hide": "Sakrij video",
"video.mute": "Ugasi zvuk",
"video.pause": "Pauziraj",
"video.play": "Pusti",
"video.unmute": "Vrati zvuk"
}

View File

@@ -0,0 +1,262 @@
{
"account.block": "Блокирај корисника @{name}",
"account.block_domain": "Сакриј све са домена {domain}",
"account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.",
"account.edit_profile": "Измени профил",
"account.follow": "Запрати",
"account.followers": "Пратиоца",
"account.follows": "Прати",
"account.follows_you": "Прати Вас",
"account.hide_reblogs": "Сакриј подршке које даје корисника @{name}",
"account.media": "Медији",
"account.mention": "Помени корисника @{name}",
"account.moved_to": "{name} се померио на:",
"account.mute": "Мутирај @{name}",
"account.mute_notifications": "Искључи обавештења од корисника @{name}",
"account.posts": "Статуса",
"account.report": "Пријави @{name}",
"account.requested": "Чекам одобрење. Кликните да поништите захтев за праћење",
"account.share": "Подели профил корисника @{name}",
"account.show_reblogs": "Прикажи подршке од корисника @{name}",
"account.unblock": "Одблокирај корисника @{name}",
"account.unblock_domain": "Одблокирај домен {domain}",
"account.unfollow": "Отпрати",
"account.unmute": "Одмутирај @{name}",
"account.unmute_notifications": "Укључи назад обавештења од корисника @{name}",
"account.view_full_profile": "Види цео профил",
"boost_modal.combo": "Можете притиснути {combo} да прескочите ово следећи пут",
"bundle_column_error.body": "Нешто је пошло по злу приликом учитавања ове компоненте.",
"bundle_column_error.retry": "Покушајте поново",
"bundle_column_error.title": "Мрежна грешка",
"bundle_modal_error.close": "Затвори",
"bundle_modal_error.message": "Нешто није било у реду при учитавању ове компоненте.",
"bundle_modal_error.retry": "Покушајте поново",
"column.blocks": "Блокирани корисници",
"column.community": "Локална лајна",
"column.favourites": "Омиљени",
"column.follow_requests": "Захтеви за праћење",
"column.home": "Почетна",
"column.lists": "Листе",
"column.mutes": "Мутирани корисници",
"column.notifications": "Обавештења",
"column.pins": "Прикачени тутови",
"column.public": "Здружена лајна",
"column_back_button.label": "Назад",
"column_header.hide_settings": "Сакриј поставке",
"column_header.moveLeft_settings": "Помери колону улево",
"column_header.moveRight_settings": "Помери колону удесно",
"column_header.pin": "Прикачи",
"column_header.show_settings": "Прикажи поставке",
"column_header.unpin": "Откачи",
"column_subheading.navigation": "Навигација",
"column_subheading.settings": "Поставке",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Ваш налог није {locked}. Свако може да Вас запрати и да види објаве намењене само Вашим пратиоцима.",
"compose_form.lock_disclaimer.lock": "закључан",
"compose_form.placeholder": "Шта Вам је на уму?",
"compose_form.publish": "Тутни",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive": "Обележи мултимедију као осетљиву",
"compose_form.spoiler": "Сакриј текст испод упозорења",
"compose_form.spoiler_placeholder": "Овде упишите упозорење",
"confirmation_modal.cancel": "Поништи",
"confirmations.block.confirm": "Блокирај",
"confirmations.block.message": "Да ли сте сигурни да желите да блокирате корисника {name}?",
"confirmations.delete.confirm": "Обриши",
"confirmations.delete.message": "Да ли сте сигурни да желите обришете овај статус?",
"confirmations.delete_list.confirm": "Обриши",
"confirmations.delete_list.message": "Да ли сте сигурни да желите да бесповратно обришете ову листу?",
"confirmations.domain_block.confirm": "Сакриј цео домен",
"confirmations.domain_block.message": "Да ли сте стварно, стварно сигурно да желите да блокирате цео домен {domain}? У већини случајева, пар добрих блокирања или мутирања су довољна и препоручљива.",
"confirmations.mute.confirm": "Мутирај",
"confirmations.mute.message": "Да ли стварно желите да мутирате корисника {name}?",
"confirmations.unfollow.confirm": "Отпрати",
"confirmations.unfollow.message": "Да ли сте сигурни да желите да отпратите корисника {name}?",
"embed.instructions": "Угради овај статус на Ваш веб сајт копирањем кода испод.",
"embed.preview": "Овако ће да изгледа:",
"emoji_button.activity": "Активност",
"emoji_button.custom": "Произвољно",
"emoji_button.flags": "Заставе",
"emoji_button.food": "Храна & пиће",
"emoji_button.label": "Убаци смајли",
"emoji_button.nature": "Природа",
"emoji_button.not_found": "Нема смајлија!! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Објекти",
"emoji_button.people": "Људи",
"emoji_button.recent": "Најчешће коришћени",
"emoji_button.search": "Претрага...",
"emoji_button.search_results": "Резултати претраге",
"emoji_button.symbols": "Симболи",
"emoji_button.travel": "Путовања & места",
"empty_column.community": "Локална лајна је празна. Напишите нешто јавно да лајна продува!",
"empty_column.hashtag": "Тренутно нема ништа на овом хештегу.",
"empty_column.home": "Ваша лајна је празна! Посетите {public} или користите претрагу да почнете и упознајете нове људе.",
"empty_column.home.public_timeline": "јавна лајна",
"empty_column.list": "У овој листи још нема ничега. Када чланови листе објаве нове статусе, они ће се појављивати овде.",
"empty_column.notifications": "Тренутно немате обавештења. Дружите се мало да започнете разговоре.",
"empty_column.public": "Овде нема ничега! Напишите нешто јавно, или нађите кориснике са других инстанци које ћете запратити да попуните ову празнину",
"follow_request.authorize": "Одобри",
"follow_request.reject": "Одбиј",
"getting_started.appsshort": "Апликације",
"getting_started.faq": "ЧПП",
"getting_started.heading": "Да почнете",
"getting_started.open_source_notice": "Мастoдонт је софтвер отвореног кода. Можете му допринети или пријавити проблеме преко GitHub-а на {github}.",
"getting_started.userguide": "Корисничко упутство",
"home.column_settings.advanced": "Напредно",
"home.column_settings.basic": "Основно",
"home.column_settings.filter_regex": "Филтрирај регуларним изразима",
"home.column_settings.show_reblogs": "Прикажи и подржавања",
"home.column_settings.show_replies": "Прикажи одговоре",
"home.settings": "Поставке колоне",
"keyboard_shortcuts.back": "да одете назад",
"keyboard_shortcuts.boost": "да подржите",
"keyboard_shortcuts.column": "да се пребаците на статус у једној од колона",
"keyboard_shortcuts.compose": "да се пребаците на писање новог тута",
"keyboard_shortcuts.description": "Опис",
"keyboard_shortcuts.down": "да се померите на доле у листи",
"keyboard_shortcuts.enter": "да отворите статус",
"keyboard_shortcuts.favourite": "да означите као омиљено",
"keyboard_shortcuts.heading": "Пречице на тастатури",
"keyboard_shortcuts.hotkey": "Пречица",
"keyboard_shortcuts.legend": "да прикажете овај подсетник",
"keyboard_shortcuts.mention": "да поменете аутора",
"keyboard_shortcuts.reply": "да одговорите",
"keyboard_shortcuts.search": "да се пребаците на претрагу",
"keyboard_shortcuts.toot": "да започнете скроз нови тут",
"keyboard_shortcuts.unfocus": "да не будете више на претрази/прављењу новог тута",
"keyboard_shortcuts.up": "да се померите на горе у листи",
"lightbox.close": "Затвори",
"lightbox.next": "Следећи",
"lightbox.previous": "Претходни",
"lists.account.add": "Додај на листу",
"lists.account.remove": "Уклони са листе",
"lists.delete": "Обриши листу",
"lists.edit": "Измени листу",
"lists.new.create": "Додај листу",
"lists.new.title_placeholder": "Наслов нове листе",
"lists.search": "Претражи међу људима које пратите",
"lists.subheading": "Ваше листе",
"loading_indicator.label": "Учитавам...",
"media_gallery.toggle_visible": "Укључи/искључи видљивост",
"missing_indicator.label": "Није пронађено",
"mute_modal.hide_notifications": "Сакриј обавештења од овог корисника?",
"navigation_bar.blocks": "Блокирани корисници",
"navigation_bar.community_timeline": "Локална лајна",
"navigation_bar.edit_profile": "Измени профил",
"navigation_bar.favourites": "Омиљени",
"navigation_bar.follow_requests": "Захтеви за праћење",
"navigation_bar.info": "О овој инстанци",
"navigation_bar.keyboard_shortcuts": "Пречице на тастатури",
"navigation_bar.lists": "Листе",
"navigation_bar.logout": "Одјава",
"navigation_bar.mutes": "Мутирани корисници",
"navigation_bar.pins": "Прикачени тутови",
"navigation_bar.preferences": "Подешавања",
"navigation_bar.public_timeline": "Здружена лајна",
"notification.favourite": "{name} је ставио Ваш статус као омиљени",
"notification.follow": "{name} Вас је запратио",
"notification.mention": "{name} Вас је поменуо",
"notification.reblog": "{name} је подржао(ла) Ваш статус",
"notifications.clear": "Очисти обавештења",
"notifications.clear_confirmation": "Да ли сте сигурно да трајно желите да очистите Ваша обавештења?",
"notifications.column_settings.alert": "Обавештења на радној површини",
"notifications.column_settings.favourite": "Омиљени:",
"notifications.column_settings.follow": "Нови пратиоци:",
"notifications.column_settings.mention": "Помињања:",
"notifications.column_settings.push": "Гурај обавештења",
"notifications.column_settings.push_meta": "Овај уређај",
"notifications.column_settings.reblog": "Подршки:",
"notifications.column_settings.show": "Прикажи у колони",
"notifications.column_settings.sound": "Пуштај звук",
"onboarding.done": "Готово",
"onboarding.next": "Следеће",
"onboarding.page_five.public_timelines": "Локална лајна приказује све јавне статусе од свих на домену {domain}. Здружена лајна приказује јавне статусе од свих људи које прате корисници са домена {domain}. Ово су јавне лајне, сјајан начин да откријете нове људе.",
"onboarding.page_four.home": "Почетна лајна приказује статусе људи које Ви пратите.",
"onboarding.page_four.notifications": "Колона са обавештењима Вам приказује када неко прича са Вама.",
"onboarding.page_one.federation": "Мастодонт је мрежа независних сервера који се увезују да направе једну већу друштвену мрежу. Ове сервере зовемо инстанцама.",
"onboarding.page_one.handle": "Ви сте на домену {domain}, па је Ваша пуна идентификација {handle}",
"onboarding.page_one.welcome": "Добродошли на Мастодонт!",
"onboarding.page_six.admin": "Администратор Ваше инстанце је {admin}.",
"onboarding.page_six.almost_done": "Још мало, па готово...",
"onboarding.page_six.appetoot": "Пријатутно!",
"onboarding.page_six.apps_available": "Постоје {apps} доступне за iOS, Андроид и друге платформе.",
"onboarding.page_six.github": "Мастодонт је слободан софтвер отвореног кода. Можете пријављивати грешке, потраживати нове фунцкионалности, или учествујући у програмирању. Наш изворни код је овде: {github}.",
"onboarding.page_six.guidelines": "смернице заједнице",
"onboarding.page_six.read_guidelines": "Прочитејте {guidelines} домена {domain}!",
"onboarding.page_six.various_app": "мобилне апликације",
"onboarding.page_three.profile": "Измените профил да промените аватар, биографију и име за приказ. Тамо ћете наћи и остала подешавања.",
"onboarding.page_three.search": "Корисите претрагу да нађете људе и гледате хештегове, као што су {illustration} и {introductions}. Да нађете особу која није на овој инстанци, користите њену пуну идентификацију.",
"onboarding.page_two.compose": "Пишите статусе из прве колоне. Можете отпремати слике, мењати подешавања приватности, и додавати упозорења за осетљив садржај преко иконица испод.",
"onboarding.skip": "Прескочи",
"privacy.change": "Подеси статус приватности",
"privacy.direct.long": "Објави само корисницима који су поменути",
"privacy.direct.short": "Директно",
"privacy.private.long": "Објави само пратиоцима",
"privacy.private.short": "Само за пратиоце",
"privacy.public.long": "Објави на јавној лајни",
"privacy.public.short": "Јавно",
"privacy.unlisted.long": "Не објављуј на јавним лајнама",
"privacy.unlisted.short": "Неизлистано",
"relative_time.days": "{number}d",
"relative_time.hours": "{number}h",
"relative_time.just_now": "сада",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Поништи",
"report.placeholder": "Додатни коментари",
"report.submit": "Пошаљи",
"report.target": "Пријављујем {target}",
"search.placeholder": "Претрага",
"search_popout.search_format": "Напредни формат претраге",
"search_popout.tips.hashtag": "хештег",
"search_popout.tips.status": "статус",
"search_popout.tips.text": "Тражењем обичног текста ћете добити сва пронађена имена, сва корисничка имена и све нађене хештегове",
"search_popout.tips.user": "корисник",
"search_results.total": "{count, number} {count, plural, one {резултат} few {резултата} other {резултата}}",
"standalone.public_title": "Поглед изнутра...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Овај статус не може да се подржи",
"status.delete": "Обриши",
"status.embed": "Угради на сајт",
"status.favourite": "Омиљено",
"status.load_more": "Учитај још",
"status.media_hidden": "Мултимедија сакривена",
"status.mention": "Помени корисника @{name}",
"status.more": "Још",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Мутирај преписку",
"status.open": "Прошири овај статус",
"status.pin": "Прикачи на профил",
"status.reblog": "Подржи",
"status.reblogged_by": "{name} подржао(ла)",
"status.reply": "Одговори",
"status.replyAll": "Одговори на дискусију",
"status.report": "Пријави корисника @{name}",
"status.sensitive_toggle": "Кликните да видите",
"status.sensitive_warning": "Осетљив садржај",
"status.share": "Подели",
"status.show_less": "Прикажи мање",
"status.show_more": "Прикажи више",
"status.unmute_conversation": "Укључи преписку",
"status.unpin": "Откачи са профила",
"tabs_bar.compose": "Напиши",
"tabs_bar.federated_timeline": "Здружено",
"tabs_bar.home": "Почетна",
"tabs_bar.local_timeline": "Локално",
"tabs_bar.notifications": "Обавештења",
"ui.beforeunload": "Ако напустите Мастодонт, изгубићете написани нацрт.",
"upload_area.title": "Превуците овде да отпремите",
"upload_button.label": "Додај мултимедију",
"upload_form.description": "Опиши за слабовиде особе",
"upload_form.undo": "Опозови",
"upload_progress.label": "Отпремам...",
"video.close": "Затвори видео",
"video.exit_fullscreen": "Напусти цео екран",
"video.expand": "Прошири видео",
"video.fullscreen": "Цео екран",
"video.hide": "Сакриј видео",
"video.mute": "Угаси звук",
"video.pause": "Паузирај",
"video.play": "Пусти",
"video.unmute": "Врати звук"
}

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Ångra fäst",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Inställningar",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Ditt konto är inte {locked}. Vemsomhelst kan följa dig och även se dina inlägg skrivna för endast dina följare.",
"compose_form.lock_disclaimer.lock": "låst",
"compose_form.placeholder": "Vad funderar du på?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "användare",
"search_results.total": "{count, number} {count, plural, ett {result} andra {results}}",
"standalone.public_title": "En titt inuti...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Detta inlägg kan inte knuffas",
"status.delete": "Ta bort",
"status.embed": "Bädda in",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media dold",
"status.mention": "Omnämn @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Tysta konversation",
"status.open": "Utvidga denna status",
"status.pin": "Fäst i profil",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigation",
"column_subheading.settings": "Settings",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
"compose_form.lock_disclaimer.lock": "locked",
"compose_form.placeholder": "What is on your mind?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Delete",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Media hidden",
"status.mention": "Mention @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Expand this status",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Navigasyon",
"column_subheading.settings": "Ayarlar",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Hesabınız {locked} değil. Sadece takipçilerle paylaştığınız gönderileri görebilmek için sizi herhangi bir kullanıcı takip edebilir.",
"compose_form.lock_disclaimer.lock": "kilitli",
"compose_form.placeholder": "Ne düşünüyorsun?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {sonuç} other {sonuçlar}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Bu gönderi boost edilemez",
"status.delete": "Sil",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Gizli görsel",
"status.mention": "Bahset @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Bu gönderiyi genişlet",
"status.pin": "Pin on profile",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "Unpin",
"column_subheading.navigation": "Навігація",
"column_subheading.settings": "Налаштування",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "Ваш акаунт не {locked}. Кожен може підписатися на Вас та бачити Ваші приватні пости.",
"compose_form.lock_disclaimer.lock": "приватний",
"compose_form.placeholder": "Що у Вас на думці?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} {count, plural, one {результат} few {результати} many {результатів} other {результатів}}",
"standalone.public_title": "A look inside...",
"status.block": "Block @{name}",
"status.cannot_reblog": "Цей допис не може бути передмухнутий",
"status.delete": "Видалити",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "Медіаконтент приховано",
"status.mention": "Згадати",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Заглушити діалог",
"status.open": "Розгорнути допис",
"status.pin": "Pin on profile",

View File

@@ -0,0 +1,2 @@
[
]

View File

@@ -0,0 +1,2 @@
[
]

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "取消固定",
"column_subheading.navigation": "导航",
"column_subheading.settings": "设置",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "你的帐户没有{locked}。任何人都可以在关注你后立即查看仅关注者可见的嘟文。",
"compose_form.lock_disclaimer.lock": "开启保护",
"compose_form.placeholder": "在想啥?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "用户",
"search_results.total": "共 {count, number} 个结果",
"standalone.public_title": "大家都在干啥?",
"status.block": "Block @{name}",
"status.cannot_reblog": "无法转嘟这条嘟文",
"status.delete": "删除",
"status.embed": "嵌入",
@@ -221,6 +223,7 @@
"status.media_hidden": "隐藏媒体内容",
"status.mention": "提及 @{name}",
"status.more": "更多",
"status.mute": "Mute @{name}",
"status.mute_conversation": "隐藏此对话",
"status.open": "展开嘟文",
"status.pin": "在个人资料页面置顶",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "取下",
"column_subheading.navigation": "瀏覽",
"column_subheading.settings": "設定",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "你的用戶狀態為「{locked}」,任何人都能立即關注你,然後看到「只有關注者能看」的文章。",
"compose_form.lock_disclaimer.lock": "公共",
"compose_form.placeholder": "你在想甚麼?",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} 項結果",
"standalone.public_title": "站點一瞥…",
"status.block": "Block @{name}",
"status.cannot_reblog": "這篇文章無法被轉推",
"status.delete": "刪除",
"status.embed": "鑲嵌",
@@ -221,6 +223,7 @@
"status.media_hidden": "隱藏媒體內容",
"status.mention": "提及 @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "靜音對話",
"status.open": "展開文章",
"status.pin": "置頂到資料頁",

View File

@@ -50,6 +50,7 @@
"column_header.unpin": "取下",
"column_subheading.navigation": "瀏覽",
"column_subheading.settings": "設定",
"compose_form.hashtag_warning": "This toot won't be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.",
"compose_form.lock_disclaimer": "你的帳號沒有{locked}。任何人都可以關注你,看到發給關注者的貼文。",
"compose_form.lock_disclaimer.lock": "上鎖",
"compose_form.placeholder": "在想些什麼?",
@@ -63,8 +64,8 @@
"confirmations.block.message": "你確定要封鎖 {name} ",
"confirmations.delete.confirm": "刪除",
"confirmations.delete.message": "你確定要刪除這個狀態?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.delete_list.confirm": "刪除",
"confirmations.delete_list.message": "確定要永久性地刪除這個名單嗎?",
"confirmations.domain_block.confirm": "隱藏整個網域",
"confirmations.domain_block.message": "你真的真的確定要隱藏整個 {domain} ?多數情況下,比較推薦封鎖或消音幾個特定目標就好。",
"confirmations.mute.confirm": "消音",
@@ -127,14 +128,14 @@
"lightbox.close": "關閉",
"lightbox.next": "繼續",
"lightbox.previous": "回退",
"lists.account.add": "Add to list",
"lists.account.remove": "Remove from list",
"lists.delete": "Delete list",
"lists.edit": "Edit list",
"lists.new.create": "Add list",
"lists.new.title_placeholder": "New list title",
"lists.search": "Search among people you follow",
"lists.subheading": "Your lists",
"lists.account.add": "加到名單裡",
"lists.account.remove": "從名單中移除",
"lists.delete": "刪除名單",
"lists.edit": "修改名單",
"lists.new.create": "新增名單",
"lists.new.title_placeholder": "名單名稱",
"lists.search": "搜尋您關注的使用者",
"lists.subheading": "您的名單",
"loading_indicator.label": "讀取中...",
"media_gallery.toggle_visible": "切換可見性",
"missing_indicator.label": "找不到",
@@ -145,8 +146,8 @@
"navigation_bar.favourites": "最愛",
"navigation_bar.follow_requests": "關注請求",
"navigation_bar.info": "關於本站",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
"navigation_bar.lists": "Lists",
"navigation_bar.keyboard_shortcuts": "快速鍵",
"navigation_bar.lists": "名單",
"navigation_bar.logout": "登出",
"navigation_bar.mutes": "消音的使用者",
"navigation_bar.pins": "置頂貼文",
@@ -213,6 +214,7 @@
"search_popout.tips.user": "user",
"search_results.total": "{count, number} 項結果",
"standalone.public_title": "站點一瞥…",
"status.block": "Block @{name}",
"status.cannot_reblog": "此貼文無法轉推",
"status.delete": "刪除",
"status.embed": "Embed",
@@ -221,6 +223,7 @@
"status.media_hidden": "媒體已隱藏",
"status.mention": "提到 @{name}",
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "消音對話",
"status.open": "展開這個狀態",
"status.pin": "置頂到個人資訊頁",

View File

@@ -1,5 +1,5 @@
import * as WebPushSubscription from './web_push_subscription';
import Mastodon from './containers/mastodon';
import * as registerPushNotifications from './actions/push_notifications';
import { default as Mastodon, store } from './containers/mastodon';
import React from 'react';
import ReactDOM from 'react-dom';
import ready from './ready';
@@ -25,7 +25,7 @@ function main() {
if (process.env.NODE_ENV === 'production') {
// avoid offline in dev mode because it's harder to debug
require('offline-plugin/runtime').install();
WebPushSubscription.register();
store.dispatch(registerPushNotifications.register());
}
perf.stop('main()');
});

View File

@@ -1,5 +1,5 @@
import { STORE_HYDRATE } from '../actions/store';
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, ALERTS_CHANGE } from '../actions/push_notifications';
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push_notifications';
import Immutable from 'immutable';
const initialState = Immutable.Map({
@@ -43,8 +43,8 @@ export default function push_subscriptions(state = initialState, action) {
return state.set('browserSupport', action.value);
case CLEAR_SUBSCRIPTION:
return initialState;
case ALERTS_CHANGE:
return state.setIn(action.key, action.value);
case SET_ALERTS:
return state.setIn(action.path, action.value);
default:
return state;
}

View File

@@ -93,7 +93,7 @@ export default function settings(state = initialState, action) {
return hydrate(state, action.state.get('settings'));
case SETTING_CHANGE:
return state
.setIn(action.key, action.value)
.setIn(action.path, action.value)
.set('saved', false);
case COLUMN_ADD:
return state

View File

@@ -1,129 +0,0 @@
import axios from 'axios';
import { store } from './containers/mastodon';
import { setBrowserSupport, setSubscription, clearSubscription } from './actions/push_notifications';
import { pushNotificationsSetting } from './settings';
// Taken from https://www.npmjs.com/package/web-push
const urlBase64ToUint8Array = (base64String) => {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
};
const getApplicationServerKey = () => document.querySelector('[name="applicationServerKey"]').getAttribute('content');
const getRegistration = () => navigator.serviceWorker.ready;
const getPushSubscription = (registration) =>
registration.pushManager.getSubscription()
.then(subscription => ({ registration, subscription }));
const subscribe = (registration) =>
registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(getApplicationServerKey()),
});
const unsubscribe = ({ registration, subscription }) =>
subscription ? subscription.unsubscribe().then(() => registration) : registration;
const sendSubscriptionToBackend = (subscription) => {
const params = { subscription };
const me = store.getState().getIn(['meta', 'me']);
if (me) {
const data = pushNotificationsSetting.get(me);
if (data) {
params.data = data;
}
}
return axios.post('/api/web/push_subscriptions', params).then(response => response.data);
};
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
const supportsPushNotifications = ('serviceWorker' in navigator && 'PushManager' in window && 'getKey' in PushSubscription.prototype);
export function register () {
store.dispatch(setBrowserSupport(supportsPushNotifications));
const me = store.getState().getIn(['meta', 'me']);
if (me && !pushNotificationsSetting.get(me)) {
const alerts = store.getState().getIn(['push_notifications', 'alerts']);
if (alerts) {
pushNotificationsSetting.set(me, { alerts: alerts });
}
}
if (supportsPushNotifications) {
if (!getApplicationServerKey()) {
console.error('The VAPID public key is not set. You will not be able to receive Web Push Notifications.');
return;
}
getRegistration()
.then(getPushSubscription)
.then(({ registration, subscription }) => {
if (subscription !== null) {
// We have a subscription, check if it is still valid
const currentServerKey = (new Uint8Array(subscription.options.applicationServerKey)).toString();
const subscriptionServerKey = urlBase64ToUint8Array(getApplicationServerKey()).toString();
const serverEndpoint = store.getState().getIn(['push_notifications', 'subscription', 'endpoint']);
// If the VAPID public key did not change and the endpoint corresponds
// to the endpoint saved in the backend, the subscription is valid
if (subscriptionServerKey === currentServerKey && subscription.endpoint === serverEndpoint) {
return subscription;
} else {
// Something went wrong, try to subscribe again
return unsubscribe({ registration, subscription }).then(subscribe).then(sendSubscriptionToBackend);
}
}
// No subscription, try to subscribe
return subscribe(registration).then(sendSubscriptionToBackend);
})
.then(subscription => {
// If we got a PushSubscription (and not a subscription object from the backend)
// it means that the backend subscription is valid (and was set during hydration)
if (!(subscription instanceof PushSubscription)) {
store.dispatch(setSubscription(subscription));
if (me) {
pushNotificationsSetting.set(me, { alerts: subscription.alerts });
}
}
})
.catch(error => {
if (error.code === 20 && error.name === 'AbortError') {
console.warn('Your browser supports Web Push Notifications, but does not seem to implement the VAPID protocol.');
} else if (error.code === 5 && error.name === 'InvalidCharacterError') {
console.error('The VAPID public key seems to be invalid:', getApplicationServerKey());
}
// Clear alerts and hide UI settings
store.dispatch(clearSubscription());
if (me) {
pushNotificationsSetting.remove(me);
}
try {
getRegistration()
.then(getPushSubscription)
.then(unsubscribe);
} catch (e) {
}
});
} else {
console.warn('Your browser does not support Web Push Notifications.');
}
}

View File

@@ -6,6 +6,7 @@
@import 'mastodon/reset';
@import 'mastodon/basics';
@import 'mastodon/modal';
@import 'mastodon/containers';
@import 'mastodon/lists';
@import 'mastodon/footer';

View File

@@ -214,6 +214,7 @@
.dropdown-menu {
position: absolute;
transform-origin: 50% 0;
}
.dropdown--active .icon-button {
@@ -1757,7 +1758,7 @@
position: absolute;
top: 0;
left: 0;
background: lighten($ui-base-color, 13%);
background: lighten($ui-base-color, 13%) url('../images/wave-drawer.png') no-repeat bottom / 100% auto;
box-sizing: border-box;
padding: 0;
display: flex;
@@ -1770,6 +1771,11 @@
&.darker {
background: $ui-base-color;
}
> .mastodon {
background: url('../images/mastodon-ui.png') no-repeat left bottom / contain;
flex: 1;
}
}
.pseudo-drawer {
@@ -2066,20 +2072,18 @@
cursor: default;
}
.getting-started__wrapper,
.getting_started {
background: $ui-base-color;
}
.getting-started__wrapper {
position: relative;
overflow-y: auto;
}
.getting-started__footer {
display: flex;
flex-direction: column;
}
.getting-started {
box-sizing: border-box;
padding-bottom: 235px;
background: url('../images/mastodon-getting-started.png') no-repeat 0 100%;
background: $ui-base-color;
flex: 1 0 auto;
p {
@@ -2104,7 +2108,7 @@
padding: 0 10px 8px;
}
code {
kbd {
display: inline-block;
padding: 3px 5px;
background-color: lighten($ui-base-color, 8%);
@@ -2148,7 +2152,8 @@
@import 'boost';
button.icon-button i.fa-retweet {
.no-reduce-motion button.icon-button i.fa-retweet {
background-position: 0 0;
height: 19px;
transition: background-position 0.9s steps(10);
@@ -2159,13 +2164,23 @@ button.icon-button i.fa-retweet {
&::before {
display: none !important;
}
}
button.icon-button.active i.fa-retweet {
.no-reduce-motion button.icon-button.active i.fa-retweet {
transition-duration: 0.9s;
background-position: 0 100%;
}
.reduce-motion button.icon-button i.fa-retweet {
color: $ui-base-lighter-color;
transition: color 100ms ease-in;
}
.reduce-motion button.icon-button.active i.fa-retweet {
color: $ui-highlight-color;
}
.status-card {
display: flex;
cursor: pointer;
@@ -2943,6 +2958,7 @@ button.icon-button.active i.fa-retweet {
border-radius: 4px;
margin-left: 40px;
overflow: hidden;
transform-origin: 50% 0;
}
.privacy-dropdown__option {

View File

@@ -0,0 +1,20 @@
.modal-layout {
background: $ui-base-color url('../images/wave-modal.png') repeat-x bottom fixed;
display: flex;
flex-direction: column;
height: 100vh;
padding: 0;
}
.modal-layout__mastodon {
display: flex;
flex: 1;
flex-direction: column;
justify-content: flex-end;
> * {
flex: 1;
max-height: 235px;
background: url('../images/mastodon-ui.png') no-repeat left bottom / contain;
}
}

View File

@@ -0,0 +1,31 @@
# frozen_string_literal: true
class ActivityTracker
EXPIRE_AFTER = 90.days.seconds
class << self
def increment(prefix)
key = [prefix, current_week].join(':')
redis.incrby(key, 1)
redis.expire(key, EXPIRE_AFTER)
end
def record(prefix, value)
key = [prefix, current_week].join(':')
redis.pfadd(key, value)
redis.expire(key, EXPIRE_AFTER)
end
private
def redis
Redis.current
end
def current_week
Time.zone.today.cweek
end
end
end

View File

@@ -2,16 +2,18 @@
class ActivityPub::Activity::Accept < ActivityPub::Activity
def perform
case @object['type']
when 'Follow'
accept_follow
if @object.respond_to?(:[]) &&
@object['type'] == 'Follow' && @object['actor'].present?
accept_follow_from @object['actor']
else
accept_follow_object @object
end
end
private
def accept_follow
target_account = account_from_uri(target_uri)
def accept_follow_from(actor)
target_account = account_from_uri(value_or_id(actor))
return if target_account.nil? || !target_account.local?
@@ -19,7 +21,8 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity
follow_request&.authorize!
end
def target_uri
@target_uri ||= value_or_id(@object['actor'])
def accept_follow_object(object)
follow_request = ActivityPub::TagManager.instance.uri_to_resource(value_or_id(object), FollowRequest)
follow_request&.authorize!
end
end

View File

@@ -13,6 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
def delete_person
SuspendAccountService.new.call(@account)
@account.destroy!
end
def delete_note

View File

@@ -28,6 +28,8 @@ class ActivityPub::TagManager
return target.uri if target.respond_to?(:local?) && !target.local?
case target.object_type
when :follow
account_follow_url(target.account.username, target)
when :person
account_url(target)
when :note, :comment, :activity
@@ -97,6 +99,12 @@ class ActivityPub::TagManager
case klass.name
when 'Account'
klass.find_local(uri_to_local_id(uri, :username))
when 'FollowRequest'
params = Rails.application.routes.recognize_path(uri)
klass.joins(:account).find_by!(
accounts: { domain: nil, username: params[:account_username] },
id: params[:id]
)
else
StatusFinder.new(uri).status
end

View File

@@ -171,10 +171,10 @@ class Formatter
end
def link_to_url(entity)
normalized_url = Addressable::URI.parse(entity[:url]).normalize
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
url = Addressable::URI.parse(entity[:url])
html_attrs = { target: '_blank', rel: 'nofollow noopener' }
Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), normalized_url, html_attrs)
Twitter::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
encode(entity[:url])
end

View File

@@ -29,7 +29,7 @@ class ProviderDiscovery < OEmbed::ProviderDiscovery
end
if format.nil? || format == :xml
provider_endpoint ||= html.at_xpath('//link[@type="application/xml+oembed"]')&.attribute('href')&.value
provider_endpoint ||= html.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value
format ||= :xml if provider_endpoint
end

View File

@@ -6,14 +6,14 @@ class Sanitize
CLASS_WHITELIST_TRANSFORMER = lambda do |env|
node = env[:node]
class_list = node['class']&.split(' ')
class_list = node['class']&.split(/[\t\n\f\r ]/)
return unless class_list
class_list.keep_if do |e|
return true if e =~ /^(h|p|u|dt|e)-/ # microformats classes
return true if e =~ /^(mention|hashtag)$/ # semantic classes
return true if e =~ /^(ellipsis|invisible)$/ # link formatting classes
next true if e =~ /^(h|p|u|dt|e)-/ # microformats classes
next true if e =~ /^(mention|hashtag)$/ # semantic classes
next true if e =~ /^(ellipsis|invisible)$/ # link formatting classes
end
node['class'] = class_list.join(' ')

View File

@@ -13,7 +13,9 @@ class UserMailer < Devise::Mailer
return if @resource.disabled?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance)
mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email,
subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance),
template_name: @resource.pending_reconfirmation? ? 'reconfirmation_instructions' : 'confirmation_instructions'
end
end
@@ -39,4 +41,15 @@ class UserMailer < Devise::Mailer
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
end
end
def email_changed(user, **)
@resource = user
@instance = Rails.configuration.x.local_domain
return if @resource.disabled?
I18n.with_locale(@resource.locale || I18n.default_locale) do
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
end
end
end

View File

@@ -21,6 +21,10 @@ class FollowRequest < ApplicationRecord
validates :account_id, uniqueness: { scope: :target_account_id }
def object_type
:follow
end
def authorize!
account.follow!(target_account, reblogs: show_reblogs)
MergeWorker.perform_async(target_account.id, account.id)

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