Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master

main
Jenkins 7 years ago
commit 447d7e6127

@ -13,11 +13,29 @@ DB_PORT=5432
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
# Optional ElasticSearch configuration
# ES_ENABLED=true
# ES_HOST=localhost
# ES_PORT=9200
# Optimizations
LD_PRELOAD=/data/lib/libjemalloc.so
# ImageMagick optimizations
MAGICK_TEMPORARY_PATH=/app/tmp
MAGICK_MEMORY_LIMIT=128MiB
MAGICK_MAP_LIMIT=64MiB
MAGICK_TIME_LIMIT=15
MAGICK_AREA_LIMIT=16MP
MAGICK_WIDTH_LIMIT=8KP
MAGICK_HEIGHT_LIMIT=8KP
# Federation # 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 should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=${APP_NAME}.nanoapp.io LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
LOCAL_HTTPS=false
# 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. # 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 # You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
@ -31,7 +49,6 @@ LOCAL_HTTPS=false
# Application secrets # Application secrets
# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`) # Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
PAPERCLIP_SECRET=$PAPERCLIP_SECRET
SECRET_KEY_BASE=$SECRET_KEY_BASE SECRET_KEY_BASE=$SECRET_KEY_BASE
OTP_SECRET=$OTP_SECRET OTP_SECRET=$OTP_SECRET
@ -131,9 +148,79 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
# Cluster number setting for streaming API server. # Cluster number setting for streaming API server.
# If you comment out following line, cluster number will be `numOfCpuCores - 1`. # If you comment out following line, cluster number will be `numOfCpuCores - 1`.
STREAMING_CLUSTER_NUM=1 # STREAMING_CLUSTER_NUM=1
# Docker mastodon user # Docker mastodon user
# If you use Docker, you may want to assign UID/GID manually. # If you use Docker, you may want to assign UID/GID manually.
# UID=1000 # UID=1000
# GID=1000 # GID=1000
# LDAP authentication (optional)
# LDAP_ENABLED=true
# LDAP_HOST=localhost
# LDAP_PORT=389
# LDAP_METHOD=simple_tls
# LDAP_BASE=
# LDAP_BIND_DN=
# LDAP_PASSWORD=
# LDAP_UID=cn
# PAM authentication (optional)
# PAM authentication uses for the email generation the "email" pam variable
# and optional as fallback PAM_DEFAULT_SUFFIX
# The pam environment variable "email" is provided by:
# https://github.com/devkral/pam_email_extractor
# PAM_ENABLED=true
# Fallback Suffix for email address generation (nil by default)
# PAM_DEFAULT_SUFFIX=pam
# Name of the pam service (pam "auth" section is evaluated)
# PAM_DEFAULT_SERVICE=rpam
# Name of the pam service used for checking if an user can register (pam "account" section is evaluated) (nil (disabled) by default)
# PAM_CONTROLLED_SERVICE=rpam
# Global OAuth settings (optional) :
# If you have only one strategy, you may want to enable this
# OAUTH_REDIRECT_AT_SIGN_IN=true
# Optional CAS authentication (cf. omniauth-cas) :
# CAS_ENABLED=true
# CAS_URL=https://sso.myserver.com/
# CAS_HOST=sso.myserver.com/
# CAS_PORT=443
# CAS_SSL=true
# CAS_VALIDATE_URL=
# CAS_CALLBACK_URL=
# CAS_LOGOUT_URL=
# CAS_LOGIN_URL=
# CAS_UID_FIELD='user'
# CAS_CA_PATH=
# CAS_DISABLE_SSL_VERIFICATION=false
# CAS_UID_KEY='user'
# CAS_NAME_KEY='name'
# CAS_EMAIL_KEY='email'
# CAS_NICKNAME_KEY='nickname'
# CAS_FIRST_NAME_KEY='firstname'
# CAS_LAST_NAME_KEY='lastname'
# CAS_LOCATION_KEY='location'
# CAS_IMAGE_KEY='image'
# CAS_PHONE_KEY='phone'
# Optional SAML authentication (cf. omniauth-saml)
# SAML_ENABLED=true
# SAML_ACS_URL=
# SAML_ISSUER=http://localhost:3000/auth/auth/saml/callback
# SAML_IDP_SSO_TARGET_URL=https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO
# SAML_IDP_CERT=
# SAML_IDP_CERT_FINGERPRINT=
# SAML_NAME_IDENTIFIER_FORMAT=
# SAML_CERT=
# SAML_PRIVATE_KEY=
# SAML_SECURITY_WANT_ASSERTION_SIGNED=true
# SAML_SECURITY_WANT_ASSERTION_ENCRYPTED=true
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

@ -207,7 +207,9 @@ STREAMING_CLUSTER_NUM=1
# SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true # SAML_SECURITY_ASSUME_EMAIL_IS_VERIFIED=true
# SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1" # SAML_ATTRIBUTES_STATEMENTS_UID="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" # SAML_ATTRIBUTES_STATEMENTS_EMAIL="urn:oid:1.3.6.1.4.1.5923.1.1.1.6"
# SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.5.4.42" # SAML_ATTRIBUTES_STATEMENTS_FULL_NAME="urn:oid:2.16.840.1.113730.3.1.241"
# SAML_ATTRIBUTES_STATEMENTS_FIRST_NAME="urn:oid:2.5.4.42"
# SAML_ATTRIBUTES_STATEMENTS_LAST_NAME="urn:oid:2.5.4.4"
# SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1" # SAML_UID_ATTRIBUTE="urn:oid:0.9.2342.19200300.100.1.1"
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED= # SAML_ATTRIBUTES_STATEMENTS_VERIFIED=
# SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL= # SAML_ATTRIBUTES_STATEMENTS_VERIFIED_EMAIL=

@ -1,4 +1,3 @@
# Federation # Federation
LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_DOMAIN=cb6e6126.ngrok.io
LOCAL_HTTPS=true LOCAL_HTTPS=true
OTP_SECRET=100c7faeef00caa29242f6b04156742bf76065771fd4117990c4282b8748ff3d99f8fdae97c982ab5bd2e6756a159121377cce4421f4a8ecd2d67bd7749a3fb4

@ -1,7 +1,7 @@
FROM ruby:2.5.0-alpine3.7 FROM ruby:2.5.0-alpine3.7
LABEL maintainer="https://github.com/tootsuite/mastodon" \ LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server" description="Your self-hosted, globally interconnected microblogging community"
ARG UID=991 ARG UID=991
ARG GID=991 ARG GID=991
@ -9,8 +9,8 @@ ARG GID=991
ENV RAILS_SERVE_STATIC_FILES=true \ ENV RAILS_SERVE_STATIC_FILES=true \
RAILS_ENV=production NODE_ENV=production RAILS_ENV=production NODE_ENV=production
ARG YARN_VERSION=1.3.2 ARG YARN_VERSION=1.5.1
ARG YARN_DOWNLOAD_SHA256=6cfe82e530ef0837212f13e45c1565ba53f5199eec2527b85ecbcd88bf26821d ARG YARN_DOWNLOAD_SHA256=cd31657232cf48d57fdbff55f38bfa058d2fb4950450bd34af72dac796af4de1
ARG LIBICONV_VERSION=1.15 ARG LIBICONV_VERSION=1.15
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178 ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
@ -38,7 +38,6 @@ RUN apk -U upgrade \
libidn \ libidn \
libpq \ libpq \
nodejs \ nodejs \
nodejs-npm \
protobuf \ protobuf \
su-exec \ su-exec \
tini \ tini \
@ -73,9 +72,13 @@ RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-in
&& yarn --pure-lockfile \ && yarn --pure-lockfile \
&& yarn cache clean && yarn cache clean
RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon RUN addgroup -g ${GID} mastodon && adduser -h /mastodon -s /bin/sh -D -G mastodon -u ${UID} mastodon \
&& mkdir -p /mastodon/public/system /mastodon/public/assets /mastodon/public/packs \
&& chown -R mastodon:mastodon /mastodon/public
COPY --chown=mastodon:mastodon . /mastodon COPY . /mastodon
RUN chown -R mastodon:mastodon /mastodon
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs

@ -28,15 +28,15 @@ gem 'bootsnap'
gem 'browser' gem 'browser'
gem 'charlock_holmes', '~> 0.7.5' gem 'charlock_holmes', '~> 0.7.5'
gem 'iso-639' gem 'iso-639'
gem 'chewy', '~> 0.10', git: 'https://github.com/toptal/chewy.git' gem 'chewy', '~> 5.0'
gem 'cld3', '~> 3.2.0' gem 'cld3', '~> 3.2.0'
gem 'devise', '~> 4.4' gem 'devise', '~> 4.4'
gem 'devise-two-factor', '~> 3.0' gem 'devise-two-factor', '~> 3.0'
gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' } gem 'devise_pam_authenticatable2', '~> 8.0', install_if: -> { ENV['PAM_ENABLED'] == 'true' }
gem 'net-ldap', '~> 0.10', install_if: -> { ENV['LDAP_ENABLED'] == 'true' } gem 'net-ldap', '~> 0.10'
gem 'omniauth-cas', '~> 1.1', install_if: -> { ENV['CAS_ENABLED'] == 'true' } gem 'omniauth-cas', '~> 1.1'
gem 'omniauth-saml', '~> 1.10', install_if: -> { ENV['SAML_ENABLED'] == 'true' } gem 'omniauth-saml', '~> 1.10'
gem 'omniauth', '~> 1.2' gem 'omniauth', '~> 1.2'
gem 'doorkeeper', '~> 4.2' gem 'doorkeeper', '~> 4.2'

@ -1,12 +1,3 @@
GIT
remote: https://github.com/toptal/chewy.git
revision: a7d21eb4b0bd7415533ef134bb6d31b2df309701
specs:
chewy (0.10.1)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
@ -118,6 +109,10 @@ GEM
case_transform (0.2) case_transform (0.2)
activesupport activesupport
charlock_holmes (0.7.5) charlock_holmes (0.7.5)
chewy (5.0.0)
activesupport (>= 4.0)
elasticsearch (>= 2.0.0)
elasticsearch-dsl
chunky_png (1.3.8) chunky_png (1.3.8)
cld3 (3.2.2) cld3 (3.2.2)
ffi (>= 1.1.0, < 1.10.0) ffi (>= 1.1.0, < 1.10.0)
@ -634,7 +629,7 @@ DEPENDENCIES
capistrano-yarn (~> 2.0) capistrano-yarn (~> 2.0)
capybara (~> 2.15) capybara (~> 2.15)
charlock_holmes (~> 0.7.5) charlock_holmes (~> 0.7.5)
chewy (~> 0.10)! chewy (~> 5.0)
cld3 (~> 3.2.0) cld3 (~> 3.2.0)
climate_control (~> 0.2) climate_control (~> 0.2)
devise (~> 4.4) devise (~> 4.4)

@ -0,0 +1,57 @@
# frozen_string_literal: true
class ActivityPub::CollectionsController < Api::BaseController
include SignatureVerification
before_action :set_account
before_action :set_size
before_action :set_statuses
def show
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json',
skip_activities: true
end
private
def set_account
@account = Account.find_local!(params[:account_username])
end
def set_statuses
@statuses = scope_for_collection.paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = cache_collection(@statuses, Status)
end
def set_size
case params[:id]
when 'featured'
@account.pinned_statuses.count
else
raise ActiveRecord::NotFound
end
end
def scope_for_collection
case params[:id]
when 'featured'
@account.statuses.permitted_for(@account, signed_request_account).tap do |scope|
scope.merge!(@account.pinned_statuses)
end
else
raise ActiveRecord::NotFound
end
end
def collection_presenter
ActivityPub::CollectionPresenter.new(
id: account_collection_url(@account, params[:id]),
type: :ordered,
size: @size,
items: @statuses
)
end
end

@ -9,7 +9,7 @@ class ActivityPub::OutboxesController < Api::BaseController
@statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id]) @statuses = @account.statuses.permitted_for(@account, signed_request_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end end
private private

@ -11,12 +11,18 @@ class Api::V1::Statuses::PinsController < Api::BaseController
def create def create
StatusPin.create!(account: current_account, status: @status) StatusPin.create!(account: current_account, status: @status)
distribute_add_activity!
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusSerializer
end end
def destroy def destroy
pin = StatusPin.find_by(account: current_account, status: @status) pin = StatusPin.find_by(account: current_account, status: @status)
pin&.destroy!
if pin
pin.destroy!
distribute_remove_activity!
end
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusSerializer
end end
@ -25,4 +31,24 @@ class Api::V1::Statuses::PinsController < Api::BaseController
def set_status def set_status
@status = Status.find(params[:status_id]) @status = Status.find(params[:status_id])
end end
def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::AddSerializer,
adapter: ActivityPub::Adapter
).as_json
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
end
def distribute_remove_activity!
json = ActiveModelSerializers::SerializableResource.new(
@status,
serializer: ActivityPub::RemoveSerializer,
adapter: ActivityPub::Adapter
).as_json
ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
end
end end

@ -13,10 +13,9 @@ class Auth::SessionsController < Devise::SessionsController
def new def new
Devise.omniauth_configs.each do |provider, config| Devise.omniauth_configs.each do |provider, config|
if config.strategy.redirect_at_sign_in return redirect_to(omniauth_authorize_path(resource_name, provider)) if config.strategy.redirect_at_sign_in
return redirect_to(omniauth_authorize_path(resource_name, provider))
end
end end
super super
end end
@ -60,6 +59,14 @@ class Auth::SessionsController < Devise::SessionsController
end end
end end
def after_sign_out_path_for(_resource_or_scope)
Devise.omniauth_configs.each_value do |config|
return root_path if config.strategy.redirect_at_sign_in
end
super
end
def two_factor_enabled? def two_factor_enabled?
find_user.try(:otp_required_for_login?) find_user.try(:otp_required_for_login?)
end end

@ -17,11 +17,7 @@ module Localized
end end
def default_locale def default_locale
request_locale || env_locale || I18n.default_locale request_locale || I18n.default_locale
end
def env_locale
ENV['DEFAULT_LOCALE']
end end
def request_locale def request_locale
@ -29,12 +25,10 @@ module Localized
end end
def preferred_locale def preferred_locale
http_accept_language.preferred_language_from([env_locale]) ||
http_accept_language.preferred_language_from(I18n.available_locales) http_accept_language.preferred_language_from(I18n.available_locales)
end end
def compatible_locale def compatible_locale
http_accept_language.compatible_language_from([env_locale]) ||
http_accept_language.compatible_language_from(I18n.available_locales) http_accept_language.compatible_language_from(I18n.available_locales)
end end
end end

@ -35,7 +35,8 @@ class HomeController < ApplicationController
end end
end end
redirect_to(default_redirect_path) matches = request.path.match(%r{\A/web/timelines/tag/(?<tag>.+)\z})
redirect_to(matches ? tag_path(CGI.unescape(matches[:tag])) : default_redirect_path)
end end
def set_pack def set_pack

@ -2,7 +2,7 @@
module InstanceHelper module InstanceHelper
def site_title def site_title
Setting.site_title.presence || site_hostname Setting.site_title
end end
def site_hostname def site_hostname

@ -8,6 +8,27 @@ module StreamEntriesHelper
account.display_name.presence || account.username account.display_name.presence || account.username
end end
def account_description(account)
prepend_str = [
[
number_to_human(account.statuses_count, strip_insignificant_zeros: true),
t('accounts.posts'),
].join(' '),
[
number_to_human(account.following_count, strip_insignificant_zeros: true),
t('accounts.following'),
].join(' '),
[
number_to_human(account.followers_count, strip_insignificant_zeros: true),
t('accounts.followers'),
].join(' '),
].join(', ')
[prepend_str, account.note].join(' · ')
end
def stream_link_target def stream_link_target
embedded_view? ? '_blank' : nil embedded_view? ? '_blank' : nil
end end

@ -1,6 +1,7 @@
import api from '../api'; import api from '../api';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
import { tagHistory } from '../settings';
import { useEmoji } from './emojis'; import { useEmoji } from './emojis';
import { import {
@ -27,6 +28,9 @@ export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE';
export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE';
export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; export const COMPOSE_MOUNT = 'COMPOSE_MOUNT';
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
@ -92,8 +96,9 @@ export function mentionCompose(account, router) {
export function submitCompose() { export function submitCompose() {
return function (dispatch, getState) { return function (dispatch, getState) {
const status = getState().getIn(['compose', 'text'], ''); const status = getState().getIn(['compose', 'text'], '');
const media = getState().getIn(['compose', 'media_attachments']);
if (!status || !status.length) { if ((!status || !status.length) && media.size === 0) {
return; return;
} }
@ -102,7 +107,7 @@ export function submitCompose() {
api(getState).post('/api/v1/statuses', { api(getState).post('/api/v1/statuses', {
status, status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), media_ids: media.map(item => item.get('id')),
sensitive: getState().getIn(['compose', 'sensitive']), sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
visibility: getState().getIn(['compose', 'privacy']), visibility: getState().getIn(['compose', 'privacy']),
@ -111,6 +116,7 @@ export function submitCompose() {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
}, },
}).then(function (response) { }).then(function (response) {
dispatch(insertIntoTagHistory(response.data.tags));
dispatch(submitComposeSuccess({ ...response.data })); dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately get the status into the columns // To make the app more responsive, immediately get the status into the columns
@ -273,12 +279,22 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
dispatch(readyComposeSuggestionsEmojis(token, results)); dispatch(readyComposeSuggestionsEmojis(token, results));
}; };
const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
dispatch(updateSuggestionTags(token));
};
export function fetchComposeSuggestions(token) { export function fetchComposeSuggestions(token) {
return (dispatch, getState) => { return (dispatch, getState) => {
if (token[0] === ':') { switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, token); fetchComposeSuggestionsEmojis(dispatch, getState, token);
} else { break;
case '#':
fetchComposeSuggestionsTags(dispatch, getState, token);
break;
default:
fetchComposeSuggestionsAccounts(dispatch, getState, token); fetchComposeSuggestionsAccounts(dispatch, getState, token);
break;
} }
}; };
}; };
@ -308,6 +324,9 @@ export function selectComposeSuggestion(position, token, suggestion) {
startPosition = position - 1; startPosition = position - 1;
dispatch(useEmoji(suggestion)); dispatch(useEmoji(suggestion));
} else if (suggestion[0] === '#') {
completion = suggestion;
startPosition = position - 1;
} else { } else {
completion = getState().getIn(['accounts', suggestion, 'acct']); completion = getState().getIn(['accounts', suggestion, 'acct']);
startPosition = position; startPosition = position;
@ -322,6 +341,48 @@ export function selectComposeSuggestion(position, token, suggestion) {
}; };
}; };
export function updateSuggestionTags(token) {
return {
type: COMPOSE_SUGGESTION_TAGS_UPDATE,
token,
};
}
export function updateTagHistory(tags) {
return {
type: COMPOSE_TAG_HISTORY_UPDATE,
tags,
};
}
export function hydrateCompose() {
return (dispatch, getState) => {
const me = getState().getIn(['meta', 'me']);
const history = tagHistory.get(me);
if (history !== null) {
dispatch(updateTagHistory(history));
}
};
}
function insertIntoTagHistory(tags) {
return (dispatch, getState) => {
const state = getState();
const oldHistory = state.getIn(['compose', 'tagHistory']);
const me = state.getIn(['meta', 'me']);
const names = tags.map(({ name }) => name);
const intersectedOldHistory = oldHistory.filter(name => !names.includes(name));
names.push(...intersectedOldHistory.toJS());
const newHistory = names.slice(0, 1000);
tagHistory.set(me, newHistory);
dispatch(updateTagHistory(newHistory));
};
}
export function mountCompose() { export function mountCompose() {
return { return {
type: COMPOSE_MOUNT, type: COMPOSE_MOUNT,

@ -0,0 +1,10 @@
export const DROPDOWN_MENU_OPEN = 'DROPDOWN_MENU_OPEN';
export const DROPDOWN_MENU_CLOSE = 'DROPDOWN_MENU_CLOSE';
export function openDropdownMenu(id, placement) {
return { type: DROPDOWN_MENU_OPEN, id, placement };
}
export function closeDropdownMenu(id) {
return { type: DROPDOWN_MENU_CLOSE, id };
}

@ -1,4 +1,5 @@
import { Iterable, fromJS } from 'immutable'; import { Iterable, fromJS } from 'immutable';
import { hydrateCompose } from './compose';
export const STORE_HYDRATE = 'STORE_HYDRATE'; export const STORE_HYDRATE = 'STORE_HYDRATE';
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@ -8,10 +9,14 @@ const convertState = rawState =>
Iterable.isIndexed(v) ? v.toList() : v.toMap()); Iterable.isIndexed(v) ? v.toList() : v.toMap());
export function hydrateStore(rawState) { export function hydrateStore(rawState) {
return dispatch => {
const state = convertState(rawState); const state = convertState(rawState);
return { dispatch({
type: STORE_HYDRATE, type: STORE_HYDRATE,
state, state,
});
dispatch(hydrateCompose());
}; };
}; };

@ -121,6 +121,7 @@ export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies });
export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`);

@ -3,6 +3,7 @@ import 'intl/locale-data/jsonp/en';
import 'es6-symbol/implement'; import 'es6-symbol/implement';
import includes from 'array-includes'; import includes from 'array-includes';
import assign from 'object-assign'; import assign from 'object-assign';
import values from 'object.values';
import isNaN from 'is-nan'; import isNaN from 'is-nan';
if (!Array.prototype.includes) { if (!Array.prototype.includes) {
@ -13,6 +14,10 @@ if (!Object.assign) {
Object.assign = assign; Object.assign = assign;
} }
if (!Object.values) {
values.shim();
}
if (!Number.isNaN) { if (!Number.isNaN) {
Number.isNaN = isNaN; Number.isNaN = isNaN;
} }

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
@ -8,10 +9,29 @@ export default class AttachmentList extends ImmutablePureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.list.isRequired, media: ImmutablePropTypes.list.isRequired,
compact: PropTypes.bool,
}; };
render () { render () {
const { media } = this.props; const { media, compact } = this.props;
if (compact) {
return (
<div className='attachment-list compact'>
<ul className='attachment-list__list'>
{media.map(attachment => {
const displayUrl = attachment.get('remote_url') || attachment.get('url');
return (
<li key={attachment.get('id')}>
<a href={displayUrl} target='_blank' rel='noopener'><i className='fa fa-link' /> {filename(displayUrl)}</a>
</li>
);
})}
</ul>
</div>
);
}
return ( return (
<div className='attachment-list'> <div className='attachment-list'>
@ -20,11 +40,15 @@ export default class AttachmentList extends ImmutablePureComponent {
</div> </div>
<ul className='attachment-list__list'> <ul className='attachment-list__list'>
{media.map(attachment => ( {media.map(attachment => {
const displayUrl = attachment.get('remote_url') || attachment.get('url');
return (
<li key={attachment.get('id')}> <li key={attachment.get('id')}>
<a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a> <a href={displayUrl} target='_blank' rel='noopener'>{filename(displayUrl)}</a>
</li> </li>
))} );
})}
</ul> </ul>
</div> </div>
); );

@ -20,7 +20,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
word = str.slice(left, right + caretPosition); word = str.slice(left, right + caretPosition);
} }
if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) { if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) {
return [null, null]; return [null, null];
} }
@ -170,6 +170,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
if (typeof suggestion === 'object') { if (typeof suggestion === 'object') {
inner = <AutosuggestEmoji emoji={suggestion} />; inner = <AutosuggestEmoji emoji={suggestion} />;
key = suggestion.id; key = suggestion.id;
} else if (suggestion[0] === '#') {
inner = suggestion;
key = suggestion;
} else { } else {
inner = <AutosuggestAccountContainer id={suggestion} />; inner = <AutosuggestAccountContainer id={suggestion} />;
key = suggestion; key = suggestion;

@ -8,6 +8,7 @@ import spring from 'react-motion/lib/spring';
import detectPassiveEvents from 'detect-passive-events'; import detectPassiveEvents from 'detect-passive-events';
const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
let id = 0;
class DropdownMenu extends React.PureComponent { class DropdownMenu extends React.PureComponent {
@ -29,6 +30,10 @@ class DropdownMenu extends React.PureComponent {
placement: 'bottom', placement: 'bottom',
}; };
state = {
mounted: false,
};
handleDocumentClick = e => { handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) { if (this.node && !this.node.contains(e.target)) {
this.props.onClose(); this.props.onClose();
@ -38,6 +43,7 @@ class DropdownMenu extends React.PureComponent {
componentDidMount () { componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
this.setState({ mounted: true });
} }
componentWillUnmount () { componentWillUnmount () {
@ -82,11 +88,15 @@ class DropdownMenu extends React.PureComponent {
render () { render () {
const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props; const { items, style, placement, arrowOffsetLeft, arrowOffsetTop } = this.props;
const { mounted } = this.state;
return ( return (
<Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
{({ opacity, scaleX, scaleY }) => ( {({ opacity, scaleX, scaleY }) => (
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}> // It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
<div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}>
<div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} /> <div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} />
<ul> <ul>
@ -115,8 +125,10 @@ export default class Dropdown extends React.PureComponent {
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
isUserTouching: PropTypes.func, isUserTouching: PropTypes.func,
isModalOpen: PropTypes.bool.isRequired, isModalOpen: PropTypes.bool.isRequired,
onModalOpen: PropTypes.func, onOpen: PropTypes.func.isRequired,
onModalClose: PropTypes.func, onClose: PropTypes.func.isRequired,
dropdownPlacement: PropTypes.string,
openDropdownId: PropTypes.number,
}; };
static defaultProps = { static defaultProps = {
@ -124,37 +136,28 @@ export default class Dropdown extends React.PureComponent {
}; };
state = { state = {
expanded: false, id: id++,
}; };
handleClick = () => { handleClick = ({ target }) => {
if (!this.state.expanded && this.props.isUserTouching() && this.props.onModalOpen) { if (this.state.id === this.props.openDropdownId) {
const { status, items } = this.props; this.handleClose();
} else {
this.props.onModalOpen({ const { top } = target.getBoundingClientRect();
status, const placement = top * 2 < innerHeight ? 'bottom' : 'top';
actions: items,
onClick: this.handleItemClick,
});
return; this.props.onOpen(this.state.id, this.handleItemClick, placement);
} }
this.setState({ expanded: !this.state.expanded });
} }
handleClose = () => { handleClose = () => {
if (this.props.onModalClose) { this.props.onClose(this.state.id);
this.props.onModalClose();
}
this.setState({ expanded: false });
} }
handleKeyDown = e => { handleKeyDown = e => {
switch(e.key) { switch(e.key) {
case 'Enter': case 'Enter':
this.handleClick(); this.handleClick(e);
break; break;
case 'Escape': case 'Escape':
this.handleClose(); this.handleClose();
@ -186,22 +189,22 @@ export default class Dropdown extends React.PureComponent {
} }
render () { render () {
const { icon, items, size, title, disabled } = this.props; const { icon, items, size, title, disabled, dropdownPlacement, openDropdownId } = this.props;
const { expanded } = this.state; const open = this.state.id === openDropdownId;
return ( return (
<div onKeyDown={this.handleKeyDown}> <div onKeyDown={this.handleKeyDown}>
<IconButton <IconButton
icon={icon} icon={icon}
title={title} title={title}
active={expanded} active={open}
disabled={disabled} disabled={disabled}
size={size} size={size}
ref={this.setTargetRef} ref={this.setTargetRef}
onClick={this.handleClick} onClick={this.handleClick}
/> />
<Overlay show={expanded} placement='bottom' target={this.findTarget}> <Overlay show={open} placement={dropdownPlacement} target={this.findTarget}>
<DropdownMenu items={items} onClose={this.handleClose} /> <DropdownMenu items={items} onClose={this.handleClose} />
</Overlay> </Overlay>
</div> </div>

@ -11,6 +11,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
time: PropTypes.number, time: PropTypes.number,
controls: PropTypes.bool.isRequired, controls: PropTypes.bool.isRequired,
muted: PropTypes.bool.isRequired, muted: PropTypes.bool.isRequired,
onClick: PropTypes.func,
}; };
handleLoadedData = () => { handleLoadedData = () => {
@ -31,6 +32,12 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
this.video = c; this.video = c;
} }
handleClick = e => {
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
render () { render () {
const { src, muted, controls, alt } = this.props; const { src, muted, controls, alt } = this.props;
@ -46,6 +53,7 @@ export default class ExtendedVideoPlayer extends React.PureComponent {
muted={muted} muted={muted}
controls={controls} controls={controls}
loop={!controls} loop={!controls}
onClick={this.handleClick}
/> />
</div> </div>
); );

@ -167,6 +167,14 @@ class Item extends React.PureComponent {
vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true); vShift = shiftToPoint(widthRatio, (containerHeight * (height / 100)), originalHeight, focusY, true);
} }
if (originalWidth > originalHeight) {
imageStyle.height = '100%';
imageStyle.width = 'auto';
} else {
imageStyle.height = 'auto';
imageStyle.width = '100%';
}
imageStyle.top = vShift; imageStyle.top = vShift;
imageStyle.left = hShift; imageStyle.left = hShift;
} else { } else {

@ -12,9 +12,15 @@ export default class Permalink extends React.PureComponent {
href: PropTypes.string.isRequired, href: PropTypes.string.isRequired,
to: PropTypes.string.isRequired, to: PropTypes.string.isRequired,
children: PropTypes.node, children: PropTypes.node,
onInterceptClick: PropTypes.func,
}; };
handleClick = (e) => { handleClick = e => {
if (this.props.onInterceptClick && this.props.onInterceptClick()) {
e.preventDefault();
return;
}
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault();
this.context.router.history.push(this.props.to); this.context.router.history.push(this.props.to);
@ -22,7 +28,7 @@ export default class Permalink extends React.PureComponent {
} }
render () { render () {
const { href, children, className, ...other } = this.props; const { href, children, className, onInterceptClick, ...other } = this.props;
return ( return (
<a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}> <a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>

@ -17,7 +17,7 @@ export default class ScrollableList extends PureComponent {
static propTypes = { static propTypes = {
scrollKey: PropTypes.string.isRequired, scrollKey: PropTypes.string.isRequired,
onScrollToBottom: PropTypes.func, onLoadMore: PropTypes.func.isRequired,
onScrollToTop: PropTypes.func, onScrollToTop: PropTypes.func,
onScroll: PropTypes.func, onScroll: PropTypes.func,
trackScroll: PropTypes.bool, trackScroll: PropTypes.bool,
@ -45,9 +45,11 @@ export default class ScrollableList extends PureComponent {
const offset = scrollHeight - scrollTop - clientHeight; const offset = scrollHeight - scrollTop - clientHeight;
this._oldScrollPosition = scrollHeight - scrollTop; this._oldScrollPosition = scrollHeight - scrollTop;
if (400 > offset && this.props.onScrollToBottom && !this.props.isLoading) { if (400 > offset && this.props.onLoadMore && !this.props.isLoading) {
this.props.onScrollToBottom(); this.props.onLoadMore();
} else if (scrollTop < 100 && this.props.onScrollToTop) { }
if (scrollTop < 100 && this.props.onScrollToTop) {
this.props.onScrollToTop(); this.props.onScrollToTop();
} else if (this.props.onScroll) { } else if (this.props.onScroll) {
this.props.onScroll(); this.props.onScroll();
@ -138,7 +140,7 @@ export default class ScrollableList extends PureComponent {
handleLoadMore = (e) => { handleLoadMore = (e) => {
e.preventDefault(); e.preventDefault();
this.props.onScrollToBottom(); this.props.onLoadMore();
} }
_recentlyMoved () { _recentlyMoved () {

@ -7,6 +7,7 @@ import RelativeTimestamp from './relative_timestamp';
import DisplayName from './display_name'; import DisplayName from './display_name';
import StatusContent from './status_content'; import StatusContent from './status_content';
import StatusActionBar from './status_action_bar'; import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video } from '../features/ui/util/async-components'; import { MediaGallery, Video } from '../features/ui/util/async-components';
@ -138,7 +139,7 @@ export default class Status extends ImmutablePureComponent {
let media = null; let media = null;
let statusAvatar, prepend; let statusAvatar, prepend;
const { hidden } = this.props; const { hidden, featured } = this.props;
const { isExpanded } = this.state; const { isExpanded } = this.state;
let { status, account, ...other } = this.props; let { status, account, ...other } = this.props;
@ -156,7 +157,14 @@ export default class Status extends ImmutablePureComponent {
); );
} }
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-thumb-tack status__prepend-icon' /></div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
</div>
);
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) }; const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
prepend = ( prepend = (
@ -170,9 +178,14 @@ export default class Status extends ImmutablePureComponent {
status = status.get('reblog'); status = status.get('reblog');
} }
if (status.get('media_attachments').size > 0 && !this.props.muted) { if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) { if (this.props.muted || status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
media = (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') { } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]); const video = status.getIn(['media_attachments', 0]);

@ -25,6 +25,11 @@ export default class StatusContent extends React.PureComponent {
_updateStatusLinks () { _updateStatusLinks () {
const node = this.node; const node = this.node;
if (!node) {
return;
}
const links = node.querySelectorAll('a'); const links = node.querySelectorAll('a');
for (var i = 0; i < links.length; ++i) { for (var i = 0; i < links.length; ++i) {
@ -115,6 +120,10 @@ export default class StatusContent extends React.PureComponent {
render () { render () {
const { status } = this.props; const { status } = this.props;
if (status.get('content').length === 0) {
return null;
}
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const content = { __html: status.get('contentHtml') }; const content = { __html: status.get('contentHtml') };

@ -11,7 +11,8 @@ export default class StatusList extends ImmutablePureComponent {
static propTypes = { static propTypes = {
scrollKey: PropTypes.string.isRequired, scrollKey: PropTypes.string.isRequired,
statusIds: ImmutablePropTypes.list.isRequired, statusIds: ImmutablePropTypes.list.isRequired,
onScrollToBottom: PropTypes.func, featuredStatusIds: ImmutablePropTypes.list,
onLoadMore: PropTypes.func,
onScrollToTop: PropTypes.func, onScrollToTop: PropTypes.func,
onScroll: PropTypes.func, onScroll: PropTypes.func,
trackScroll: PropTypes.bool, trackScroll: PropTypes.bool,
@ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent {
} }
render () { render () {
const { statusIds, ...other } = this.props; const { statusIds, featuredStatusIds, ...other } = this.props;
const { isLoading, isPartial } = other; const { isLoading, isPartial } = other;
if (isPartial) { if (isPartial) {
@ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent {
); );
} }
const scrollableContent = (isLoading || statusIds.size > 0) ? ( let scrollableContent = (isLoading || statusIds.size > 0) ? (
statusIds.map((statusId) => ( statusIds.map(statusId => (
<StatusContainer <StatusContainer
key={statusId} key={statusId}
id={statusId} id={statusId}
@ -79,6 +80,18 @@ export default class StatusList extends ImmutablePureComponent {
)) ))
) : null; ) : null;
if (scrollableContent && featuredStatusIds) {
scrollableContent = featuredStatusIds.map(statusId => (
<StatusContainer
key={`f-${statusId}`}
id={statusId}
featured
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
/>
)).concat(scrollableContent);
}
return ( return (
<ScrollableList {...other} ref={this.setRef}> <ScrollableList {...other} ref={this.setRef}>
{scrollableContent} {scrollableContent}

@ -1,3 +1,4 @@
import { openDropdownMenu, closeDropdownMenu } from '../actions/dropdown_menu';
import { openModal, closeModal } from '../actions/modal'; import { openModal, closeModal } from '../actions/modal';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import DropdownMenu from '../components/dropdown_menu'; import DropdownMenu from '../components/dropdown_menu';
@ -5,12 +6,22 @@ import { isUserTouching } from '../is_mobile';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
isModalOpen: state.get('modal').modalType === 'ACTIONS', isModalOpen: state.get('modal').modalType === 'ACTIONS',
dropdownPlacement: state.getIn(['dropdown_menu', 'placement']),
openDropdownId: state.getIn(['dropdown_menu', 'openId']),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch, { status, items }) => ({
isUserTouching, onOpen(id, onItemClick, dropdownPlacement) {
onModalOpen: props => dispatch(openModal('ACTIONS', props)), dispatch(isUserTouching() ? openModal('ACTIONS', {
onModalClose: () => dispatch(closeModal()), status,
actions: items,
onClick: onItemClick,
}) : openDropdownMenu(id, dropdownPlacement));
},
onClose(id) {
dispatch(closeModal());
dispatch(closeDropdownMenu(id));
},
}); });
export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu); export default connect(mapStateToProps, mapDispatchToProps)(DropdownMenu);

@ -13,6 +13,7 @@ const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
follow: { id: 'account.follow', defaultMessage: 'Follow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
}); });
class Avatar extends ImmutablePureComponent { class Avatar extends ImmutablePureComponent {
@ -69,6 +70,7 @@ export default class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -80,11 +82,20 @@ export default class Header extends ImmutablePureComponent {
} }
let info = ''; let info = '';
let mutingInfo = '';
let actionBtn = ''; let actionBtn = '';
let lockedIcon = ''; let lockedIcon = '';
if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>; info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>;
} else if (me !== account.get('id') && account.getIn(['relationship', 'blocking'])) {
info = <span className='account--follows-info'><FormattedMessage id='account.blocked' defaultMessage='Blocked' /></span>;
}
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>;
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
mutingInfo = <span className='account--muting-info'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>;
} }
if (me !== account.get('id')) { if (me !== account.get('id')) {
@ -100,6 +111,12 @@ export default class Header extends ImmutablePureComponent {
<IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} /> <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
</div> </div>
); );
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = (
<div className='account--action-button'>
<IconButton size={26} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />
</div>
);
} }
} }
@ -124,6 +141,7 @@ export default class Header extends ImmutablePureComponent {
<div className='account__header__content' dangerouslySetInnerHTML={content} /> <div className='account__header__content' dangerouslySetInnerHTML={content} />
{info} {info}
{mutingInfo}
{actionBtn} {actionBtn}
</div> </div>
</div> </div>

@ -2,6 +2,7 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
import { displaySensitiveMedia } from '../../../initial_state';
export default class MediaItem extends ImmutablePureComponent { export default class MediaItem extends ImmutablePureComponent {
@ -9,8 +10,22 @@ export default class MediaItem extends ImmutablePureComponent {
media: ImmutablePropTypes.map.isRequired, media: ImmutablePropTypes.map.isRequired,
}; };
state = {
visible: !this.props.media.getIn(['status', 'sensitive']) || displaySensitiveMedia,
};
handleClick = () => {
if (!this.state.visible) {
this.setState({ visible: true });
return true;
}
return false;
}
render () { render () {
const { media } = this.props; const { media } = this.props;
const { visible } = this.state;
const status = media.get('status'); const status = media.get('status');
const focusX = media.getIn(['meta', 'focus', 'x']); const focusX = media.getIn(['meta', 'focus', 'x']);
const focusY = media.getIn(['meta', 'focus', 'y']); const focusY = media.getIn(['meta', 'focus', 'y']);
@ -18,21 +33,28 @@ export default class MediaItem extends ImmutablePureComponent {
const y = ((focusY / -2) + .5) * 100; const y = ((focusY / -2) + .5) * 100;
const style = {}; const style = {};
let content; let label, icon;
if (media.get('type') === 'gifv') { if (media.get('type') === 'gifv') {
content = <span className='media-gallery__gifv__label'>GIF</span>; label = <span className='media-gallery__gifv__label'>GIF</span>;
} }
if (!status.get('sensitive')) { if (visible) {
style.backgroundImage = `url(${media.get('preview_url')})`; style.backgroundImage = `url(${media.get('preview_url')})`;
style.backgroundPosition = `${x}% ${y}%`; style.backgroundPosition = `${x}% ${y}%`;
} else {
icon = (
<span className='account-gallery__item__icons'>
<i className='fa fa-eye-slash' />
</span>
);
} }
return ( return (
<div className='account-gallery__item'> <div className='account-gallery__item'>
<Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style}> <Permalink to={`/statuses/${status.get('id')}`} href={status.get('url')} style={style} onInterceptClick={this.handleClick}>
{content} {icon}
{label}
</Permalink> </Permalink>
</div> </div>
); );

@ -21,6 +21,7 @@ export default class Header extends ImmutablePureComponent {
onMute: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired,
onBlockDomain: PropTypes.func.isRequired, onBlockDomain: PropTypes.func.isRequired,
onUnblockDomain: PropTypes.func.isRequired, onUnblockDomain: PropTypes.func.isRequired,
hideTabs: PropTypes.bool,
}; };
static contextTypes = { static contextTypes = {
@ -68,7 +69,7 @@ export default class Header extends ImmutablePureComponent {
} }
render () { render () {
const { account } = this.props; const { account, hideTabs } = this.props;
if (account === null) { if (account === null) {
return <MissingIndicator />; return <MissingIndicator />;
@ -81,6 +82,7 @@ export default class Header extends ImmutablePureComponent {
<InnerHeader <InnerHeader
account={account} account={account}
onFollow={this.handleFollow} onFollow={this.handleFollow}
onBlock={this.handleBlock}
/> />
<ActionBar <ActionBar
@ -94,11 +96,13 @@ export default class Header extends ImmutablePureComponent {
onUnblockDomain={this.handleUnblockDomain} onUnblockDomain={this.handleUnblockDomain}
/> />
{!hideTabs && (
<div className='account__section-headline'> <div className='account__section-headline'>
<NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink> <NavLink exact to={`/accounts/${account.get('id')}`}><FormattedMessage id='account.posts' defaultMessage='Toots' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink> <NavLink exact to={`/accounts/${account.get('id')}/with_replies`}><FormattedMessage id='account.posts_with_replies' defaultMessage='Toots with replies' /></NavLink>
<NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink> <NavLink exact to={`/accounts/${account.get('id')}/media`}><FormattedMessage id='account.media' defaultMessage='Media' /></NavLink>
</div> </div>
)}
</div> </div>
); );
} }

@ -3,7 +3,7 @@ import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { fetchAccount } from '../../actions/accounts'; import { fetchAccount } from '../../actions/accounts';
import { refreshAccountTimeline, expandAccountTimeline } from '../../actions/timelines'; import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
import StatusList from '../../components/status_list'; import StatusList from '../../components/status_list';
import LoadingIndicator from '../../components/loading_indicator'; import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@ -17,6 +17,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false })
return { return {
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
featuredStatusIds: state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']), hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']),
}; };
@ -29,31 +30,36 @@ export default class AccountTimeline extends ImmutablePureComponent {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list, statusIds: ImmutablePropTypes.list,
featuredStatusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
withReplies: PropTypes.bool, withReplies: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
this.props.dispatch(fetchAccount(this.props.params.accountId)); const { params: { accountId }, withReplies } = this.props;
this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies));
this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(refreshAccountFeaturedTimeline(accountId));
this.props.dispatch(refreshAccountTimeline(accountId, withReplies));
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId)); this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId));
this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies)); this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies));
} }
} }
handleScrollToBottom = () => { handleLoadMore = () => {
if (!this.props.isLoading && this.props.hasMore) { if (!this.props.isLoading && this.props.hasMore) {
this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies)); this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies));
} }
} }
render () { render () {
const { statusIds, isLoading, hasMore } = this.props; const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
if (!statusIds && isLoading) { if (!statusIds && isLoading) {
return ( return (
@ -71,9 +77,10 @@ export default class AccountTimeline extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} />} prepend={<HeaderContainer accountId={this.props.params.accountId} />}
scrollKey='account_timeline' scrollKey='account_timeline'
statusIds={statusIds} statusIds={statusIds}
featuredStatusIds={featuredStatusIds}
isLoading={isLoading} isLoading={isLoading}
hasMore={hasMore} hasMore={hasMore}
onScrollToBottom={this.handleScrollToBottom} onLoadMore={this.handleLoadMore}
/> />
</Column> </Column>
); );

@ -50,6 +50,7 @@ export default class ComposeForm extends ImmutablePureComponent {
onPaste: PropTypes.func.isRequired, onPaste: PropTypes.func.isRequired,
onPickEmoji: PropTypes.func.isRequired, onPickEmoji: PropTypes.func.isRequired,
showSearch: PropTypes.bool, showSearch: PropTypes.bool,
anyMedia: PropTypes.bool,
}; };
static defaultProps = { static defaultProps = {
@ -142,10 +143,10 @@ export default class ComposeForm extends ImmutablePureComponent {
} }
render () { render () {
const { intl, onPaste, showSearch } = this.props; const { intl, onPaste, showSearch, anyMedia } = this.props;
const disabled = this.props.is_submitting; const disabled = this.props.is_submitting;
const text = [this.props.spoiler_text, countableText(this.props.text)].join(''); const text = [this.props.spoiler_text, countableText(this.props.text)].join('');
const disabledButton = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
let publishText = ''; let publishText = '';
if (this.props.privacy === 'private' || this.props.privacy === 'direct') { if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
@ -203,7 +204,7 @@ export default class ComposeForm extends ImmutablePureComponent {
</div> </div>
<div className='compose-form__publish'> <div className='compose-form__publish'>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div> <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabledButton} block /></div>
</div> </div>
</div> </div>
); );

@ -23,6 +23,7 @@ const mapStateToProps = state => ({
is_submitting: state.getIn(['compose', 'is_submitting']), is_submitting: state.getIn(['compose', 'is_submitting']),
is_uploading: state.getIn(['compose', 'is_uploading']), is_uploading: state.getIn(['compose', 'is_uploading']),
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({

@ -62,7 +62,7 @@ export default class Favourites extends ImmutablePureComponent {
this.column = c; this.column = c;
} }
handleScrollToBottom = debounce(() => { handleLoadMore = debounce(() => {
this.props.dispatch(expandFavouritedStatuses()); this.props.dispatch(expandFavouritedStatuses());
}, 300, { leading: true }) }, 300, { leading: true })
@ -89,7 +89,7 @@ export default class Favourites extends ImmutablePureComponent {
scrollKey={`favourited_statuses-${columnId}`} scrollKey={`favourited_statuses-${columnId}`}
hasMore={hasMore} hasMore={hasMore}
isLoading={isLoading} isLoading={isLoading}
onScrollToBottom={this.handleScrollToBottom} onLoadMore={this.handleLoadMore}
/> />
</Column> </Column>
); );

@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
<ScrollContainer scrollKey='followers'> <ScrollContainer scrollKey='followers'>
<div className='scrollable' onScroll={this.handleScroll}> <div className='scrollable' onScroll={this.handleScroll}>
<div className='followers'> <div className='followers'>
<HeaderContainer accountId={this.props.params.accountId} /> <HeaderContainer accountId={this.props.params.accountId} hideTabs />
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
{loadMore} {loadMore}
</div> </div>

@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
<ScrollContainer scrollKey='following'> <ScrollContainer scrollKey='following'>
<div className='scrollable' onScroll={this.handleScroll}> <div className='scrollable' onScroll={this.handleScroll}>
<div className='following'> <div className='following'>
<HeaderContainer accountId={this.props.params.accountId} /> <HeaderContainer accountId={this.props.params.accountId} hideTabs />
{accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)} {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
{loadMore} {loadMore}
</div> </div>

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
export default class ClearColumnButton extends React.Component { export default class ClearColumnButton extends React.PureComponent {
static propTypes = { static propTypes = {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,

@ -50,8 +50,14 @@ export default class Notifications extends React.PureComponent {
trackScroll: true, trackScroll: true,
}; };
handleScrollToBottom = debounce(() => { componentWillUnmount () {
this.handleLoadMore.cancel();
this.handleScrollToTop.cancel();
this.handleScroll.cancel();
this.props.dispatch(scrollTopNotifications(false)); this.props.dispatch(scrollTopNotifications(false));
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandNotifications()); this.props.dispatch(expandNotifications());
}, 300, { leading: true }); }, 300, { leading: true });
@ -136,7 +142,7 @@ export default class Notifications extends React.PureComponent {
isLoading={isLoading} isLoading={isLoading}
hasMore={hasMore} hasMore={hasMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
onScrollToBottom={this.handleScrollToBottom} onLoadMore={this.handleLoadMore}
onScrollToTop={this.handleScrollToTop} onScrollToTop={this.handleScrollToTop}
onScroll={this.handleScroll} onScroll={this.handleScroll}
shouldUpdateScroll={shouldUpdateScroll} shouldUpdateScroll={shouldUpdateScroll}

@ -2,6 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import noop from 'lodash/noop';
import StatusContent from '../../../components/status_content';
import { MediaGallery, Video } from '../../ui/util/async-components';
import Bundle from '../../ui/components/bundle';
export default class StatusCheckBox extends React.PureComponent { export default class StatusCheckBox extends React.PureComponent {
@ -14,18 +18,48 @@ export default class StatusCheckBox extends React.PureComponent {
render () { render () {
const { status, checked, onToggle, disabled } = this.props; const { status, checked, onToggle, disabled } = this.props;
const content = { __html: status.get('contentHtml') }; let media = null;
if (status.get('reblog')) { if (status.get('reblog')) {
return null; return null;
} }
if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const video = status.getIn(['media_attachments', 0]);
media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (
<Component
preview={video.get('preview_url')}
src={video.get('url')}
width={239}
height={110}
inline
sensitive={status.get('sensitive')}
onOpenVideo={noop}
/>
)}
</Bundle>
);
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={noop} />}
</Bundle>
);
}
}
return ( return (
<div className='status-check-box'> <div className='status-check-box'>
<div <div className='status-check-box__status'>
className='status__content' <StatusContent status={status} />
dangerouslySetInnerHTML={content} {media}
/> </div>
<div className='status-check-box-toggle'> <div className='status-check-box-toggle'>
<Toggle checked={checked} onChange={onToggle} disabled={disabled} /> <Toggle checked={checked} onChange={onToggle} disabled={disabled} />

@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
const emptyComponent = () => null; const emptyComponent = () => null;
const noop = () => { }; const noop = () => { };
class Bundle extends React.Component { class Bundle extends React.PureComponent {
static propTypes = { static propTypes = {
fetchComponent: PropTypes.func.isRequired, fetchComponent: PropTypes.func.isRequired,

@ -13,7 +13,7 @@ const messages = defineMessages({
retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' }, retry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' },
}); });
class BundleColumnError extends React.Component { class BundleColumnError extends React.PureComponent {
static propTypes = { static propTypes = {
onRetry: PropTypes.func.isRequired, onRetry: PropTypes.func.isRequired,

@ -10,7 +10,7 @@ const messages = defineMessages({
close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' }, close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
}); });
class BundleModalError extends React.Component { class BundleModalError extends React.PureComponent {
static propTypes = { static propTypes = {
onRetry: PropTypes.func.isRequired, onRetry: PropTypes.func.isRequired,

@ -103,8 +103,8 @@ export default class FocalPointModal extends ImmutablePureComponent {
const height = media.getIn(['meta', 'original', 'height']) || null; const height = media.getIn(['meta', 'original', 'height']) || null;
return ( return (
<div className='modal-root__modal media-modal'> <div className='modal-root__modal video-modal'>
<div className={classNames('media-modal__content focal-point', { dragging })} ref={this.setRef}> <div className={classNames('focal-point', { dragging })} ref={this.setRef}>
<ImageLoader <ImageLoader
previewSrc={media.get('preview_url')} previewSrc={media.get('preview_url')}
src={media.get('url')} src={media.get('url')}

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import ZoomableImage from './zoomable_image';
export default class ImageLoader extends React.PureComponent { export default class ImageLoader extends React.PureComponent {
@ -10,6 +11,7 @@ export default class ImageLoader extends React.PureComponent {
previewSrc: PropTypes.string, previewSrc: PropTypes.string,
width: PropTypes.number, width: PropTypes.number,
height: PropTypes.number, height: PropTypes.number,
onClick: PropTypes.func,
} }
static defaultProps = { static defaultProps = {
@ -24,6 +26,7 @@ export default class ImageLoader extends React.PureComponent {
} }
removers = []; removers = [];
canvas = null;
get canvasContext() { get canvasContext() {
if (!this.canvas) { if (!this.canvas) {
@ -43,6 +46,10 @@ export default class ImageLoader extends React.PureComponent {
} }
} }
componentWillUnmount () {
this.removeEventListeners();
}
loadImage (props) { loadImage (props) {
this.removeEventListeners(); this.removeEventListeners();
this.setState({ loading: true, error: false }); this.setState({ loading: true, error: false });
@ -118,7 +125,7 @@ export default class ImageLoader extends React.PureComponent {
} }
render () { render () {
const { alt, src, width, height } = this.props; const { alt, src, width, height, onClick } = this.props;
const { loading } = this.state; const { loading } = this.state;
const className = classNames('image-loader', { const className = classNames('image-loader', {
@ -128,21 +135,18 @@ export default class ImageLoader extends React.PureComponent {
return ( return (
<div className={className}> <div className={className}>
{loading ? (
<canvas <canvas
className='image-loader__preview-canvas' className='image-loader__preview-canvas'
ref={this.setCanvasRef}
width={width} width={width}
height={height} height={height}
ref={this.setCanvasRef}
style={{ opacity: loading ? 1 : 0 }}
/> />
) : (
{!loading && ( <ZoomableImage
<img
alt={alt} alt={alt}
className='image-loader__img'
src={src} src={src}
width={width} onClick={onClick}
height={height}
/> />
)} )}
</div> </div>

@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ExtendedVideoPlayer from '../../../components/extended_video_player'; import ExtendedVideoPlayer from '../../../components/extended_video_player';
import classNames from 'classnames';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@ -26,6 +27,7 @@ export default class MediaModal extends ImmutablePureComponent {
state = { state = {
index: null, index: null,
navigationHidden: false,
}; };
handleSwipe = (index) => { handleSwipe = (index) => {
@ -68,14 +70,21 @@ export default class MediaModal extends ImmutablePureComponent {
return this.state.index !== null ? this.state.index : this.props.index; return this.state.index !== null ? this.state.index : this.props.index;
} }
toggleNavigation = () => {
this.setState(prevState => ({
navigationHidden: !prevState.navigationHidden,
}));
};
render () { render () {
const { media, intl, onClose } = this.props; const { media, intl, onClose } = this.props;
const { navigationHidden } = this.state;
const index = this.getIndex(); const index = this.getIndex();
let pagination = []; let pagination = [];
const leftNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>; const leftNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><i className='fa fa-fw fa-chevron-left' /></button>;
const rightNav = media.size > 1 && <button tabIndex='0' className='modal-container__nav modal-container__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>; const rightNav = media.size > 1 && <button tabIndex='0' className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><i className='fa fa-fw fa-chevron-right' /></button>;
if (media.size > 1) { if (media.size > 1) {
pagination = media.map((item, i) => { pagination = media.map((item, i) => {
@ -92,9 +101,30 @@ export default class MediaModal extends ImmutablePureComponent {
const height = image.getIn(['meta', 'original', 'height']) || null; const height = image.getIn(['meta', 'original', 'height']) || null;
if (image.get('type') === 'image') { if (image.get('type') === 'image') {
return <ImageLoader previewSrc={image.get('preview_url')} src={image.get('url')} width={width} height={height} alt={image.get('description')} key={image.get('url')} />; return (
<ImageLoader
previewSrc={image.get('preview_url')}
src={image.get('url')}
width={width}
height={height}
alt={image.get('description')}
key={image.get('url')}
onClick={this.toggleNavigation}
/>
);
} else if (image.get('type') === 'gifv') { } else if (image.get('type') === 'gifv') {
return <ExtendedVideoPlayer src={image.get('url')} muted controls={false} width={width} height={height} key={image.get('preview_url')} alt={image.get('description')} />; return (
<ExtendedVideoPlayer
src={image.get('url')}
muted
controls={false}
width={width}
height={height}
key={image.get('preview_url')}
alt={image.get('description')}
onClick={this.toggleNavigation}
/>
);
} }
return null; return null;
@ -104,21 +134,43 @@ export default class MediaModal extends ImmutablePureComponent {
alignItems: 'center', // center vertically alignItems: 'center', // center vertically
}; };
const navigationClassName = classNames('media-modal__navigation', {
'media-modal__navigation--hidden': navigationHidden,
});
return ( return (
<div className='modal-root__modal media-modal'> <div className='modal-root__modal media-modal'>
{leftNav} <div
className='media-modal__closer'
role='presentation'
onClick={onClose}
>
<div className='media-modal__content'> <div className='media-modal__content'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} /> <ReactSwipeableViews
<ReactSwipeableViews containerStyle={containerStyle} onChangeIndex={this.handleSwipe} index={index}> style={{
// you can't use 100vh, because the viewport height is taller
// than the visible part of the document in some mobile
// browsers when it's address bar is visible.
// https://developers.google.com/web/updates/2016/12/url-bar-resizing
height: `${document.body.clientHeight}px`,
}}
containerStyle={containerStyle}
onChangeIndex={this.handleSwipe}
onSwitching={this.handleSwitching}
index={index}
>
{content} {content}
</ReactSwipeableViews> </ReactSwipeableViews>
</div> </div>
</div>
<div className={navigationClassName}>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
{leftNav}
{rightNav}
<ul className='media-modal__pagination'> <ul className='media-modal__pagination'>
{pagination} {pagination}
</ul> </ul>
</div>
{rightNav}
</div> </div>
); );
} }

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom'; import { NavLink, withRouter } from 'react-router-dom';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage, injectIntl } from 'react-intl';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { isUserTouching } from '../../../is_mobile'; import { isUserTouching } from '../../../is_mobile';
@ -24,14 +24,12 @@ export function getLink (index) {
} }
@injectIntl @injectIntl
export default class TabsBar extends React.Component { @withRouter
export default class TabsBar extends React.PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
}
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
} }
setRef = ref => { setRef = ref => {
@ -59,7 +57,7 @@ export default class TabsBar extends React.Component {
const listener = debounce(() => { const listener = debounce(() => {
nextTab.removeEventListener('transitionend', listener); nextTab.removeEventListener('transitionend', listener);
this.context.router.history.push(to); this.props.history.push(to);
}, 50); }, 50);
nextTab.addEventListener('transitionend', listener); nextTab.addEventListener('transitionend', listener);

@ -16,7 +16,7 @@ export default class VideoModal extends ImmutablePureComponent {
const { media, time, onClose } = this.props; const { media, time, onClose } = this.props;
return ( return (
<div className='modal-root__modal media-modal'> <div className='modal-root__modal video-modal'>
<div> <div>
<Video <Video
preview={media.get('preview_url')} preview={media.get('preview_url')}

@ -0,0 +1,151 @@
import React from 'react';
import PropTypes from 'prop-types';
const MIN_SCALE = 1;
const MAX_SCALE = 4;
const getMidpoint = (p1, p2) => ({
x: (p1.clientX + p2.clientX) / 2,
y: (p1.clientY + p2.clientY) / 2,
});
const getDistance = (p1, p2) =>
Math.sqrt(Math.pow(p1.clientX - p2.clientX, 2) + Math.pow(p1.clientY - p2.clientY, 2));
const clamp = (min, max, value) => Math.min(max, Math.max(min, value));
export default class ZoomableImage extends React.PureComponent {
static propTypes = {
alt: PropTypes.string,
src: PropTypes.string.isRequired,
width: PropTypes.number,
height: PropTypes.number,
onClick: PropTypes.func,
}
static defaultProps = {
alt: '',
width: null,
height: null,
};
state = {
scale: MIN_SCALE,
}
removers = [];
container = null;
image = null;
lastTouchEndTime = 0;
lastDistance = 0;
componentDidMount () {
let handler = this.handleTouchStart;
this.container.addEventListener('touchstart', handler);
this.removers.push(() => this.container.removeEventListener('touchstart', handler));
handler = this.handleTouchMove;
// on Chrome 56+, touch event listeners will default to passive
// https://www.chromestatus.com/features/5093566007214080
this.container.addEventListener('touchmove', handler, { passive: false });
this.removers.push(() => this.container.removeEventListener('touchend', handler));
}
componentWillUnmount () {
this.removeEventListeners();
}
removeEventListeners () {
this.removers.forEach(listeners => listeners());
this.removers = [];
}
handleTouchStart = e => {
if (e.touches.length !== 2) return;
this.lastDistance = getDistance(...e.touches);
}
handleTouchMove = e => {
const { scrollTop, scrollHeight, clientHeight } = this.container;
if (e.touches.length === 1 && scrollTop !== scrollHeight - clientHeight) {
// prevent propagating event to MediaModal
e.stopPropagation();
return;
}
if (e.touches.length !== 2) return;
e.preventDefault();
e.stopPropagation();
const distance = getDistance(...e.touches);
const midpoint = getMidpoint(...e.touches);
const scale = clamp(MIN_SCALE, MAX_SCALE, this.state.scale * distance / this.lastDistance);
this.zoom(scale, midpoint);
this.lastMidpoint = midpoint;
this.lastDistance = distance;
}
zoom(nextScale, midpoint) {
const { scale } = this.state;
const { scrollLeft, scrollTop } = this.container;
// math memo:
// x = (scrollLeft + midpoint.x) / scrollWidth
// x' = (nextScrollLeft + midpoint.x) / nextScrollWidth
// scrollWidth = clientWidth * scale
// scrollWidth' = clientWidth * nextScale
// Solve x = x' for nextScrollLeft
const nextScrollLeft = (scrollLeft + midpoint.x) * nextScale / scale - midpoint.x;
const nextScrollTop = (scrollTop + midpoint.y) * nextScale / scale - midpoint.y;
this.setState({ scale: nextScale }, () => {
this.container.scrollLeft = nextScrollLeft;
this.container.scrollTop = nextScrollTop;
});
}
handleClick = e => {
// don't propagate event to MediaModal
e.stopPropagation();
const handler = this.props.onClick;
if (handler) handler();
}
setContainerRef = c => {
this.container = c;
}
setImageRef = c => {
this.image = c;
}
render () {
const { alt, src } = this.props;
const { scale } = this.state;
const overflow = scale === 1 ? 'hidden' : 'scroll';
return (
<div
className='zoomable-image'
ref={this.setContainerRef}
style={{ overflow }}
>
<img
role='presentation'
ref={this.setImageRef}
alt={alt}
src={src}
style={{
transform: `scale(${scale})`,
transformOrigin: '0 0',
}}
onClick={this.handleClick}
/>
</div>
);
}
}

@ -56,10 +56,7 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({ const mapDispatchToProps = (dispatch, { timelineId, loadMore }) => ({
onScrollToBottom: debounce(() => { onLoadMore: debounce(loadMore, 300, { leading: true }),
dispatch(scrollTopTimeline(timelineId, false));
loadMore();
}, 300, { leading: true }),
onScrollToTop: debounce(() => { onScrollToTop: debounce(() => {
dispatch(scrollTopTimeline(timelineId, true)); dispatch(scrollTopTimeline(timelineId, true));

@ -1,3 +1,4 @@
import classNames from 'classnames';
import React from 'react'; import React from 'react';
import NotificationsContainer from './containers/notifications_container'; import NotificationsContainer from './containers/notifications_container';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -55,6 +56,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({ const mapStateToProps = state => ({
isComposing: state.getIn(['compose', 'is_composing']), isComposing: state.getIn(['compose', 'is_composing']),
hasComposingText: state.getIn(['compose', 'text']) !== '', hasComposingText: state.getIn(['compose', 'text']) !== '',
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
}); });
const keyMap = { const keyMap = {
@ -84,10 +86,93 @@ const keyMap = {
goToMuted: 'g m', goToMuted: 'g m',
}; };
class SwitchingColumnsArea extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
location: PropTypes.object,
onLayoutChange: PropTypes.func.isRequired,
};
state = {
mobile: isMobile(window.innerWidth),
};
componentWillMount () {
window.addEventListener('resize', this.handleResize, { passive: true });
}
componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.node.handleChildrenContentChange();
}
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize);
}
handleResize = debounce(() => {
// The cached heights are no longer accurate, invalidate
this.props.onLayoutChange();
this.setState({ mobile: isMobile(window.innerWidth) });
}, 500, {
trailing: true,
});
setRef = c => {
this.node = c.getWrappedInstance().getWrappedInstance();
}
render () {
const { children } = this.props;
const { mobile } = this.state;
return (
<ColumnsAreaContainer ref={this.setRef} singleColumn={mobile}>
<WrappedSwitch>
<Redirect from='/' to='/getting-started' exact />
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute component={GenericNotFound} content={children} />
</WrappedSwitch>
</ColumnsAreaContainer>
);
}
}
@connect(mapStateToProps) @connect(mapStateToProps)
@injectIntl @injectIntl
@withRouter @withRouter
export default class UI extends React.Component { export default class UI extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object.isRequired, router: PropTypes.object.isRequired,
@ -100,10 +185,10 @@ export default class UI extends React.Component {
hasComposingText: PropTypes.bool, hasComposingText: PropTypes.bool,
location: PropTypes.object, location: PropTypes.object,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
}; };
state = { state = {
width: window.innerWidth,
draggingOver: false, draggingOver: false,
}; };
@ -118,14 +203,10 @@ export default class UI extends React.Component {
} }
} }
handleResize = debounce(() => { handleLayoutChange = () => {
// The cached heights are no longer accurate, invalidate // The cached heights are no longer accurate, invalidate
this.props.dispatch(clearHeight()); this.props.dispatch(clearHeight());
}
this.setState({ width: window.innerWidth });
}, 500, {
trailing: true,
});
handleDragEnter = (e) => { handleDragEnter = (e) => {
e.preventDefault(); e.preventDefault();
@ -193,7 +274,6 @@ export default class UI extends React.Component {
componentWillMount () { componentWillMount () {
window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false);
window.addEventListener('resize', this.handleResize, { passive: true });
document.addEventListener('dragenter', this.handleDragEnter, false); document.addEventListener('dragenter', this.handleDragEnter, false);
document.addEventListener('dragover', this.handleDragOver, false); document.addEventListener('dragover', this.handleDragOver, false);
document.addEventListener('drop', this.handleDrop, false); document.addEventListener('drop', this.handleDrop, false);
@ -214,28 +294,8 @@ export default class UI extends React.Component {
}; };
} }
shouldComponentUpdate (nextProps) {
if (nextProps.isComposing !== this.props.isComposing) {
// Avoid expensive update just to toggle a class
this.node.classList.toggle('is-composing', nextProps.isComposing);
return false;
}
// Why isn't this working?!?
// return super.shouldComponentUpdate(nextProps, nextState);
return true;
}
componentDidUpdate (prevProps) {
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
this.columnsAreaNode.handleChildrenContentChange();
}
}
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('beforeunload', this.handleBeforeUnload); window.removeEventListener('beforeunload', this.handleBeforeUnload);
window.removeEventListener('resize', this.handleResize);
document.removeEventListener('dragenter', this.handleDragEnter); document.removeEventListener('dragenter', this.handleDragEnter);
document.removeEventListener('dragover', this.handleDragOver); document.removeEventListener('dragover', this.handleDragOver);
document.removeEventListener('drop', this.handleDrop); document.removeEventListener('drop', this.handleDrop);
@ -247,10 +307,6 @@ export default class UI extends React.Component {
this.node = c; this.node = c;
} }
setColumnsAreaRef = c => {
this.columnsAreaNode = c.getWrappedInstance().getWrappedInstance();
}
handleHotkeyNew = e => { handleHotkeyNew = e => {
e.preventDefault(); e.preventDefault();
@ -350,8 +406,8 @@ export default class UI extends React.Component {
} }
render () { render () {
const { width, draggingOver } = this.state; const { draggingOver } = this.state;
const { children } = this.props; const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
const handlers = { const handlers = {
help: this.handleHotkeyToggleHelp, help: this.handleHotkeyToggleHelp,
@ -374,43 +430,12 @@ export default class UI extends React.Component {
return ( return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}> <HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef}>
<div className='ui' ref={this.setRef}> <div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<TabsBar /> <TabsBar />
<ColumnsAreaContainer ref={this.setColumnsAreaRef} singleColumn={isMobile(width)}> <SwitchingColumnsArea location={location} onLayoutChange={this.handleLayoutChange}>
<WrappedSwitch> {children}
<Redirect from='/' to='/getting-started' exact /> </SwitchingColumnsArea>
<WrappedRoute path='/getting-started' component={GettingStarted} content={children} />
<WrappedRoute path='/keyboard-shortcuts' component={KeyboardShortcuts} content={children} />
<WrappedRoute path='/timelines/home' component={HomeTimeline} content={children} />
<WrappedRoute path='/timelines/public' exact component={PublicTimeline} content={children} />
<WrappedRoute path='/timelines/public/local' component={CommunityTimeline} content={children} />
<WrappedRoute path='/timelines/tag/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/timelines/list/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/statuses/new' component={Compose} content={children} />
<WrappedRoute path='/statuses/:statusId' exact component={Status} content={children} />
<WrappedRoute path='/statuses/:statusId/reblogs' component={Reblogs} content={children} />
<WrappedRoute path='/statuses/:statusId/favourites' component={Favourites} content={children} />
<WrappedRoute path='/accounts/:accountId' exact component={AccountTimeline} content={children} />
<WrappedRoute path='/accounts/:accountId/with_replies' component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
<WrappedRoute path='/accounts/:accountId/followers' component={Followers} content={children} />
<WrappedRoute path='/accounts/:accountId/following' component={Following} content={children} />
<WrappedRoute path='/accounts/:accountId/media' component={AccountGallery} content={children} />
<WrappedRoute path='/follow_requests' component={FollowRequests} content={children} />
<WrappedRoute path='/blocks' component={Blocks} content={children} />
<WrappedRoute path='/mutes' component={Mutes} content={children} />
<WrappedRoute path='/lists' component={Lists} content={children} />
<WrappedRoute component={GenericNotFound} content={children} />
</WrappedSwitch>
</ColumnsAreaContainer>
<NotificationsContainer /> <NotificationsContainer />
<LoadingBarContainer className='loading-bar' /> <LoadingBarContainer className='loading-bar' />

@ -14,6 +14,7 @@ function loadPolyfills() {
const needsBasePolyfills = !( const needsBasePolyfills = !(
window.Intl && window.Intl &&
Object.assign && Object.assign &&
Object.values &&
Number.isNaN && Number.isNaN &&
window.Symbol && window.Symbol &&
Array.prototype.includes Array.prototype.includes

@ -1,7 +1,9 @@
{ {
"account.block": "حظر @{name}", "account.block": "حظر @{name}",
"account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}", "account.block_domain": "إخفاء كل شيئ قادم من إسم النطاق {domain}",
"account.blocked": "محظور",
"account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.", "account.disclaimer_full": "قد لا تعكس المعلومات أدناه الملف الشخصي الكامل للمستخدم.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "تعديل الملف الشخصي", "account.edit_profile": "تعديل الملف الشخصي",
"account.follow": "تابِع", "account.follow": "تابِع",
"account.followers": "المتابعون", "account.followers": "المتابعون",
@ -13,8 +15,9 @@
"account.moved_to": "{name} إنتقل إلى :", "account.moved_to": "{name} إنتقل إلى :",
"account.mute": "أكتم @{name}", "account.mute": "أكتم @{name}",
"account.mute_notifications": "كتم إخطارات @{name}", "account.mute_notifications": "كتم إخطارات @{name}",
"account.muted": "مكتوم",
"account.posts": "التبويقات", "account.posts": "التبويقات",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "تبويقات تحتوي على رُدود",
"account.report": "أبلغ عن @{name}", "account.report": "أبلغ عن @{name}",
"account.requested": "في انتظار الموافقة", "account.requested": "في انتظار الموافقة",
"account.share": "مشاركة @{name}'s profile", "account.share": "مشاركة @{name}'s profile",
@ -208,21 +211,22 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "إلغاء", "reply_indicator.cancel": "إلغاء",
"report.forward": "Forward to {target}", "report.forward": "التحويل إلى {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "هذا الحساب ينتمي إلى خادوم آخَر. هل تودّ إرسال نسخة مجهولة مِن التقرير إلى هنالك أيضًا ؟",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:",
"report.placeholder": "تعليقات إضافية", "report.placeholder": "تعليقات إضافية",
"report.submit": "إرسال", "report.submit": "إرسال",
"report.target": "إبلاغ", "report.target": "إبلاغ",
"search.placeholder": "ابحث", "search.placeholder": "ابحث",
"search_popout.search_format": "نمط البحث المتقدم", "search_popout.search_format": "نمط البحث المتقدم",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "وسم", "search_popout.tips.hashtag": "وسم",
"search_popout.tips.status": "حالة", "search_popout.tips.status": "حالة",
"search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية", "search_popout.tips.text": "جملة قصيرة تُمكّنُك من عرض أسماء و حسابات و كلمات رمزية",
"search_popout.tips.user": "مستخدِم", "search_popout.tips.user": "مستخدِم",
"search_results.accounts": "People", "search_results.accounts": "أشخاص",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "الوُسوم",
"search_results.statuses": "Toots", "search_results.statuses": "التبويقات",
"search_results.total": "{count, number} {count, plural, one {result} و {results}}", "search_results.total": "{count, number} {count, plural, one {result} و {results}}",
"standalone.public_title": "نظرة على ...", "standalone.public_title": "نظرة على ...",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@ -238,6 +242,7 @@
"status.mute_conversation": "كتم المحادثة", "status.mute_conversation": "كتم المحادثة",
"status.open": "وسع هذه المشاركة", "status.open": "وسع هذه المشاركة",
"status.pin": "تدبيس على الملف الشخصي", "status.pin": "تدبيس على الملف الشخصي",
"status.pinned": "تبويق مثبَّت",
"status.reblog": "رَقِّي", "status.reblog": "رَقِّي",
"status.reblogged_by": "{name} رقى", "status.reblogged_by": "{name} رقى",
"status.reply": "ردّ", "status.reply": "ردّ",
@ -250,7 +255,6 @@
"status.show_more": "أظهر المزيد", "status.show_more": "أظهر المزيد",
"status.unmute_conversation": "فك الكتم عن المحادثة", "status.unmute_conversation": "فك الكتم عن المحادثة",
"status.unpin": "فك التدبيس من الملف الشخصي", "status.unpin": "فك التدبيس من الملف الشخصي",
"tabs_bar.compose": "تحرير",
"tabs_bar.federated_timeline": "الموحَّد", "tabs_bar.federated_timeline": "الموحَّد",
"tabs_bar.home": "الرئيسية", "tabs_bar.home": "الرئيسية",
"tabs_bar.local_timeline": "المحلي", "tabs_bar.local_timeline": "المحلي",
@ -259,7 +263,7 @@
"upload_area.title": "إسحب ثم أفلت للرفع", "upload_area.title": "إسحب ثم أفلت للرفع",
"upload_button.label": "إضافة وسائط", "upload_button.label": "إضافة وسائط",
"upload_form.description": "وصف للمعاقين بصريا", "upload_form.description": "وصف للمعاقين بصريا",
"upload_form.focus": "Crop", "upload_form.focus": "قص",
"upload_form.undo": "إلغاء", "upload_form.undo": "إلغاء",
"upload_progress.label": "يرفع...", "upload_progress.label": "يرفع...",
"video.close": "إغلاق الفيديو", "video.close": "إغلاق الفيديو",

@ -1,7 +1,9 @@
{ {
"account.block": "Блокирай", "account.block": "Блокирай",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Редактирай профила си", "account.edit_profile": "Редактирай профила си",
"account.follow": "Последвай", "account.follow": "Последвай",
"account.followers": "Последователи", "account.followers": "Последователи",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}", "account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Публикации", "account.posts": "Публикации",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}", "account.report": "Report @{name}",
@ -216,6 +219,7 @@
"report.target": "Reporting", "report.target": "Reporting",
"search.placeholder": "Търсене", "search.placeholder": "Търсене",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Expand this status", "status.open": "Expand this status",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Споделяне", "status.reblog": "Споделяне",
"status.reblogged_by": "{name} сподели", "status.reblogged_by": "{name} сподели",
"status.reply": "Отговор", "status.reply": "Отговор",
@ -250,7 +255,6 @@
"status.show_more": "Show more", "status.show_more": "Show more",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Съставяне",
"tabs_bar.federated_timeline": "Federated", "tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Начало", "tabs_bar.home": "Начало",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloca @{name}", "account.block": "Bloca @{name}",
"account.block_domain": "Amaga-ho tot de {domain}", "account.block_domain": "Amaga-ho tot de {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.", "account.disclaimer_full": "La informació següent pot reflectir incompleta el perfil de l'usuari.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edita el perfil", "account.edit_profile": "Edita el perfil",
"account.follow": "Segueix", "account.follow": "Segueix",
"account.followers": "Seguidors", "account.followers": "Seguidors",
@ -13,8 +15,9 @@
"account.moved_to": "{name} s'ha mogut a:", "account.moved_to": "{name} s'ha mogut a:",
"account.mute": "Silencia @{name}", "account.mute": "Silencia @{name}",
"account.mute_notifications": "Notificacions desactivades de @{name}", "account.mute_notifications": "Notificacions desactivades de @{name}",
"account.muted": "Muted",
"account.posts": "Toots", "account.posts": "Toots",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots amb respostes",
"account.report": "Informe @{name}", "account.report": "Informe @{name}",
"account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment", "account.requested": "Esperant aprovació. Clic per a cancel·lar la petició de seguiment",
"account.share": "Comparteix el perfil de @{name}", "account.share": "Comparteix el perfil de @{name}",
@ -208,20 +211,21 @@
"relative_time.minutes": "fa {number} minuts", "relative_time.minutes": "fa {number} minuts",
"relative_time.seconds": "fa {number} segons", "relative_time.seconds": "fa {number} segons",
"reply_indicator.cancel": "Cancel·lar", "reply_indicator.cancel": "Cancel·lar",
"report.forward": "Forward to {target}", "report.forward": "Reenvia a {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "Aquest compte és d'un altre servidor. Enviar-hi també una copia anònima del informe?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "El informe s'enviarà als moderadors de la teva instància. Pots explicar perquè vols informar d'aquest compte aquí:",
"report.placeholder": "Comentaris addicionals", "report.placeholder": "Comentaris addicionals",
"report.submit": "Enviar", "report.submit": "Enviar",
"report.target": "Informes", "report.target": "Informes",
"search.placeholder": "Cercar", "search.placeholder": "Cercar",
"search_popout.search_format": "Format de cerca avançada", "search_popout.search_format": "Format de cerca avançada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta", "search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags", "search_popout.tips.text": "El text simple retorna coincidències amb els noms de visualització, els noms d'usuari i els hashtags",
"search_popout.tips.user": "usuari", "search_popout.tips.user": "usuari",
"search_results.accounts": "People", "search_results.accounts": "Gent",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Etiquetes",
"search_results.statuses": "Toots", "search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, un {result} altres {results}}", "search_results.total": "{count, number} {count, plural, un {result} altres {results}}",
"standalone.public_title": "Una mirada a l'interior ...", "standalone.public_title": "Una mirada a l'interior ...",
@ -238,6 +242,7 @@
"status.mute_conversation": "Silenciar conversació", "status.mute_conversation": "Silenciar conversació",
"status.open": "Ampliar aquest estat", "status.open": "Ampliar aquest estat",
"status.pin": "Fixat en el perfil", "status.pin": "Fixat en el perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Impuls", "status.reblog": "Impuls",
"status.reblogged_by": "{name} ha retootejat", "status.reblogged_by": "{name} ha retootejat",
"status.reply": "Respondre", "status.reply": "Respondre",
@ -250,7 +255,6 @@
"status.show_more": "Mostra més", "status.show_more": "Mostra més",
"status.unmute_conversation": "Activar conversació", "status.unmute_conversation": "Activar conversació",
"status.unpin": "Deslliga del perfil", "status.unpin": "Deslliga del perfil",
"tabs_bar.compose": "Compondre",
"tabs_bar.federated_timeline": "Federada", "tabs_bar.federated_timeline": "Federada",
"tabs_bar.home": "Inici", "tabs_bar.home": "Inici",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",
@ -259,7 +263,7 @@
"upload_area.title": "Arrossega i deixa anar per carregar", "upload_area.title": "Arrossega i deixa anar per carregar",
"upload_button.label": "Afegir multimèdia", "upload_button.label": "Afegir multimèdia",
"upload_form.description": "Descriure els problemes visuals", "upload_form.description": "Descriure els problemes visuals",
"upload_form.focus": "Crop", "upload_form.focus": "Retallar",
"upload_form.undo": "Desfer", "upload_form.undo": "Desfer",
"upload_progress.label": "Pujant...", "upload_progress.label": "Pujant...",
"video.close": "Tancar el vídeo", "video.close": "Tancar el vídeo",

@ -1,7 +1,9 @@
{ {
"account.block": "@{name} blocken", "account.block": "@{name} blocken",
"account.block_domain": "Alles von {domain} verstecken", "account.block_domain": "Alles von {domain} verstecken",
"account.blocked": "Blocked",
"account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.", "account.disclaimer_full": "Das Profil wird möglicherweise unvollständig wiedergegeben.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Profil bearbeiten", "account.edit_profile": "Profil bearbeiten",
"account.follow": "Folgen", "account.follow": "Folgen",
"account.followers": "Folgende", "account.followers": "Folgende",
@ -13,6 +15,7 @@
"account.moved_to": "{name} ist umgezogen auf:", "account.moved_to": "{name} ist umgezogen auf:",
"account.mute": "@{name} stummschalten", "account.mute": "@{name} stummschalten",
"account.mute_notifications": "Benachrichtigungen von @{name} verbergen", "account.mute_notifications": "Benachrichtigungen von @{name} verbergen",
"account.muted": "Muted",
"account.posts": "Beiträge", "account.posts": "Beiträge",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "@{name} melden", "account.report": "@{name} melden",
@ -216,6 +219,7 @@
"report.target": "{target} melden", "report.target": "{target} melden",
"search.placeholder": "Suche", "search.placeholder": "Suche",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Thread stummschalten", "status.mute_conversation": "Thread stummschalten",
"status.open": "Diesen Beitrag öffnen", "status.open": "Diesen Beitrag öffnen",
"status.pin": "Im Profil anheften", "status.pin": "Im Profil anheften",
"status.pinned": "Pinned toot",
"status.reblog": "Teilen", "status.reblog": "Teilen",
"status.reblogged_by": "{name} teilte", "status.reblogged_by": "{name} teilte",
"status.reply": "Antworten", "status.reply": "Antworten",
@ -250,7 +255,6 @@
"status.show_more": "Mehr anzeigen", "status.show_more": "Mehr anzeigen",
"status.unmute_conversation": "Stummschaltung von Thread aufheben", "status.unmute_conversation": "Stummschaltung von Thread aufheben",
"status.unpin": "Vom Profil lösen", "status.unpin": "Vom Profil lösen",
"tabs_bar.compose": "Schreiben",
"tabs_bar.federated_timeline": "Föderation", "tabs_bar.federated_timeline": "Föderation",
"tabs_bar.home": "Startseite", "tabs_bar.home": "Startseite",
"tabs_bar.local_timeline": "Lokal", "tabs_bar.local_timeline": "Lokal",

@ -274,6 +274,10 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "Pinned toot",
"id": "status.pinned"
},
{ {
"defaultMessage": "{name} boosted", "defaultMessage": "{name} boosted",
"id": "status.reblogged_by" "id": "status.reblogged_by"
@ -469,9 +473,25 @@
"defaultMessage": "Awaiting approval. Click to cancel follow request", "defaultMessage": "Awaiting approval. Click to cancel follow request",
"id": "account.requested" "id": "account.requested"
}, },
{
"defaultMessage": "Unblock @{name}",
"id": "account.unblock"
},
{ {
"defaultMessage": "Follows you", "defaultMessage": "Follows you",
"id": "account.follows_you" "id": "account.follows_you"
},
{
"defaultMessage": "Blocked",
"id": "account.blocked"
},
{
"defaultMessage": "Muted",
"id": "account.muted"
},
{
"defaultMessage": "Domain hidden",
"id": "account.domain_blocked"
} }
], ],
"path": "app/javascript/mastodon/features/account/components/header.json" "path": "app/javascript/mastodon/features/account/components/header.json"
@ -683,6 +703,14 @@
"defaultMessage": "Search", "defaultMessage": "Search",
"id": "search.placeholder" "id": "search.placeholder"
}, },
{
"defaultMessage": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"id": "search_popout.tips.full_text"
},
{
"defaultMessage": "Simple text returns matching display names, usernames and hashtags",
"id": "search_popout.tips.text"
},
{ {
"defaultMessage": "Advanced search format", "defaultMessage": "Advanced search format",
"id": "search_popout.search_format" "id": "search_popout.search_format"
@ -698,10 +726,6 @@
{ {
"defaultMessage": "status", "defaultMessage": "status",
"id": "search_popout.tips.status" "id": "search_popout.tips.status"
},
{
"defaultMessage": "Simple text returns matching display names, usernames and hashtags",
"id": "search_popout.tips.text"
} }
], ],
"path": "app/javascript/mastodon/features/compose/components/search.json" "path": "app/javascript/mastodon/features/compose/components/search.json"
@ -1589,6 +1613,10 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "Close",
"id": "lightbox.close"
},
{ {
"defaultMessage": "Additional comments", "defaultMessage": "Additional comments",
"id": "report.placeholder" "id": "report.placeholder"
@ -1618,10 +1646,6 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "Compose",
"id": "tabs_bar.compose"
},
{ {
"defaultMessage": "Home", "defaultMessage": "Home",
"id": "tabs_bar.home" "id": "tabs_bar.home"

@ -1,7 +1,9 @@
{ {
"account.block": "Block @{name}", "account.block": "Block @{name}",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edit profile", "account.edit_profile": "Edit profile",
"account.follow": "Follow", "account.follow": "Follow",
"account.followers": "Followers", "account.followers": "Followers",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}", "account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Toots", "account.posts": "Toots",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}", "account.report": "Report @{name}",
@ -223,6 +226,7 @@
"report.target": "Reporting {target}", "report.target": "Reporting {target}",
"search.placeholder": "Search", "search.placeholder": "Search",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -245,6 +249,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Expand this status", "status.open": "Expand this status",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Boost", "status.reblog": "Boost",
"status.reblogged_by": "{name} boosted", "status.reblogged_by": "{name} boosted",
"status.reply": "Reply", "status.reply": "Reply",
@ -257,7 +262,6 @@
"status.show_more": "Show more", "status.show_more": "Show more",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Compose",
"tabs_bar.federated_timeline": "Federated", "tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home", "tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloki @{name}", "account.block": "Bloki @{name}",
"account.block_domain": "Kaŝi ĉion de {domain}", "account.block_domain": "Kaŝi ĉion de {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.", "account.disclaimer_full": "Subaj informoj povas reflekti la profilon de la uzanto nekomplete.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Redakti profilon", "account.edit_profile": "Redakti profilon",
"account.follow": "Sekvi", "account.follow": "Sekvi",
"account.followers": "Sekvantoj", "account.followers": "Sekvantoj",
@ -13,8 +15,9 @@
"account.moved_to": "{name} moviĝis al:", "account.moved_to": "{name} moviĝis al:",
"account.mute": "Silentigi @{name}", "account.mute": "Silentigi @{name}",
"account.mute_notifications": "Silentigi sciigojn el @{name}", "account.mute_notifications": "Silentigi sciigojn el @{name}",
"account.posts": "Hupoj", "account.muted": "Muted",
"account.posts_with_replies": "Toots with replies", "account.posts": "Mesaĝoj",
"account.posts_with_replies": "Mesaĝoj kun respondoj",
"account.report": "Signali @{name}", "account.report": "Signali @{name}",
"account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado", "account.requested": "Atendo de aprobo. Alklaku por nuligi peton de sekvado",
"account.share": "Diskonigi la profilon de @{name}", "account.share": "Diskonigi la profilon de @{name}",
@ -147,7 +150,7 @@
"navigation_bar.edit_profile": "Redakti profilon", "navigation_bar.edit_profile": "Redakti profilon",
"navigation_bar.favourites": "Stelumoj", "navigation_bar.favourites": "Stelumoj",
"navigation_bar.follow_requests": "Petoj de sekvado", "navigation_bar.follow_requests": "Petoj de sekvado",
"navigation_bar.info": "Pri ĉiu tiu nodo", "navigation_bar.info": "Pri ĉi tiu nodo",
"navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj", "navigation_bar.keyboard_shortcuts": "Klavaraj mallongigoj",
"navigation_bar.lists": "Listoj", "navigation_bar.lists": "Listoj",
"navigation_bar.logout": "Elsaluti", "navigation_bar.logout": "Elsaluti",
@ -208,21 +211,22 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Nuligi", "reply_indicator.cancel": "Nuligi",
"report.forward": "Forward to {target}", "report.forward": "Plusendi al {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "La konto estas en alia servilo. Ĉu sendi sennomigitan kopion de la signalo ankaŭ tien?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "La signalo estos sendita al la kontrolantoj de via nodo. Vi povas doni klarigon pri kial vi signalas ĉi tiun konton sube:",
"report.placeholder": "Pliaj komentoj", "report.placeholder": "Pliaj komentoj",
"report.submit": "Sendi", "report.submit": "Sendi",
"report.target": "Signali {target}", "report.target": "Signali {target}",
"search.placeholder": "Serĉi", "search.placeholder": "Serĉi",
"search_popout.search_format": "Detala serĉo", "search_popout.search_format": "Detala serĉo",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "kradvorto", "search_popout.tips.hashtag": "kradvorto",
"search_popout.tips.status": "mesaĝoj", "search_popout.tips.status": "mesaĝoj",
"search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn", "search_popout.tips.text": "Simpla teksto montras la kongruajn afiŝitajn nomojn, uzantnomojn kaj kradvortojn",
"search_popout.tips.user": "uzanto", "search_popout.tips.user": "uzanto",
"search_results.accounts": "People", "search_results.accounts": "Homoj",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Kradvortoj",
"search_results.statuses": "Toots", "search_results.statuses": "Mesaĝoj",
"search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}", "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezultoj}}",
"standalone.public_title": "Enrigardo…", "standalone.public_title": "Enrigardo…",
"status.block": "Bloki @{name}", "status.block": "Bloki @{name}",
@ -238,6 +242,7 @@
"status.mute_conversation": "Silentigi konversacion", "status.mute_conversation": "Silentigi konversacion",
"status.open": "Grandigi ĉi tiun mesaĝon", "status.open": "Grandigi ĉi tiun mesaĝon",
"status.pin": "Alpingli en la profilo", "status.pin": "Alpingli en la profilo",
"status.pinned": "Pinned toot",
"status.reblog": "Diskonigi", "status.reblog": "Diskonigi",
"status.reblogged_by": "{name} diskonigis", "status.reblogged_by": "{name} diskonigis",
"status.reply": "Respondi", "status.reply": "Respondi",
@ -250,7 +255,6 @@
"status.show_more": "Grandigi", "status.show_more": "Grandigi",
"status.unmute_conversation": "Malsilentigi konversacion", "status.unmute_conversation": "Malsilentigi konversacion",
"status.unpin": "Depingli de profilo", "status.unpin": "Depingli de profilo",
"tabs_bar.compose": "Ekskribi",
"tabs_bar.federated_timeline": "Fratara tempolinio", "tabs_bar.federated_timeline": "Fratara tempolinio",
"tabs_bar.home": "Hejmo", "tabs_bar.home": "Hejmo",
"tabs_bar.local_timeline": "Loka tempolinio", "tabs_bar.local_timeline": "Loka tempolinio",
@ -259,7 +263,7 @@
"upload_area.title": "Altreni kaj lasi por alŝuti", "upload_area.title": "Altreni kaj lasi por alŝuti",
"upload_button.label": "Aldoni aŭdovidaĵon", "upload_button.label": "Aldoni aŭdovidaĵon",
"upload_form.description": "Priskribi por misvidantaj homoj", "upload_form.description": "Priskribi por misvidantaj homoj",
"upload_form.focus": "Crop", "upload_form.focus": "Stuci",
"upload_form.undo": "Malfari", "upload_form.undo": "Malfari",
"upload_progress.label": "Alŝutado…", "upload_progress.label": "Alŝutado…",
"video.close": "Fermi videon", "video.close": "Fermi videon",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloquear", "account.block": "Bloquear",
"account.block_domain": "Ocultar todo de {domain}", "account.block_domain": "Ocultar todo de {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.", "account.disclaimer_full": "La siguiente información del usuario puede estar incompleta.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
"account.follow": "Seguir", "account.follow": "Seguir",
"account.followers": "Seguidores", "account.followers": "Seguidores",
@ -13,6 +15,7 @@
"account.moved_to": "{name} se ha mudado a:", "account.moved_to": "{name} se ha mudado a:",
"account.mute": "Silenciar a @{name}", "account.mute": "Silenciar a @{name}",
"account.mute_notifications": "Silenciar notificaciones de @{name}", "account.mute_notifications": "Silenciar notificaciones de @{name}",
"account.muted": "Muted",
"account.posts": "Publicaciones", "account.posts": "Publicaciones",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Reportar a @{name}", "account.report": "Reportar a @{name}",
@ -216,6 +219,7 @@
"report.target": "Reportando", "report.target": "Reportando",
"search.placeholder": "Buscar", "search.placeholder": "Buscar",
"search_popout.search_format": "Formato de búsqueda avanzada", "search_popout.search_format": "Formato de búsqueda avanzada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta", "search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag", "search_popout.tips.text": "El texto simple devuelve correspondencias de nombre, usuario y hashtag",
@ -238,6 +242,7 @@
"status.mute_conversation": "Silenciar conversación", "status.mute_conversation": "Silenciar conversación",
"status.open": "Expandir estado", "status.open": "Expandir estado",
"status.pin": "Fijar", "status.pin": "Fijar",
"status.pinned": "Pinned toot",
"status.reblog": "Retootear", "status.reblog": "Retootear",
"status.reblogged_by": "Retooteado por {name}", "status.reblogged_by": "Retooteado por {name}",
"status.reply": "Responder", "status.reply": "Responder",
@ -250,7 +255,6 @@
"status.show_more": "Mostrar más", "status.show_more": "Mostrar más",
"status.unmute_conversation": "Dejar de silenciar conversación", "status.unmute_conversation": "Dejar de silenciar conversación",
"status.unpin": "Dejar de fijar", "status.unpin": "Dejar de fijar",
"tabs_bar.compose": "Redactar",
"tabs_bar.federated_timeline": "Federado", "tabs_bar.federated_timeline": "Federado",
"tabs_bar.home": "Inicio", "tabs_bar.home": "Inicio",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "مسدودسازی @{name}", "account.block": "مسدودسازی @{name}",
"account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}", "account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.", "account.disclaimer_full": "اطلاعات زیر ممکن است نمایهٔ این کاربر را به تمامی نشان ندهد.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "ویرایش نمایه", "account.edit_profile": "ویرایش نمایه",
"account.follow": "پی بگیرید", "account.follow": "پی بگیرید",
"account.followers": "پیگیران", "account.followers": "پیگیران",
@ -13,6 +15,7 @@
"account.moved_to": "{name} منتقل شده است به:", "account.moved_to": "{name} منتقل شده است به:",
"account.mute": "بی‌صدا کردن @{name}", "account.mute": "بی‌صدا کردن @{name}",
"account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}", "account.mute_notifications": "بی‌صداکردن اعلان‌ها از طرف @{name}",
"account.muted": "Muted",
"account.posts": "نوشته‌ها", "account.posts": "نوشته‌ها",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "گزارش @{name}", "account.report": "گزارش @{name}",
@ -216,6 +219,7 @@
"report.target": "گزارش‌دادن", "report.target": "گزارش‌دادن",
"search.placeholder": "جستجو", "search.placeholder": "جستجو",
"search_popout.search_format": "راهنمای جستجوی پیشرفته", "search_popout.search_format": "راهنمای جستجوی پیشرفته",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "هشتگ", "search_popout.tips.hashtag": "هشتگ",
"search_popout.tips.status": "نوشته", "search_popout.tips.status": "نوشته",
"search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها", "search_popout.tips.text": "جستجوی متنی ساده برای نام‌ها، نام‌های کاربری، و هشتگ‌ها",
@ -238,6 +242,7 @@
"status.mute_conversation": "بی‌صداکردن گفتگو", "status.mute_conversation": "بی‌صداکردن گفتگو",
"status.open": "این نوشته را باز کن", "status.open": "این نوشته را باز کن",
"status.pin": "نوشتهٔ ثابت نمایه", "status.pin": "نوشتهٔ ثابت نمایه",
"status.pinned": "Pinned toot",
"status.reblog": "بازبوقیدن", "status.reblog": "بازبوقیدن",
"status.reblogged_by": "{name} بازبوقید", "status.reblogged_by": "{name} بازبوقید",
"status.reply": "پاسخ", "status.reply": "پاسخ",
@ -250,7 +255,6 @@
"status.show_more": "نمایش", "status.show_more": "نمایش",
"status.unmute_conversation": "باصداکردن گفتگو", "status.unmute_conversation": "باصداکردن گفتگو",
"status.unpin": "برداشتن نوشتهٔ ثابت نمایه", "status.unpin": "برداشتن نوشتهٔ ثابت نمایه",
"tabs_bar.compose": "بنویسید",
"tabs_bar.federated_timeline": "همگانی", "tabs_bar.federated_timeline": "همگانی",
"tabs_bar.home": "خانه", "tabs_bar.home": "خانه",
"tabs_bar.local_timeline": "محلی", "tabs_bar.local_timeline": "محلی",

@ -1,7 +1,9 @@
{ {
"account.block": "Estä @{name}", "account.block": "Estä @{name}",
"account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}", "account.block_domain": "Piilota kaikki sisältö verkkotunnuksesta {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.", "account.disclaimer_full": "Alla olevat käyttäjän profiilitiedot saattavat olla epätäydellisiä.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Muokkaa", "account.edit_profile": "Muokkaa",
"account.follow": "Seuraa", "account.follow": "Seuraa",
"account.followers": "Seuraajia", "account.followers": "Seuraajia",
@ -13,6 +15,7 @@
"account.moved_to": "{name} on muuttanut instanssiin:", "account.moved_to": "{name} on muuttanut instanssiin:",
"account.mute": "Mykistä @{name}", "account.mute": "Mykistä @{name}",
"account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}", "account.mute_notifications": "Mykistä ilmoitukset käyttäjältä @{name}",
"account.muted": "Muted",
"account.posts": "Töötit", "account.posts": "Töötit",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}", "account.report": "Report @{name}",
@ -216,6 +219,7 @@
"report.target": "Reporting", "report.target": "Reporting",
"search.placeholder": "Hae", "search.placeholder": "Hae",
"search_popout.search_format": "Tarkennettu haku", "search_popout.search_format": "Tarkennettu haku",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtagi", "search_popout.tips.hashtag": "hashtagi",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit", "search_popout.tips.text": "Pelkkä tekstihaku palauttaa hakua vastaavat nimimerkit, käyttäjänimet ja hastagit",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mykistä keskustelu", "status.mute_conversation": "Mykistä keskustelu",
"status.open": "Laajenna statuspäivitys", "status.open": "Laajenna statuspäivitys",
"status.pin": "Kiinnitä profiiliin", "status.pin": "Kiinnitä profiiliin",
"status.pinned": "Pinned toot",
"status.reblog": "Buustaa", "status.reblog": "Buustaa",
"status.reblogged_by": "{name} buustasi", "status.reblogged_by": "{name} buustasi",
"status.reply": "Vastaa", "status.reply": "Vastaa",
@ -250,7 +255,6 @@
"status.show_more": "Näytä lisää", "status.show_more": "Näytä lisää",
"status.unmute_conversation": "Poista mykistys keskustelulta", "status.unmute_conversation": "Poista mykistys keskustelulta",
"status.unpin": "Irrota profiilista", "status.unpin": "Irrota profiilista",
"tabs_bar.compose": "Luo",
"tabs_bar.federated_timeline": "Federated", "tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Koti", "tabs_bar.home": "Koti",
"tabs_bar.local_timeline": "Paikallinen", "tabs_bar.local_timeline": "Paikallinen",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloquer @{name}", "account.block": "Bloquer @{name}",
"account.block_domain": "Tout masquer venant de {domain}", "account.block_domain": "Tout masquer venant de {domain}",
"account.blocked": "Bloqué",
"account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.", "account.disclaimer_full": "Les données ci-dessous peuvent ne pas refléter ce profil dans sa totalité.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modifier le profil", "account.edit_profile": "Modifier le profil",
"account.follow": "Suivre", "account.follow": "Suivre",
"account.followers": "Abonné⋅e⋅s", "account.followers": "Abonné⋅e⋅s",
@ -13,8 +15,9 @@
"account.moved_to": "{name} a déménagé vers :", "account.moved_to": "{name} a déménagé vers :",
"account.mute": "Masquer @{name}", "account.mute": "Masquer @{name}",
"account.mute_notifications": "Ignorer les notifications de @{name}", "account.mute_notifications": "Ignorer les notifications de @{name}",
"account.posts": "Statuts", "account.muted": "Silencé",
"account.posts_with_replies": "Toots with replies", "account.posts": "Pouets",
"account.posts_with_replies": "Pouets avec réponses",
"account.report": "Signaler", "account.report": "Signaler",
"account.requested": "Invitation envoyée", "account.requested": "Invitation envoyée",
"account.share": "Partager le profil de @{name}", "account.share": "Partager le profil de @{name}",
@ -208,21 +211,22 @@
"relative_time.minutes": "{number} min", "relative_time.minutes": "{number} min",
"relative_time.seconds": "{number} s", "relative_time.seconds": "{number} s",
"reply_indicator.cancel": "Annuler", "reply_indicator.cancel": "Annuler",
"report.forward": "Forward to {target}", "report.forward": "Transférer à {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "Le compte provient d'un autre serveur. Envoyez également une copie anonyme du rapport ?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "Le rapport sera envoyé aux modérateurs de votre instance. Vous pouvez expliquer pourquoi vous signalez ce compte ci-dessous :",
"report.placeholder": "Commentaires additionnels", "report.placeholder": "Commentaires additionnels",
"report.submit": "Envoyer", "report.submit": "Envoyer",
"report.target": "Signalement", "report.target": "Signalement",
"search.placeholder": "Rechercher", "search.placeholder": "Rechercher",
"search_popout.search_format": "Recherche avancée", "search_popout.search_format": "Recherche avancée",
"search_popout.tips.full_text": "Les textes simples retournent les pouets que vous avez écris, mis en favori, épinglés, ou ayant été mentionnés, ainsi que les noms d'utilisateurs, les noms affichés, et les hashtags correspondant.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "statuts", "search_popout.tips.status": "statuts",
"search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms dutilisateur⋅ice et les hashtags correspondants", "search_popout.tips.text": "Un texte simple renvoie les noms affichés, les noms dutilisateur⋅ice et les hashtags correspondants",
"search_popout.tips.user": "utilisateur⋅ice", "search_popout.tips.user": "utilisateur⋅ice",
"search_results.accounts": "People", "search_results.accounts": "Personnes",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots", "search_results.statuses": "Pouets",
"search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}", "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
"standalone.public_title": "Jeter un coup dœil…", "standalone.public_title": "Jeter un coup dœil…",
"status.block": "Block @{name}", "status.block": "Block @{name}",
@ -238,6 +242,7 @@
"status.mute_conversation": "Masquer la conversation", "status.mute_conversation": "Masquer la conversation",
"status.open": "Déplier ce statut", "status.open": "Déplier ce statut",
"status.pin": "Épingler sur le profil", "status.pin": "Épingler sur le profil",
"status.pinned": "Pouet épinglé",
"status.reblog": "Partager", "status.reblog": "Partager",
"status.reblogged_by": "{name} a partagé:", "status.reblogged_by": "{name} a partagé:",
"status.reply": "Répondre", "status.reply": "Répondre",
@ -250,7 +255,6 @@
"status.show_more": "Déplier", "status.show_more": "Déplier",
"status.unmute_conversation": "Ne plus masquer la conversation", "status.unmute_conversation": "Ne plus masquer la conversation",
"status.unpin": "Retirer du profil", "status.unpin": "Retirer du profil",
"tabs_bar.compose": "Composer",
"tabs_bar.federated_timeline": "Fil public global", "tabs_bar.federated_timeline": "Fil public global",
"tabs_bar.home": "Accueil", "tabs_bar.home": "Accueil",
"tabs_bar.local_timeline": "Fil public local", "tabs_bar.local_timeline": "Fil public local",
@ -259,7 +263,7 @@
"upload_area.title": "Glissez et déposez pour envoyer", "upload_area.title": "Glissez et déposez pour envoyer",
"upload_button.label": "Joindre un média", "upload_button.label": "Joindre un média",
"upload_form.description": "Décrire pour les malvoyants", "upload_form.description": "Décrire pour les malvoyants",
"upload_form.focus": "Crop", "upload_form.focus": "Recadrer",
"upload_form.undo": "Annuler", "upload_form.undo": "Annuler",
"upload_progress.label": "Envoi en cours…", "upload_progress.label": "Envoi en cours…",
"video.close": "Fermer la vidéo", "video.close": "Fermer la vidéo",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloquear @{name}", "account.block": "Bloquear @{name}",
"account.block_domain": "Ocultar calquer contido de {domain}", "account.block_domain": "Ocultar calquer contido de {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.", "account.disclaimer_full": "A información inferior podería mostrar un perfil incompleto da usuaria.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
"account.follow": "Seguir", "account.follow": "Seguir",
"account.followers": "Seguidoras", "account.followers": "Seguidoras",
@ -13,6 +15,7 @@
"account.moved_to": "{name} marchou a:", "account.moved_to": "{name} marchou a:",
"account.mute": "Acalar @{name}", "account.mute": "Acalar @{name}",
"account.mute_notifications": "Acalar as notificacións de @{name}", "account.mute_notifications": "Acalar as notificacións de @{name}",
"account.muted": "Muted",
"account.posts": "Toots", "account.posts": "Toots",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Informar sobre @{name}", "account.report": "Informar sobre @{name}",
@ -216,6 +219,7 @@
"report.target": "Informar {target}", "report.target": "Informar {target}",
"search.placeholder": "Buscar", "search.placeholder": "Buscar",
"search_popout.search_format": "Formato de busca avanzada", "search_popout.search_format": "Formato de busca avanzada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta", "search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "estado", "search_popout.tips.status": "estado",
"search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas", "search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
@ -238,6 +242,7 @@
"status.mute_conversation": "Acalar conversa", "status.mute_conversation": "Acalar conversa",
"status.open": "Expandir este estado", "status.open": "Expandir este estado",
"status.pin": "Fixar no perfil", "status.pin": "Fixar no perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Promover", "status.reblog": "Promover",
"status.reblogged_by": "{name} promoveu", "status.reblogged_by": "{name} promoveu",
"status.reply": "Resposta", "status.reply": "Resposta",
@ -250,7 +255,6 @@
"status.show_more": "Mostrar máis", "status.show_more": "Mostrar máis",
"status.unmute_conversation": "Non acalar a conversa", "status.unmute_conversation": "Non acalar a conversa",
"status.unpin": "Despegar do perfil", "status.unpin": "Despegar do perfil",
"tabs_bar.compose": "Compoñer",
"tabs_bar.federated_timeline": "Federado", "tabs_bar.federated_timeline": "Federado",
"tabs_bar.home": "Inicio", "tabs_bar.home": "Inicio",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "חסימת @{name}", "account.block": "חסימת @{name}",
"account.block_domain": "להסתיר הכל מהקהילה {domain}", "account.block_domain": "להסתיר הכל מהקהילה {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.", "account.disclaimer_full": "המידע להלן עשוי להיות לא עדכני או לא שלם.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "עריכת פרופיל", "account.edit_profile": "עריכת פרופיל",
"account.follow": "מעקב", "account.follow": "מעקב",
"account.followers": "עוקבים", "account.followers": "עוקבים",
@ -13,6 +15,7 @@
"account.moved_to": "החשבון {name} הועבר אל:", "account.moved_to": "החשבון {name} הועבר אל:",
"account.mute": "להשתיק את @{name}", "account.mute": "להשתיק את @{name}",
"account.mute_notifications": "להסתיר התראות מאת @{name}", "account.mute_notifications": "להסתיר התראות מאת @{name}",
"account.muted": "Muted",
"account.posts": "הודעות", "account.posts": "הודעות",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "לדווח על @{name}", "account.report": "לדווח על @{name}",
@ -216,6 +219,7 @@
"report.target": "דיווח", "report.target": "דיווח",
"search.placeholder": "חיפוש", "search.placeholder": "חיפוש",
"search_popout.search_format": "מבנה חיפוש מתקדם", "search_popout.search_format": "מבנה חיפוש מתקדם",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "האשתג", "search_popout.tips.hashtag": "האשתג",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים", "search_popout.tips.text": "טקסט פשוט מחזיר כינויים, שמות משתמש והאשתגים",
@ -238,6 +242,7 @@
"status.mute_conversation": "השתקת שיחה", "status.mute_conversation": "השתקת שיחה",
"status.open": "הרחבת הודעה", "status.open": "הרחבת הודעה",
"status.pin": "לקבע באודות", "status.pin": "לקבע באודות",
"status.pinned": "Pinned toot",
"status.reblog": "הדהוד", "status.reblog": "הדהוד",
"status.reblogged_by": "הודהד על ידי {name}", "status.reblogged_by": "הודהד על ידי {name}",
"status.reply": "תגובה", "status.reply": "תגובה",
@ -250,7 +255,6 @@
"status.show_more": "הראה יותר", "status.show_more": "הראה יותר",
"status.unmute_conversation": "הסרת השתקת שיחה", "status.unmute_conversation": "הסרת השתקת שיחה",
"status.unpin": "לשחרר מקיבוע באודות", "status.unpin": "לשחרר מקיבוע באודות",
"tabs_bar.compose": "חיבור",
"tabs_bar.federated_timeline": "ציר זמן בין-קהילתי", "tabs_bar.federated_timeline": "ציר זמן בין-קהילתי",
"tabs_bar.home": "בבית", "tabs_bar.home": "בבית",
"tabs_bar.local_timeline": "ציר זמן מקומי", "tabs_bar.local_timeline": "ציר זמן מקומי",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokiraj @{name}", "account.block": "Blokiraj @{name}",
"account.block_domain": "Sakrij sve sa {domain}", "account.block_domain": "Sakrij sve sa {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.", "account.disclaimer_full": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Uredi profil", "account.edit_profile": "Uredi profil",
"account.follow": "Slijedi", "account.follow": "Slijedi",
"account.followers": "Sljedbenici", "account.followers": "Sljedbenici",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Utišaj @{name}", "account.mute": "Utišaj @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Postovi", "account.posts": "Postovi",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Prijavi @{name}", "account.report": "Prijavi @{name}",
@ -216,6 +219,7 @@
"report.target": "Prijavljivanje", "report.target": "Prijavljivanje",
"search.placeholder": "Traži", "search.placeholder": "Traži",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Utišaj razgovor", "status.mute_conversation": "Utišaj razgovor",
"status.open": "Proširi ovaj status", "status.open": "Proširi ovaj status",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Podigni", "status.reblog": "Podigni",
"status.reblogged_by": "{name} je podigao", "status.reblogged_by": "{name} je podigao",
"status.reply": "Odgovori", "status.reply": "Odgovori",
@ -250,7 +255,6 @@
"status.show_more": "Pokaži više", "status.show_more": "Pokaži više",
"status.unmute_conversation": "Poništi utišavanje razgovora", "status.unmute_conversation": "Poništi utišavanje razgovora",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Sastavi",
"tabs_bar.federated_timeline": "Federalni", "tabs_bar.federated_timeline": "Federalni",
"tabs_bar.home": "Dom", "tabs_bar.home": "Dom",
"tabs_bar.local_timeline": "Lokalno", "tabs_bar.local_timeline": "Lokalno",

@ -1,7 +1,9 @@
{ {
"account.block": "@{name} letiltása", "account.block": "@{name} letiltása",
"account.block_domain": "Minden elrejtése innen: {domain}", "account.block_domain": "Minden elrejtése innen: {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.", "account.disclaimer_full": "Az alul található információk hiányosan mutathatják be a felhasználót.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Profil szerkesztése", "account.edit_profile": "Profil szerkesztése",
"account.follow": "Követés", "account.follow": "Követés",
"account.followers": "Követők", "account.followers": "Követők",
@ -13,6 +15,7 @@
"account.moved_to": "{name} átköltözött:", "account.moved_to": "{name} átköltözött:",
"account.mute": "@{name} némítása", "account.mute": "@{name} némítása",
"account.mute_notifications": "@{name} értesítések némítása", "account.mute_notifications": "@{name} értesítések némítása",
"account.muted": "Muted",
"account.posts": "Státuszok", "account.posts": "Státuszok",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "@{name} jelentése", "account.report": "@{name} jelentése",
@ -216,6 +219,7 @@
"report.target": "Reporting", "report.target": "Reporting",
"search.placeholder": "Keresés", "search.placeholder": "Keresés",
"search_popout.search_format": "Fejlett keresés", "search_popout.search_format": "Fejlett keresés",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Beszélgetés némítása", "status.mute_conversation": "Beszélgetés némítása",
"status.open": "Státusz kinagyítása", "status.open": "Státusz kinagyítása",
"status.pin": "Kitűzés a profilra", "status.pin": "Kitűzés a profilra",
"status.pinned": "Pinned toot",
"status.reblog": "Reblog", "status.reblog": "Reblog",
"status.reblogged_by": "{name} reblogolta", "status.reblogged_by": "{name} reblogolta",
"status.reply": "Válasz", "status.reply": "Válasz",
@ -250,7 +255,6 @@
"status.show_more": "Többet", "status.show_more": "Többet",
"status.unmute_conversation": "Beszélgetés némításának elvonása", "status.unmute_conversation": "Beszélgetés némításának elvonása",
"status.unpin": "Kitűzés eltávolítása a profilról", "status.unpin": "Kitűzés eltávolítása a profilról",
"tabs_bar.compose": "Összeállítás",
"tabs_bar.federated_timeline": "Federált", "tabs_bar.federated_timeline": "Federált",
"tabs_bar.home": "Kezdőlap", "tabs_bar.home": "Kezdőlap",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "Արգելափակել @{name}֊ին", "account.block": "Արգելափակել @{name}֊ին",
"account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}", "account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։", "account.disclaimer_full": "Ներքոհիշյալը կարող է ոչ ամբողջությամբ արտացոլել օգտատիրոջ էջի տվյալները։",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Խմբագրել անձնական էջը", "account.edit_profile": "Խմբագրել անձնական էջը",
"account.follow": "Հետեւել", "account.follow": "Հետեւել",
"account.followers": "Հետեւվողներ", "account.followers": "Հետեւվողներ",
@ -13,6 +15,7 @@
"account.moved_to": "{name}֊ը տեղափոխվել է՝", "account.moved_to": "{name}֊ը տեղափոխվել է՝",
"account.mute": "Լռեցնել @{name}֊ին", "account.mute": "Լռեցնել @{name}֊ին",
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից", "account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
"account.muted": "Muted",
"account.posts": "Գրառումներ", "account.posts": "Գրառումներ",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Բողոքել @{name}֊ից", "account.report": "Բողոքել @{name}֊ից",
@ -216,6 +219,7 @@
"report.target": "Բողոքել {target}֊ի մասին", "report.target": "Բողոքել {target}֊ի մասին",
"search.placeholder": "Փնտրել", "search.placeholder": "Փնտրել",
"search_popout.search_format": "Փնտրելու առաջադեմ ձեւ", "search_popout.search_format": "Փնտրելու առաջադեմ ձեւ",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "պիտակ", "search_popout.tips.hashtag": "պիտակ",
"search_popout.tips.status": "թութ", "search_popout.tips.status": "թութ",
"search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ", "search_popout.tips.text": "Հասարակ տեքստը կվերադարձնի համընկնող անուններ, օգտանուններ ու պիտակներ",
@ -238,6 +242,7 @@
"status.mute_conversation": "Լռեցնել խոսակցությունը", "status.mute_conversation": "Լռեցնել խոսակցությունը",
"status.open": "Ընդարձակել այս թութը", "status.open": "Ընդարձակել այս թութը",
"status.pin": "Ամրացնել անձնական էջում", "status.pin": "Ամրացնել անձնական էջում",
"status.pinned": "Pinned toot",
"status.reblog": "Տարածել", "status.reblog": "Տարածել",
"status.reblogged_by": "{name} տարածել է", "status.reblogged_by": "{name} տարածել է",
"status.reply": "Պատասխանել", "status.reply": "Պատասխանել",
@ -250,7 +255,6 @@
"status.show_more": "Ավելին", "status.show_more": "Ավելին",
"status.unmute_conversation": "Ապալռեցնել խոսակցությունը", "status.unmute_conversation": "Ապալռեցնել խոսակցությունը",
"status.unpin": "Հանել անձնական էջից", "status.unpin": "Հանել անձնական էջից",
"tabs_bar.compose": "Շարադրել",
"tabs_bar.federated_timeline": "Դաշնային", "tabs_bar.federated_timeline": "Դաշնային",
"tabs_bar.home": "Հիմնական", "tabs_bar.home": "Հիմնական",
"tabs_bar.local_timeline": "Տեղական", "tabs_bar.local_timeline": "Տեղական",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokir @{name}", "account.block": "Blokir @{name}",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Ubah profil", "account.edit_profile": "Ubah profil",
"account.follow": "Ikuti", "account.follow": "Ikuti",
"account.followers": "Pengikut", "account.followers": "Pengikut",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Bisukan @{name}", "account.mute": "Bisukan @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Postingan", "account.posts": "Postingan",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Laporkan @{name}", "account.report": "Laporkan @{name}",
@ -216,6 +219,7 @@
"report.target": "Melaporkan", "report.target": "Melaporkan",
"search.placeholder": "Pencarian", "search.placeholder": "Pencarian",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Tampilkan status ini", "status.open": "Tampilkan status ini",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Boost", "status.reblog": "Boost",
"status.reblogged_by": "di-boost {name}", "status.reblogged_by": "di-boost {name}",
"status.reply": "Balas", "status.reply": "Balas",
@ -250,7 +255,6 @@
"status.show_more": "Tampilkan semua", "status.show_more": "Tampilkan semua",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Tulis",
"tabs_bar.federated_timeline": "Gabungan", "tabs_bar.federated_timeline": "Gabungan",
"tabs_bar.home": "Beranda", "tabs_bar.home": "Beranda",
"tabs_bar.local_timeline": "Lokal", "tabs_bar.local_timeline": "Lokal",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokusar @{name}", "account.block": "Blokusar @{name}",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modifikar profilo", "account.edit_profile": "Modifikar profilo",
"account.follow": "Sequar", "account.follow": "Sequar",
"account.followers": "Sequanti", "account.followers": "Sequanti",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Celar @{name}", "account.mute": "Celar @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Mesaji", "account.posts": "Mesaji",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Denuncar @{name}", "account.report": "Denuncar @{name}",
@ -216,6 +219,7 @@
"report.target": "Denuncante", "report.target": "Denuncante",
"search.placeholder": "Serchez", "search.placeholder": "Serchez",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Detaligar ca mesajo", "status.open": "Detaligar ca mesajo",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Repetar", "status.reblog": "Repetar",
"status.reblogged_by": "{name} repetita", "status.reblogged_by": "{name} repetita",
"status.reply": "Respondar", "status.reply": "Respondar",
@ -250,7 +255,6 @@
"status.show_more": "Montrar plue", "status.show_more": "Montrar plue",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Kompozar",
"tabs_bar.federated_timeline": "Federata", "tabs_bar.federated_timeline": "Federata",
"tabs_bar.home": "Hemo", "tabs_bar.home": "Hemo",
"tabs_bar.local_timeline": "Lokala", "tabs_bar.local_timeline": "Lokala",

@ -1,7 +1,9 @@
{ {
"account.block": "Blocca @{name}", "account.block": "Blocca @{name}",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modifica profilo", "account.edit_profile": "Modifica profilo",
"account.follow": "Segui", "account.follow": "Segui",
"account.followers": "Seguaci", "account.followers": "Seguaci",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Silenzia @{name}", "account.mute": "Silenzia @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Posts", "account.posts": "Posts",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Segnala @{name}", "account.report": "Segnala @{name}",
@ -216,6 +219,7 @@
"report.target": "Invio la segnalazione", "report.target": "Invio la segnalazione",
"search.placeholder": "Cerca", "search.placeholder": "Cerca",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Espandi questo post", "status.open": "Espandi questo post",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Condividi", "status.reblog": "Condividi",
"status.reblogged_by": "{name} ha condiviso", "status.reblogged_by": "{name} ha condiviso",
"status.reply": "Rispondi", "status.reply": "Rispondi",
@ -250,7 +255,6 @@
"status.show_more": "Mostra di più", "status.show_more": "Mostra di più",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Scrivi",
"tabs_bar.federated_timeline": "Federazione", "tabs_bar.federated_timeline": "Federazione",
"tabs_bar.home": "Home", "tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Locale", "tabs_bar.local_timeline": "Locale",

@ -1,7 +1,9 @@
{ {
"account.block": "@{name}さんをブロック", "account.block": "@{name}さんをブロック",
"account.block_domain": "{domain}全体を非表示", "account.block_domain": "{domain}全体を非表示",
"account.blocked": "ブロック済み",
"account.disclaimer_full": "以下の情報は不正確な可能性があります。", "account.disclaimer_full": "以下の情報は不正確な可能性があります。",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "プロフィールを編集", "account.edit_profile": "プロフィールを編集",
"account.follow": "フォロー", "account.follow": "フォロー",
"account.followers": "フォロワー", "account.followers": "フォロワー",
@ -13,8 +15,9 @@
"account.moved_to": "{name}さんは引っ越しました:", "account.moved_to": "{name}さんは引っ越しました:",
"account.mute": "@{name}さんをミュート", "account.mute": "@{name}さんをミュート",
"account.mute_notifications": "@{name}さんからの通知を受け取らない", "account.mute_notifications": "@{name}さんからの通知を受け取らない",
"account.muted": "ミュート済み",
"account.posts": "投稿", "account.posts": "投稿",
"account.posts_with_replies": "トゥートと返信", "account.posts_with_replies": "投稿と返信",
"account.report": "@{name}さんを通報", "account.report": "@{name}さんを通報",
"account.requested": "フォロー承認待ちです。クリックしてキャンセル", "account.requested": "フォロー承認待ちです。クリックしてキャンセル",
"account.share": "@{name}さんのプロフィールを共有する", "account.share": "@{name}さんのプロフィールを共有する",
@ -223,6 +226,7 @@
"report.target": "{target}さんを通報する", "report.target": "{target}さんを通報する",
"search.placeholder": "検索", "search.placeholder": "検索",
"search_popout.search_format": "高度な検索フォーマット", "search_popout.search_format": "高度な検索フォーマット",
"search_popout.tips.full_text": "表示名やユーザー名、ハッシュタグのほか、あなたのトゥートやお気に入り、ブーストしたトゥート、返信に一致する単純なテキスト。",
"search_popout.tips.hashtag": "ハッシュタグ", "search_popout.tips.hashtag": "ハッシュタグ",
"search_popout.tips.status": "トゥート", "search_popout.tips.status": "トゥート",
"search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト", "search_popout.tips.text": "表示名やユーザー名、ハッシュタグに一致する単純なテキスト",
@ -245,8 +249,9 @@
"status.mute_conversation": "会話をミュート", "status.mute_conversation": "会話をミュート",
"status.open": "詳細を表示", "status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示", "status.pin": "プロフィールに固定表示",
"status.pinned": "固定されたトゥート",
"status.reblog": "ブースト", "status.reblog": "ブースト",
"status.reblogged_by": "{name}さんにブーストされました", "status.reblogged_by": "{name}さんがブースト",
"status.reply": "返信", "status.reply": "返信",
"status.replyAll": "全員に返信", "status.replyAll": "全員に返信",
"status.report": "@{name}さんを通報", "status.report": "@{name}さんを通報",
@ -257,12 +262,11 @@
"status.show_more": "もっと見る", "status.show_more": "もっと見る",
"status.unmute_conversation": "会話のミュートを解除", "status.unmute_conversation": "会話のミュートを解除",
"status.unpin": "プロフィールの固定表示を解除", "status.unpin": "プロフィールの固定表示を解除",
"tabs_bar.compose": "投稿",
"tabs_bar.federated_timeline": "連合", "tabs_bar.federated_timeline": "連合",
"tabs_bar.home": "ホーム", "tabs_bar.home": "ホーム",
"tabs_bar.local_timeline": "ローカル", "tabs_bar.local_timeline": "ローカル",
"tabs_bar.notifications": "通知", "tabs_bar.notifications": "通知",
"ui.beforeunload": "Mastodonから離れるとあなたのドラフトは失われます。", "ui.beforeunload": "Mastodonから離れると送信前の投稿は失われます。",
"upload_area.title": "ドラッグ&ドロップでアップロード", "upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加", "upload_button.label": "メディアを追加",
"upload_form.description": "視覚障害者のための説明", "upload_form.description": "視覚障害者のための説明",

@ -1,7 +1,9 @@
{ {
"account.block": "@{name}을 차단", "account.block": "@{name}을 차단",
"account.block_domain": "{domain} 전체를 숨김", "account.block_domain": "{domain} 전체를 숨김",
"account.blocked": "Blocked",
"account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.", "account.disclaimer_full": "여기 있는 정보는 유저의 프로파일을 정확히 반영하지 못 할 수도 있습니다.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "프로필 편집", "account.edit_profile": "프로필 편집",
"account.follow": "팔로우", "account.follow": "팔로우",
"account.followers": "팔로워", "account.followers": "팔로워",
@ -13,6 +15,7 @@
"account.moved_to": "{name}는 계정을 이동했습니다:", "account.moved_to": "{name}는 계정을 이동했습니다:",
"account.mute": "@{name} 뮤트", "account.mute": "@{name} 뮤트",
"account.mute_notifications": "@{name}의 알림을 뮤트", "account.mute_notifications": "@{name}의 알림을 뮤트",
"account.muted": "Muted",
"account.posts": "게시물", "account.posts": "게시물",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "@{name} 신고", "account.report": "@{name} 신고",
@ -216,6 +219,7 @@
"report.target": "문제가 된 사용자", "report.target": "문제가 된 사용자",
"search.placeholder": "검색", "search.placeholder": "검색",
"search_popout.search_format": "고급 검색 방법", "search_popout.search_format": "고급 검색 방법",
"search_popout.tips.full_text": "단순한 텍스트 검색은 당신이 작성했거나, 관심글로 지정했거나, 부스트했거나, 멘션을 받은 게시글, 그리고 유저네임, 디스플레이네임, 해시태그를 반환합니다.",
"search_popout.tips.hashtag": "해시태그", "search_popout.tips.hashtag": "해시태그",
"search_popout.tips.status": "툿", "search_popout.tips.status": "툿",
"search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다", "search_popout.tips.text": "단순한 텍스트 검색은 관계된 프로필 이름, 유저 이름 그리고 해시태그를 표시합니다",
@ -238,6 +242,7 @@
"status.mute_conversation": "이 대화를 뮤트", "status.mute_conversation": "이 대화를 뮤트",
"status.open": "상세 정보 표시", "status.open": "상세 정보 표시",
"status.pin": "고정", "status.pin": "고정",
"status.pinned": "Pinned toot",
"status.reblog": "부스트", "status.reblog": "부스트",
"status.reblogged_by": "{name}님이 부스트 했습니다", "status.reblogged_by": "{name}님이 부스트 했습니다",
"status.reply": "답장", "status.reply": "답장",
@ -250,7 +255,6 @@
"status.show_more": "더 보기", "status.show_more": "더 보기",
"status.unmute_conversation": "이 대화의 뮤트 해제하기", "status.unmute_conversation": "이 대화의 뮤트 해제하기",
"status.unpin": "고정 해제", "status.unpin": "고정 해제",
"tabs_bar.compose": "포스트",
"tabs_bar.federated_timeline": "연합", "tabs_bar.federated_timeline": "연합",
"tabs_bar.home": "홈", "tabs_bar.home": "홈",
"tabs_bar.local_timeline": "로컬", "tabs_bar.local_timeline": "로컬",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokkeer @{name}", "account.block": "Blokkeer @{name}",
"account.block_domain": "Negeer alles van {domain}", "account.block_domain": "Negeer alles van {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.", "account.disclaimer_full": "De informatie hieronder kan mogelijk een incompleet beeld geven van dit gebruikersprofiel.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Profiel bewerken", "account.edit_profile": "Profiel bewerken",
"account.follow": "Volgen", "account.follow": "Volgen",
"account.followers": "Volgers", "account.followers": "Volgers",
@ -13,8 +15,9 @@
"account.moved_to": "{name} is verhuisd naar:", "account.moved_to": "{name} is verhuisd naar:",
"account.mute": "Negeer @{name}", "account.mute": "Negeer @{name}",
"account.mute_notifications": "Negeer meldingen van @{name}", "account.mute_notifications": "Negeer meldingen van @{name}",
"account.muted": "Muted",
"account.posts": "Toots", "account.posts": "Toots",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots met reacties",
"account.report": "Rapporteer @{name}", "account.report": "Rapporteer @{name}",
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren", "account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
"account.share": "Profiel van @{name} delen", "account.share": "Profiel van @{name} delen",
@ -208,19 +211,20 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Annuleren", "reply_indicator.cancel": "Annuleren",
"report.forward": "Forward to {target}", "report.forward": "Doorsturen naar {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "Het account bevindt zich op een andere server. Stuur daar eveneens een geanonimiseerde kopie van de gerapporteerde toot(s) naartoe?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "De gerapporteerde toot(s) worden naar de moderatoren van jouw server gestuurd. Je kunt hieronder een uitleg geven waarom je dit account rapporteert:",
"report.placeholder": "Extra opmerkingen", "report.placeholder": "Extra opmerkingen",
"report.submit": "Verzenden", "report.submit": "Verzenden",
"report.target": "Rapporteer {target}", "report.target": "Rapporteer {target}",
"search.placeholder": "Zoeken", "search.placeholder": "Zoeken",
"search_popout.search_format": "Geavanceerd zoeken", "search_popout.search_format": "Geavanceerd zoeken",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "toot", "search_popout.tips.status": "toot",
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags", "search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
"search_popout.tips.user": "gebruiker", "search_popout.tips.user": "gebruiker",
"search_results.accounts": "People", "search_results.accounts": "Gebruikers",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots", "search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}", "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
@ -238,6 +242,7 @@
"status.mute_conversation": "Negeer conversatie", "status.mute_conversation": "Negeer conversatie",
"status.open": "Toot volledig tonen", "status.open": "Toot volledig tonen",
"status.pin": "Aan profielpagina vastmaken", "status.pin": "Aan profielpagina vastmaken",
"status.pinned": "Pinned toot",
"status.reblog": "Boost", "status.reblog": "Boost",
"status.reblogged_by": "{name} boostte", "status.reblogged_by": "{name} boostte",
"status.reply": "Reageren", "status.reply": "Reageren",
@ -250,7 +255,6 @@
"status.show_more": "Meer tonen", "status.show_more": "Meer tonen",
"status.unmute_conversation": "Conversatie niet meer negeren", "status.unmute_conversation": "Conversatie niet meer negeren",
"status.unpin": "Van profielpagina losmaken", "status.unpin": "Van profielpagina losmaken",
"tabs_bar.compose": "Schrijven",
"tabs_bar.federated_timeline": "Globaal", "tabs_bar.federated_timeline": "Globaal",
"tabs_bar.home": "Start", "tabs_bar.home": "Start",
"tabs_bar.local_timeline": "Lokaal", "tabs_bar.local_timeline": "Lokaal",
@ -259,7 +263,7 @@
"upload_area.title": "Hierin slepen om te uploaden", "upload_area.title": "Hierin slepen om te uploaden",
"upload_button.label": "Media toevoegen", "upload_button.label": "Media toevoegen",
"upload_form.description": "Omschrijf dit voor mensen met een visuele beperking", "upload_form.description": "Omschrijf dit voor mensen met een visuele beperking",
"upload_form.focus": "Crop", "upload_form.focus": "Bijsnijden",
"upload_form.undo": "Ongedaan maken", "upload_form.undo": "Ongedaan maken",
"upload_progress.label": "Uploaden...", "upload_progress.label": "Uploaden...",
"video.close": "Video sluiten", "video.close": "Video sluiten",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokkér @{name}", "account.block": "Blokkér @{name}",
"account.block_domain": "Skjul alt fra {domain}", "account.block_domain": "Skjul alt fra {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.", "account.disclaimer_full": "Informasjonen nedenfor kan gi et ufullstendig bilde av brukerens profil.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Rediger profil", "account.edit_profile": "Rediger profil",
"account.follow": "Følg", "account.follow": "Følg",
"account.followers": "Følgere", "account.followers": "Følgere",
@ -13,6 +15,7 @@
"account.moved_to": "{name} har flyttet til:", "account.moved_to": "{name} har flyttet til:",
"account.mute": "Demp @{name}", "account.mute": "Demp @{name}",
"account.mute_notifications": "Ignorer varsler fra @{name}", "account.mute_notifications": "Ignorer varsler fra @{name}",
"account.muted": "Muted",
"account.posts": "Innlegg", "account.posts": "Innlegg",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Rapportér @{name}", "account.report": "Rapportér @{name}",
@ -216,6 +219,7 @@
"report.target": "Rapporterer", "report.target": "Rapporterer",
"search.placeholder": "Søk", "search.placeholder": "Søk",
"search_popout.search_format": "Avansert søkeformat", "search_popout.search_format": "Avansert søkeformat",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "emneknagg", "search_popout.tips.hashtag": "emneknagg",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger", "search_popout.tips.text": "Enkel tekst returnerer matchende visningsnavn, brukernavn og emneknagger",
@ -238,6 +242,7 @@
"status.mute_conversation": "Demp samtale", "status.mute_conversation": "Demp samtale",
"status.open": "Utvid denne statusen", "status.open": "Utvid denne statusen",
"status.pin": "Fest på profilen", "status.pin": "Fest på profilen",
"status.pinned": "Pinned toot",
"status.reblog": "Fremhev", "status.reblog": "Fremhev",
"status.reblogged_by": "Fremhevd av {name}", "status.reblogged_by": "Fremhevd av {name}",
"status.reply": "Svar", "status.reply": "Svar",
@ -250,7 +255,6 @@
"status.show_more": "Vis mer", "status.show_more": "Vis mer",
"status.unmute_conversation": "Ikke demp samtale", "status.unmute_conversation": "Ikke demp samtale",
"status.unpin": "Angre festing på profilen", "status.unpin": "Angre festing på profilen",
"tabs_bar.compose": "Komponer",
"tabs_bar.federated_timeline": "Felles", "tabs_bar.federated_timeline": "Felles",
"tabs_bar.home": "Hjem", "tabs_bar.home": "Hjem",
"tabs_bar.local_timeline": "Lokal", "tabs_bar.local_timeline": "Lokal",

@ -1,7 +1,9 @@
{ {
"account.block": "Blocar @{name}", "account.block": "Blocar @{name}",
"account.block_domain": "Tot amagar del domeni {domain}", "account.block_domain": "Tot amagar del domeni {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.", "account.disclaimer_full": "Aquelas informacions de perfil pòdon èsser incomplètas.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Modificar lo perfil", "account.edit_profile": "Modificar lo perfil",
"account.follow": "Sègre", "account.follow": "Sègre",
"account.followers": "Seguidors", "account.followers": "Seguidors",
@ -13,6 +15,7 @@
"account.moved_to": "{name} a mudat los catons a:", "account.moved_to": "{name} a mudat los catons a:",
"account.mute": "Rescondre @{name}", "account.mute": "Rescondre @{name}",
"account.mute_notifications": "Rescondre las notificacions de @{name}", "account.mute_notifications": "Rescondre las notificacions de @{name}",
"account.muted": "Muted",
"account.posts": "Estatuts", "account.posts": "Estatuts",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Senhalar @{name}", "account.report": "Senhalar @{name}",
@ -216,6 +219,7 @@
"report.target": "Senhalar {target}", "report.target": "Senhalar {target}",
"search.placeholder": "Recercar", "search.placeholder": "Recercar",
"search_popout.search_format": "Format recèrca avançada", "search_popout.search_format": "Format recèrca avançada",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "etiqueta", "search_popout.tips.hashtag": "etiqueta",
"search_popout.tips.status": "estatut", "search_popout.tips.status": "estatut",
"search_popout.tips.text": "Lo tèxt brut tòrna escais, noms dutilizaire e etiquetas correspondents", "search_popout.tips.text": "Lo tèxt brut tòrna escais, noms dutilizaire e etiquetas correspondents",
@ -238,6 +242,7 @@
"status.mute_conversation": "Rescondre la conversacion", "status.mute_conversation": "Rescondre la conversacion",
"status.open": "Desplegar aqueste estatut", "status.open": "Desplegar aqueste estatut",
"status.pin": "Penjar al perfil", "status.pin": "Penjar al perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Partejar", "status.reblog": "Partejar",
"status.reblogged_by": "{name} a partejat", "status.reblogged_by": "{name} a partejat",
"status.reply": "Respondre", "status.reply": "Respondre",
@ -250,7 +255,6 @@
"status.show_more": "Desplegar", "status.show_more": "Desplegar",
"status.unmute_conversation": "Tornar mostrar la conversacion", "status.unmute_conversation": "Tornar mostrar la conversacion",
"status.unpin": "Tirar del perfil", "status.unpin": "Tirar del perfil",
"tabs_bar.compose": "Compausar",
"tabs_bar.federated_timeline": "Flux public global", "tabs_bar.federated_timeline": "Flux public global",
"tabs_bar.home": "Acuèlh", "tabs_bar.home": "Acuèlh",
"tabs_bar.local_timeline": "Flux public local", "tabs_bar.local_timeline": "Flux public local",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokuj @{name}", "account.block": "Blokuj @{name}",
"account.block_domain": "Blokuj wszystko z {domain}", "account.block_domain": "Blokuj wszystko z {domain}",
"account.blocked": "Zablokowany",
"account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.", "account.disclaimer_full": "Poniższe informacje mogą nie odwzorowywać bezbłędnie profilu użytkownika.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edytuj profil", "account.edit_profile": "Edytuj profil",
"account.follow": "Śledź", "account.follow": "Śledź",
"account.followers": "Śledzący", "account.followers": "Śledzący",
@ -13,6 +15,7 @@
"account.moved_to": "{name} przeniósł się do:", "account.moved_to": "{name} przeniósł się do:",
"account.mute": "Wycisz @{name}", "account.mute": "Wycisz @{name}",
"account.mute_notifications": "Wycisz powiadomienia o @{name}", "account.mute_notifications": "Wycisz powiadomienia o @{name}",
"account.muted": "Wyciszony",
"account.posts": "Wpisy", "account.posts": "Wpisy",
"account.posts_with_replies": "Wpisy z odpowiedziami", "account.posts_with_replies": "Wpisy z odpowiedziami",
"account.report": "Zgłoś @{name}", "account.report": "Zgłoś @{name}",
@ -223,6 +226,7 @@
"report.target": "Zgłaszanie {target}", "report.target": "Zgłaszanie {target}",
"search.placeholder": "Szukaj", "search.placeholder": "Szukaj",
"search_popout.search_format": "Zaawansowane wyszukiwanie", "search_popout.search_format": "Zaawansowane wyszukiwanie",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "wpis", "search_popout.tips.status": "wpis",
"search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów", "search_popout.tips.text": "Proste wyszukiwanie pasujących pseudonimów, nazw użytkowników i hashtagów",
@ -245,6 +249,7 @@
"status.mute_conversation": "Wycisz konwersację", "status.mute_conversation": "Wycisz konwersację",
"status.open": "Rozszerz ten wpis", "status.open": "Rozszerz ten wpis",
"status.pin": "Przypnij do profilu", "status.pin": "Przypnij do profilu",
"status.pinned": "Przypięty wpis",
"status.reblog": "Podbij", "status.reblog": "Podbij",
"status.reblogged_by": "{name} podbił", "status.reblogged_by": "{name} podbił",
"status.reply": "Odpowiedz", "status.reply": "Odpowiedz",
@ -257,7 +262,6 @@
"status.show_more": "Pokaż więcej", "status.show_more": "Pokaż więcej",
"status.unmute_conversation": "Cofnij wyciszenie konwersacji", "status.unmute_conversation": "Cofnij wyciszenie konwersacji",
"status.unpin": "Odepnij z profilu", "status.unpin": "Odepnij z profilu",
"tabs_bar.compose": "Napisz",
"tabs_bar.federated_timeline": "Globalne", "tabs_bar.federated_timeline": "Globalne",
"tabs_bar.home": "Strona główna", "tabs_bar.home": "Strona główna",
"tabs_bar.local_timeline": "Lokalne", "tabs_bar.local_timeline": "Lokalne",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloquear @{name}", "account.block": "Bloquear @{name}",
"account.block_domain": "Esconder tudo de {domain}", "account.block_domain": "Esconder tudo de {domain}",
"account.blocked": "Bloqueado",
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de maneira incompleta.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
"account.follow": "Seguir", "account.follow": "Seguir",
"account.followers": "Seguidores", "account.followers": "Seguidores",
@ -13,8 +15,9 @@
"account.moved_to": "{name} se mudou para:", "account.moved_to": "{name} se mudou para:",
"account.mute": "Silenciar @{name}", "account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificações de @{name}", "account.mute_notifications": "Silenciar notificações de @{name}",
"account.posts": "Posts", "account.muted": "Silenciado",
"account.posts_with_replies": "Toots with replies", "account.posts": "Toots",
"account.posts_with_replies": "Toots e respostas",
"account.report": "Denunciar @{name}", "account.report": "Denunciar @{name}",
"account.requested": "Aguardando aprovação. Clique para cancelar a solicitação", "account.requested": "Aguardando aprovação. Clique para cancelar a solicitação",
"account.share": "Compartilhar perfil de @{name}", "account.share": "Compartilhar perfil de @{name}",
@ -208,19 +211,20 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Cancelar", "reply_indicator.cancel": "Cancelar",
"report.forward": "Forward to {target}", "report.forward": "Encaminhar para {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "Essa conta pertence à um outro servidor. Encaminhar uma cópia da denúncia com seus dados tornados anônimos para esse servidor?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "A sua denúncia será enviada aos moderadores da instância. Você pode adicionar uma explicação de porque você está denunciando essa conta abaixo:",
"report.placeholder": "Comentários adicionais", "report.placeholder": "Comentários adicionais",
"report.submit": "Enviar", "report.submit": "Enviar",
"report.target": "Denunciar", "report.target": "Denunciar",
"search.placeholder": "Pesquisar", "search.placeholder": "Pesquisar",
"search_popout.search_format": "Formato de busca avançado", "search_popout.search_format": "Formato de busca avançado",
"search_popout.tips.full_text": "Texto simples retorna status que você escreveu, favoritou, compartilhou ou em que tenha sido mencionado; também retorna nomes de exibição, usuários e hashtags correspondentes.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes", "search_popout.tips.text": "Texto simples retorna nomes de exibição, usuários e hashtags correspondentes",
"search_popout.tips.user": "usuário", "search_popout.tips.user": "usuário",
"search_results.accounts": "People", "search_results.accounts": "Pessoas",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Hashtags",
"search_results.statuses": "Toots", "search_results.statuses": "Toots",
"search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}", "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
@ -238,6 +242,7 @@
"status.mute_conversation": "Silenciar conversa", "status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir", "status.open": "Expandir",
"status.pin": "Fixar no perfil", "status.pin": "Fixar no perfil",
"status.pinned": "Toot fixado",
"status.reblog": "Compartilhar", "status.reblog": "Compartilhar",
"status.reblogged_by": "{name} compartilhou", "status.reblogged_by": "{name} compartilhou",
"status.reply": "Responder", "status.reply": "Responder",
@ -250,7 +255,6 @@
"status.show_more": "Mostrar mais", "status.show_more": "Mostrar mais",
"status.unmute_conversation": "Desativar silêncio desta conversa", "status.unmute_conversation": "Desativar silêncio desta conversa",
"status.unpin": "Desafixar do perfil", "status.unpin": "Desafixar do perfil",
"tabs_bar.compose": "Criar",
"tabs_bar.federated_timeline": "Global", "tabs_bar.federated_timeline": "Global",
"tabs_bar.home": "Página inicial", "tabs_bar.home": "Página inicial",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",
@ -259,7 +263,7 @@
"upload_area.title": "Arraste e solte para enviar", "upload_area.title": "Arraste e solte para enviar",
"upload_button.label": "Adicionar mídia", "upload_button.label": "Adicionar mídia",
"upload_form.description": "Descreva a imagem para deficientes visuais", "upload_form.description": "Descreva a imagem para deficientes visuais",
"upload_form.focus": "Crop", "upload_form.focus": "Recortar",
"upload_form.undo": "Desfazer", "upload_form.undo": "Desfazer",
"upload_progress.label": "Salvando...", "upload_progress.label": "Salvando...",
"video.close": "Fechar vídeo", "video.close": "Fechar vídeo",

@ -1,7 +1,9 @@
{ {
"account.block": "Bloquear @{name}", "account.block": "Bloquear @{name}",
"account.block_domain": "Esconder tudo do domínio {domain}", "account.block_domain": "Esconder tudo do domínio {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.", "account.disclaimer_full": "As informações abaixo podem refletir o perfil do usuário de forma incompleta.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Editar perfil", "account.edit_profile": "Editar perfil",
"account.follow": "Seguir", "account.follow": "Seguir",
"account.followers": "Seguidores", "account.followers": "Seguidores",
@ -13,6 +15,7 @@
"account.moved_to": "{name} mudou a sua conta para:", "account.moved_to": "{name} mudou a sua conta para:",
"account.mute": "Silenciar @{name}", "account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificações de @{name}", "account.mute_notifications": "Silenciar notificações de @{name}",
"account.muted": "Muted",
"account.posts": "Posts", "account.posts": "Posts",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Denunciar @{name}", "account.report": "Denunciar @{name}",
@ -216,6 +219,7 @@
"report.target": "Denunciar", "report.target": "Denunciar",
"search.placeholder": "Pesquisar", "search.placeholder": "Pesquisar",
"search_popout.search_format": "Formato avançado de pesquisa", "search_popout.search_format": "Formato avançado de pesquisa",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags", "search_popout.tips.text": "O texto simples retorna a correspondência de nomes, utilizadores e hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Silenciar conversa", "status.mute_conversation": "Silenciar conversa",
"status.open": "Expandir", "status.open": "Expandir",
"status.pin": "Fixar no perfil", "status.pin": "Fixar no perfil",
"status.pinned": "Pinned toot",
"status.reblog": "Partilhar", "status.reblog": "Partilhar",
"status.reblogged_by": "{name} partilhou", "status.reblogged_by": "{name} partilhou",
"status.reply": "Responder", "status.reply": "Responder",
@ -250,7 +255,6 @@
"status.show_more": "Mostrar mais", "status.show_more": "Mostrar mais",
"status.unmute_conversation": "Deixar de silenciar esta conversa", "status.unmute_conversation": "Deixar de silenciar esta conversa",
"status.unpin": "Não fixar no perfil", "status.unpin": "Não fixar no perfil",
"tabs_bar.compose": "Criar",
"tabs_bar.federated_timeline": "Global", "tabs_bar.federated_timeline": "Global",
"tabs_bar.home": "Home", "tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "Блокировать", "account.block": "Блокировать",
"account.block_domain": "Блокировать все с {domain}", "account.block_domain": "Блокировать все с {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.", "account.disclaimer_full": "Нижеуказанная информация может не полностью отражать профиль пользователя.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Изменить профиль", "account.edit_profile": "Изменить профиль",
"account.follow": "Подписаться", "account.follow": "Подписаться",
"account.followers": "Подписаны", "account.followers": "Подписаны",
@ -13,6 +15,7 @@
"account.moved_to": "Ищите {name} здесь:", "account.moved_to": "Ищите {name} здесь:",
"account.mute": "Заглушить", "account.mute": "Заглушить",
"account.mute_notifications": "Скрыть уведомления от @{name}", "account.mute_notifications": "Скрыть уведомления от @{name}",
"account.muted": "Muted",
"account.posts": "Посты", "account.posts": "Посты",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Пожаловаться", "account.report": "Пожаловаться",
@ -216,6 +219,7 @@
"report.target": "Жалуемся на", "report.target": "Жалуемся на",
"search.placeholder": "Поиск", "search.placeholder": "Поиск",
"search_popout.search_format": "Продвинутый формат поиска", "search_popout.search_format": "Продвинутый формат поиска",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "хэштег", "search_popout.tips.hashtag": "хэштег",
"search_popout.tips.status": "статус", "search_popout.tips.status": "статус",
"search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги", "search_popout.tips.text": "Простой ввод текста покажет совпадающие имена пользователей, отображаемые имена и хэштеги",
@ -238,6 +242,7 @@
"status.mute_conversation": "Заглушить тред", "status.mute_conversation": "Заглушить тред",
"status.open": "Развернуть статус", "status.open": "Развернуть статус",
"status.pin": "Закрепить в профиле", "status.pin": "Закрепить в профиле",
"status.pinned": "Pinned toot",
"status.reblog": "Продвинуть", "status.reblog": "Продвинуть",
"status.reblogged_by": "{name} продвинул(а)", "status.reblogged_by": "{name} продвинул(а)",
"status.reply": "Ответить", "status.reply": "Ответить",
@ -250,7 +255,6 @@
"status.show_more": "Развернуть", "status.show_more": "Развернуть",
"status.unmute_conversation": "Снять глушение с треда", "status.unmute_conversation": "Снять глушение с треда",
"status.unpin": "Открепить от профиля", "status.unpin": "Открепить от профиля",
"tabs_bar.compose": "Написать",
"tabs_bar.federated_timeline": "Глобальная", "tabs_bar.federated_timeline": "Глобальная",
"tabs_bar.home": "Главная", "tabs_bar.home": "Главная",
"tabs_bar.local_timeline": "Локальная", "tabs_bar.local_timeline": "Локальная",

@ -1,20 +1,23 @@
{ {
"account.block": "Blokovať @{name}", "account.block": "Blokovať @{name}",
"account.block_domain": "Ukryť všetko z {domain}", "account.block_domain": "Ukryť všetko z {domain}",
"account.blocked": "Blokovaný/á",
"account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.", "account.disclaimer_full": "Inofrmácie nižšie nemusia byť úplným odrazom uživateľovho účtu.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Upraviť profil", "account.edit_profile": "Upraviť profil",
"account.follow": "Následovať", "account.follow": "Následovať",
"account.followers": "Sledujúci", "account.followers": "Sledujúci",
"account.follows": "Sledujete", "account.follows": "Sledujete",
"account.follows_you": "Následuje vás", "account.follows_you": "Následuje ťa",
"account.hide_reblogs": "Skryť povýšenia od @{name}", "account.hide_reblogs": "Skryť povýšenia od @{name}",
"account.media": "Médiá", "account.media": "Médiá",
"account.mention": "Spomeňte @{name}", "account.mention": "Spomeňte @{name}",
"account.moved_to": "{name} sa presunul/a na:", "account.moved_to": "{name} sa presunul/a na:",
"account.mute": "Ignorovať @{name}", "account.mute": "Ignorovať @{name}",
"account.mute_notifications": "Stĺmiť notifikácie od @{name}", "account.mute_notifications": "Stĺmiť notifikácie od @{name}",
"account.muted": "Utíšený/á",
"account.posts": "Hlášky", "account.posts": "Hlášky",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Príspevky s odpoveďami",
"account.report": "Nahlásiť @{name}", "account.report": "Nahlásiť @{name}",
"account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti", "account.requested": "Čaká na schválenie. Kliknite pre zrušenie žiadosti",
"account.share": "Zdieľať @{name} profil", "account.share": "Zdieľať @{name} profil",
@ -156,8 +159,8 @@
"navigation_bar.preferences": "Možnosti", "navigation_bar.preferences": "Možnosti",
"navigation_bar.public_timeline": "Federovaná časová os", "navigation_bar.public_timeline": "Federovaná časová os",
"notification.favourite": "{name} sa páči tvoj status", "notification.favourite": "{name} sa páči tvoj status",
"notification.follow": "{name} vás začal(a) sledovať", "notification.follow": "{name} ťa začal/a následovať",
"notification.mention": "{name} vás spomenul", "notification.mention": "{name} ťa spomenul/a",
"notification.reblog": "{name} re-tootol tvoj status", "notification.reblog": "{name} re-tootol tvoj status",
"notifications.clear": "Vyčistiť zoznam notifikácii", "notifications.clear": "Vyčistiť zoznam notifikácii",
"notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?", "notifications.clear_confirmation": "Naozaj chcete nenávratne prečistiť všetky vaše notifikácie?",
@ -208,21 +211,22 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"reply_indicator.cancel": "Zrušiť", "reply_indicator.cancel": "Zrušiť",
"report.forward": "Forward to {target}", "report.forward": "Posuň ku {target}",
"report.forward_hint": "The account is from another server. Send an anonymized copy of the report there as well?", "report.forward_hint": "Tento účet je z iného serveru. Chceš poslať anonymnú kópiu reportu aj tam?",
"report.hint": "The report will be sent to your instance moderators. You can provide an explanation of why you are reporting this account below:", "report.hint": "Toto nahlásenie bude zaslané správcom servera. Môžeš napísať odvôvodnenie prečo si nahlásil/a tento účet:",
"report.placeholder": "Ďalšie komentáre", "report.placeholder": "Ďalšie komentáre",
"report.submit": "Poslať", "report.submit": "Poslať",
"report.target": "Nahlásenie {target}", "report.target": "Nahlásenie {target}",
"search.placeholder": "Hľadať", "search.placeholder": "Hľadať",
"search_popout.search_format": "Pokročilý formát vyhľadávania", "search_popout.search_format": "Pokročilý formát vyhľadávania",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "haštag", "search_popout.tips.hashtag": "haštag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy", "search_popout.tips.text": "Jednoduchý text vráti zhodujúce sa mená, prezývky a hashtagy",
"search_popout.tips.user": "používateľ", "search_popout.tips.user": "používateľ",
"search_results.accounts": "People", "search_results.accounts": "Ľudia",
"search_results.hashtags": "Hashtags", "search_results.hashtags": "Haštagy",
"search_results.statuses": "Toots", "search_results.statuses": "Príspevky",
"search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}", "search_results.total": "{count, number} {count, plural, one {result} ostatné {results}}",
"standalone.public_title": "Pohľad dovnútra...", "standalone.public_title": "Pohľad dovnútra...",
"status.block": "Blokovať @{name}", "status.block": "Blokovať @{name}",
@ -238,6 +242,7 @@
"status.mute_conversation": "Ignorovať konverzáciu", "status.mute_conversation": "Ignorovať konverzáciu",
"status.open": "Otvoriť tento status", "status.open": "Otvoriť tento status",
"status.pin": "Pripnúť na profil", "status.pin": "Pripnúť na profil",
"status.pinned": "Pripnutý príspevok",
"status.reblog": "Povýšiť", "status.reblog": "Povýšiť",
"status.reblogged_by": "{name} povýšil", "status.reblogged_by": "{name} povýšil",
"status.reply": "Odpovedať", "status.reply": "Odpovedať",
@ -250,7 +255,6 @@
"status.show_more": "Zobraz viac", "status.show_more": "Zobraz viac",
"status.unmute_conversation": "Prestať ignorovať konverzáciu", "status.unmute_conversation": "Prestať ignorovať konverzáciu",
"status.unpin": "Odopnúť z profilu", "status.unpin": "Odopnúť z profilu",
"tabs_bar.compose": "Napísať",
"tabs_bar.federated_timeline": "Federovaná", "tabs_bar.federated_timeline": "Federovaná",
"tabs_bar.home": "Domov", "tabs_bar.home": "Domov",
"tabs_bar.local_timeline": "Lokálna", "tabs_bar.local_timeline": "Lokálna",
@ -259,7 +263,7 @@
"upload_area.title": "Ťahaj a pusti pre nahratie", "upload_area.title": "Ťahaj a pusti pre nahratie",
"upload_button.label": "Pridať médiá", "upload_button.label": "Pridať médiá",
"upload_form.description": "Opis pre slabo vidiacich", "upload_form.description": "Opis pre slabo vidiacich",
"upload_form.focus": "Crop", "upload_form.focus": "Vystrihni",
"upload_form.undo": "Navrátiť", "upload_form.undo": "Navrátiť",
"upload_progress.label": "Nahráva sa...", "upload_progress.label": "Nahráva sa...",
"video.close": "Zavrieť video", "video.close": "Zavrieť video",

@ -1,7 +1,9 @@
{ {
"account.block": "Blokiraj korisnika @{name}", "account.block": "Blokiraj korisnika @{name}",
"account.block_domain": "Sakrij sve sa domena {domain}", "account.block_domain": "Sakrij sve sa domena {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.", "account.disclaimer_full": "Navedene informacije možda ne odslikavaju korisnički profil u potpunosti.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Izmeni profil", "account.edit_profile": "Izmeni profil",
"account.follow": "Zaprati", "account.follow": "Zaprati",
"account.followers": "Pratioca", "account.followers": "Pratioca",
@ -13,6 +15,7 @@
"account.moved_to": "{name} se pomerio na:", "account.moved_to": "{name} se pomerio na:",
"account.mute": "Ućutkaj korisnika @{name}", "account.mute": "Ućutkaj korisnika @{name}",
"account.mute_notifications": "Isključi obaveštenja od korisnika @{name}", "account.mute_notifications": "Isključi obaveštenja od korisnika @{name}",
"account.muted": "Muted",
"account.posts": "Statusa", "account.posts": "Statusa",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Prijavi @{name}", "account.report": "Prijavi @{name}",
@ -216,6 +219,7 @@
"report.target": "Prijavljujem {target}", "report.target": "Prijavljujem {target}",
"search.placeholder": "Pretraga", "search.placeholder": "Pretraga",
"search_popout.search_format": "Napredni format pretrage", "search_popout.search_format": "Napredni format pretrage",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hešteg", "search_popout.tips.hashtag": "hešteg",
"search_popout.tips.status": "status", "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.text": "Traženjem običnog teksta ćete dobiti sva pronađena imena, sva korisnička imena i sve nađene heštegove",
@ -238,6 +242,7 @@
"status.mute_conversation": "Ućutkaj prepisku", "status.mute_conversation": "Ućutkaj prepisku",
"status.open": "Proširi ovaj status", "status.open": "Proširi ovaj status",
"status.pin": "Prikači na profil", "status.pin": "Prikači na profil",
"status.pinned": "Pinned toot",
"status.reblog": "Podrži", "status.reblog": "Podrži",
"status.reblogged_by": "{name} podržao(la)", "status.reblogged_by": "{name} podržao(la)",
"status.reply": "Odgovori", "status.reply": "Odgovori",
@ -250,7 +255,6 @@
"status.show_more": "Prikaži više", "status.show_more": "Prikaži više",
"status.unmute_conversation": "Uključi prepisku", "status.unmute_conversation": "Uključi prepisku",
"status.unpin": "Otkači sa profila", "status.unpin": "Otkači sa profila",
"tabs_bar.compose": "Napiši",
"tabs_bar.federated_timeline": "Federisano", "tabs_bar.federated_timeline": "Federisano",
"tabs_bar.home": "Početna", "tabs_bar.home": "Početna",
"tabs_bar.local_timeline": "Lokalno", "tabs_bar.local_timeline": "Lokalno",

@ -1,7 +1,9 @@
{ {
"account.block": "Блокирај корисника @{name}", "account.block": "Блокирај корисника @{name}",
"account.block_domain": "Сакриј све са домена {domain}", "account.block_domain": "Сакриј све са домена {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.", "account.disclaimer_full": "Наведене информације можда не одсликавају кориснички профил у потпуности.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Измени профил", "account.edit_profile": "Измени профил",
"account.follow": "Запрати", "account.follow": "Запрати",
"account.followers": "Пратиоца", "account.followers": "Пратиоца",
@ -13,6 +15,7 @@
"account.moved_to": "{name} се померио на:", "account.moved_to": "{name} се померио на:",
"account.mute": "Ућуткај корисника @{name}", "account.mute": "Ућуткај корисника @{name}",
"account.mute_notifications": "Искључи обавештења од корисника @{name}", "account.mute_notifications": "Искључи обавештења од корисника @{name}",
"account.muted": "Muted",
"account.posts": "Статуса", "account.posts": "Статуса",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Пријави @{name}", "account.report": "Пријави @{name}",
@ -216,6 +219,7 @@
"report.target": "Пријављујем {target}", "report.target": "Пријављујем {target}",
"search.placeholder": "Претрага", "search.placeholder": "Претрага",
"search_popout.search_format": "Напредни формат претраге", "search_popout.search_format": "Напредни формат претраге",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "хештег", "search_popout.tips.hashtag": "хештег",
"search_popout.tips.status": "статус", "search_popout.tips.status": "статус",
"search_popout.tips.text": "Тражењем обичног текста ћете добити сва пронађена имена, сва корисничка имена и све нађене хештегове", "search_popout.tips.text": "Тражењем обичног текста ћете добити сва пронађена имена, сва корисничка имена и све нађене хештегове",
@ -238,6 +242,7 @@
"status.mute_conversation": "Ућуткај преписку", "status.mute_conversation": "Ућуткај преписку",
"status.open": "Прошири овај статус", "status.open": "Прошири овај статус",
"status.pin": "Прикачи на профил", "status.pin": "Прикачи на профил",
"status.pinned": "Pinned toot",
"status.reblog": "Подржи", "status.reblog": "Подржи",
"status.reblogged_by": "{name} подржао(ла)", "status.reblogged_by": "{name} подржао(ла)",
"status.reply": "Одговори", "status.reply": "Одговори",
@ -250,7 +255,6 @@
"status.show_more": "Прикажи више", "status.show_more": "Прикажи више",
"status.unmute_conversation": "Укључи преписку", "status.unmute_conversation": "Укључи преписку",
"status.unpin": "Откачи са профила", "status.unpin": "Откачи са профила",
"tabs_bar.compose": "Напиши",
"tabs_bar.federated_timeline": "Федерисано", "tabs_bar.federated_timeline": "Федерисано",
"tabs_bar.home": "Почетна", "tabs_bar.home": "Почетна",
"tabs_bar.local_timeline": "Локално", "tabs_bar.local_timeline": "Локално",

@ -1,7 +1,9 @@
{ {
"account.block": "Blockera @{name}", "account.block": "Blockera @{name}",
"account.block_domain": "Dölj allt från {domain}", "account.block_domain": "Dölj allt från {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.", "account.disclaimer_full": "Informationen nedan kan spegla användarens profil ofullständigt.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Redigera profil", "account.edit_profile": "Redigera profil",
"account.follow": "Följ", "account.follow": "Följ",
"account.followers": "Följare", "account.followers": "Följare",
@ -13,6 +15,7 @@
"account.moved_to": "{name} har flyttat till:", "account.moved_to": "{name} har flyttat till:",
"account.mute": "Tysta @{name}", "account.mute": "Tysta @{name}",
"account.mute_notifications": "Stäng av notifieringar från @{name}", "account.mute_notifications": "Stäng av notifieringar från @{name}",
"account.muted": "Muted",
"account.posts": "Inlägg", "account.posts": "Inlägg",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Rapportera @{name}", "account.report": "Rapportera @{name}",
@ -216,6 +219,7 @@
"report.target": "Rapporterar {target}", "report.target": "Rapporterar {target}",
"search.placeholder": "Sök", "search.placeholder": "Sök",
"search_popout.search_format": "Avancerat sökformat", "search_popout.search_format": "Avancerat sökformat",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags", "search_popout.tips.text": "Enkel text returnerar matchande visningsnamn, användarnamn och hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Tysta konversation", "status.mute_conversation": "Tysta konversation",
"status.open": "Utvidga denna status", "status.open": "Utvidga denna status",
"status.pin": "Fäst i profil", "status.pin": "Fäst i profil",
"status.pinned": "Pinned toot",
"status.reblog": "Knuff", "status.reblog": "Knuff",
"status.reblogged_by": "{name} knuffade", "status.reblogged_by": "{name} knuffade",
"status.reply": "Svara", "status.reply": "Svara",
@ -250,7 +255,6 @@
"status.show_more": "Visa mer", "status.show_more": "Visa mer",
"status.unmute_conversation": "Öppna konversation", "status.unmute_conversation": "Öppna konversation",
"status.unpin": "Ångra fäst i profil", "status.unpin": "Ångra fäst i profil",
"tabs_bar.compose": "Skriv",
"tabs_bar.federated_timeline": "Förenad", "tabs_bar.federated_timeline": "Förenad",
"tabs_bar.home": "Hem", "tabs_bar.home": "Hem",
"tabs_bar.local_timeline": "Lokal", "tabs_bar.local_timeline": "Lokal",

@ -1,7 +1,9 @@
{ {
"account.block": "Block @{name}", "account.block": "Block @{name}",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Edit profile", "account.edit_profile": "Edit profile",
"account.follow": "Follow", "account.follow": "Follow",
"account.followers": "Followers", "account.followers": "Followers",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Mute @{name}", "account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Posts", "account.posts": "Posts",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Report @{name}", "account.report": "Report @{name}",
@ -216,6 +219,7 @@
"report.target": "Reporting", "report.target": "Reporting",
"search.placeholder": "Search", "search.placeholder": "Search",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Expand this status", "status.open": "Expand this status",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Boost", "status.reblog": "Boost",
"status.reblogged_by": "{name} boosted", "status.reblogged_by": "{name} boosted",
"status.reply": "Reply", "status.reply": "Reply",
@ -250,7 +255,6 @@
"status.show_more": "Show more", "status.show_more": "Show more",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Compose",
"tabs_bar.federated_timeline": "Federated", "tabs_bar.federated_timeline": "Federated",
"tabs_bar.home": "Home", "tabs_bar.home": "Home",
"tabs_bar.local_timeline": "Local", "tabs_bar.local_timeline": "Local",

@ -1,7 +1,9 @@
{ {
"account.block": "Engelle @{name}", "account.block": "Engelle @{name}",
"account.block_domain": "Hide everything from {domain}", "account.block_domain": "Hide everything from {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Profili düzenle", "account.edit_profile": "Profili düzenle",
"account.follow": "Takip et", "account.follow": "Takip et",
"account.followers": "Takipçiler", "account.followers": "Takipçiler",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Sustur @{name}", "account.mute": "Sustur @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Gönderiler", "account.posts": "Gönderiler",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Rapor et @{name}", "account.report": "Rapor et @{name}",
@ -216,6 +219,7 @@
"report.target": "Raporlama", "report.target": "Raporlama",
"search.placeholder": "Ara", "search.placeholder": "Ara",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Mute conversation", "status.mute_conversation": "Mute conversation",
"status.open": "Bu gönderiyi genişlet", "status.open": "Bu gönderiyi genişlet",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Boost'la", "status.reblog": "Boost'la",
"status.reblogged_by": "{name} boost etti", "status.reblogged_by": "{name} boost etti",
"status.reply": "Cevapla", "status.reply": "Cevapla",
@ -250,7 +255,6 @@
"status.show_more": "Daha fazlası", "status.show_more": "Daha fazlası",
"status.unmute_conversation": "Unmute conversation", "status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Oluştur",
"tabs_bar.federated_timeline": "Federe", "tabs_bar.federated_timeline": "Federe",
"tabs_bar.home": "Ana sayfa", "tabs_bar.home": "Ana sayfa",
"tabs_bar.local_timeline": "Yerel", "tabs_bar.local_timeline": "Yerel",

@ -1,7 +1,9 @@
{ {
"account.block": "Заблокувати", "account.block": "Заблокувати",
"account.block_domain": "Заглушити {domain}", "account.block_domain": "Заглушити {domain}",
"account.blocked": "Blocked",
"account.disclaimer_full": "Information below may reflect the user's profile incompletely.", "account.disclaimer_full": "Information below may reflect the user's profile incompletely.",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "Налаштування профілю", "account.edit_profile": "Налаштування профілю",
"account.follow": "Підписатися", "account.follow": "Підписатися",
"account.followers": "Підписники", "account.followers": "Підписники",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "Заглушити", "account.mute": "Заглушити",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "Пости", "account.posts": "Пости",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "Поскаржитися", "account.report": "Поскаржитися",
@ -216,6 +219,7 @@
"report.target": "Скаржимося на", "report.target": "Скаржимося на",
"search.placeholder": "Пошук", "search.placeholder": "Пошук",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "Заглушити діалог", "status.mute_conversation": "Заглушити діалог",
"status.open": "Розгорнути допис", "status.open": "Розгорнути допис",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned toot",
"status.reblog": "Передмухнути", "status.reblog": "Передмухнути",
"status.reblogged_by": "{name} передмухнув(-ла)", "status.reblogged_by": "{name} передмухнув(-ла)",
"status.reply": "Відповісти", "status.reply": "Відповісти",
@ -250,7 +255,6 @@
"status.show_more": "Розгорнути", "status.show_more": "Розгорнути",
"status.unmute_conversation": "Зняти глушення з діалогу", "status.unmute_conversation": "Зняти глушення з діалогу",
"status.unpin": "Unpin from profile", "status.unpin": "Unpin from profile",
"tabs_bar.compose": "Написати",
"tabs_bar.federated_timeline": "Глобальна", "tabs_bar.federated_timeline": "Глобальна",
"tabs_bar.home": "Головна", "tabs_bar.home": "Головна",
"tabs_bar.local_timeline": "Локальна", "tabs_bar.local_timeline": "Локальна",

@ -1,7 +1,9 @@
{ {
"account.block": "屏蔽 @{name}", "account.block": "屏蔽 @{name}",
"account.block_domain": "隐藏来自 {domain} 的内容", "account.block_domain": "隐藏来自 {domain} 的内容",
"account.blocked": "Blocked",
"account.disclaimer_full": "此处显示的信息可能不是全部内容。", "account.disclaimer_full": "此处显示的信息可能不是全部内容。",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "修改个人资料", "account.edit_profile": "修改个人资料",
"account.follow": "关注", "account.follow": "关注",
"account.followers": "关注者", "account.followers": "关注者",
@ -13,6 +15,7 @@
"account.moved_to": "{name} 已经迁移到:", "account.moved_to": "{name} 已经迁移到:",
"account.mute": "隐藏 @{name}", "account.mute": "隐藏 @{name}",
"account.mute_notifications": "隐藏来自 @{name} 的通知", "account.mute_notifications": "隐藏来自 @{name} 的通知",
"account.muted": "Muted",
"account.posts": "嘟文", "account.posts": "嘟文",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "举报 @{name}", "account.report": "举报 @{name}",
@ -216,6 +219,7 @@
"report.target": "举报 {target}", "report.target": "举报 {target}",
"search.placeholder": "搜索", "search.placeholder": "搜索",
"search_popout.search_format": "高级搜索格式", "search_popout.search_format": "高级搜索格式",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "话题标签", "search_popout.tips.hashtag": "话题标签",
"search_popout.tips.status": "嘟文", "search_popout.tips.status": "嘟文",
"search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签", "search_popout.tips.text": "使用普通字符进行搜索将会返回昵称、用户名和话题标签",
@ -238,6 +242,7 @@
"status.mute_conversation": "隐藏此对话", "status.mute_conversation": "隐藏此对话",
"status.open": "展开嘟文", "status.open": "展开嘟文",
"status.pin": "在个人资料页面置顶", "status.pin": "在个人资料页面置顶",
"status.pinned": "Pinned toot",
"status.reblog": "转嘟", "status.reblog": "转嘟",
"status.reblogged_by": "{name} 转嘟了", "status.reblogged_by": "{name} 转嘟了",
"status.reply": "回复", "status.reply": "回复",
@ -250,7 +255,6 @@
"status.show_more": "显示内容", "status.show_more": "显示内容",
"status.unmute_conversation": "不再隐藏此对话", "status.unmute_conversation": "不再隐藏此对话",
"status.unpin": "在个人资料页面取消置顶", "status.unpin": "在个人资料页面取消置顶",
"tabs_bar.compose": "撰写",
"tabs_bar.federated_timeline": "跨站", "tabs_bar.federated_timeline": "跨站",
"tabs_bar.home": "主页", "tabs_bar.home": "主页",
"tabs_bar.local_timeline": "本站", "tabs_bar.local_timeline": "本站",

@ -1,7 +1,9 @@
{ {
"account.block": "封鎖 @{name}", "account.block": "封鎖 @{name}",
"account.block_domain": "隱藏來自 {domain} 的一切文章", "account.block_domain": "隱藏來自 {domain} 的一切文章",
"account.blocked": "Blocked",
"account.disclaimer_full": "下列資料不一定完整。", "account.disclaimer_full": "下列資料不一定完整。",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "修改個人資料", "account.edit_profile": "修改個人資料",
"account.follow": "關注", "account.follow": "關注",
"account.followers": "關注的人", "account.followers": "關注的人",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "將 @{name} 靜音", "account.mute": "將 @{name} 靜音",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "文章", "account.posts": "文章",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "舉報 @{name}", "account.report": "舉報 @{name}",
@ -216,6 +219,7 @@
"report.target": "舉報", "report.target": "舉報",
"search.placeholder": "搜尋", "search.placeholder": "搜尋",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "靜音對話", "status.mute_conversation": "靜音對話",
"status.open": "展開文章", "status.open": "展開文章",
"status.pin": "置頂到資料頁", "status.pin": "置頂到資料頁",
"status.pinned": "Pinned toot",
"status.reblog": "轉推", "status.reblog": "轉推",
"status.reblogged_by": "{name} 轉推", "status.reblogged_by": "{name} 轉推",
"status.reply": "回應", "status.reply": "回應",
@ -250,7 +255,6 @@
"status.show_more": "顯示更多", "status.show_more": "顯示更多",
"status.unmute_conversation": "解禁對話", "status.unmute_conversation": "解禁對話",
"status.unpin": "解除置頂", "status.unpin": "解除置頂",
"tabs_bar.compose": "撰寫",
"tabs_bar.federated_timeline": "跨站", "tabs_bar.federated_timeline": "跨站",
"tabs_bar.home": "主頁", "tabs_bar.home": "主頁",
"tabs_bar.local_timeline": "本站", "tabs_bar.local_timeline": "本站",

@ -1,7 +1,9 @@
{ {
"account.block": "封鎖 @{name}", "account.block": "封鎖 @{name}",
"account.block_domain": "隱藏來自 {domain} 的一切貼文", "account.block_domain": "隱藏來自 {domain} 的一切貼文",
"account.blocked": "Blocked",
"account.disclaimer_full": "下列資料不一定完整。", "account.disclaimer_full": "下列資料不一定完整。",
"account.domain_blocked": "Domain hidden",
"account.edit_profile": "編輯用者資訊", "account.edit_profile": "編輯用者資訊",
"account.follow": "關注", "account.follow": "關注",
"account.followers": "專注者", "account.followers": "專注者",
@ -13,6 +15,7 @@
"account.moved_to": "{name} has moved to:", "account.moved_to": "{name} has moved to:",
"account.mute": "消音 @{name}", "account.mute": "消音 @{name}",
"account.mute_notifications": "Mute notifications from @{name}", "account.mute_notifications": "Mute notifications from @{name}",
"account.muted": "Muted",
"account.posts": "貼文", "account.posts": "貼文",
"account.posts_with_replies": "Toots with replies", "account.posts_with_replies": "Toots with replies",
"account.report": "檢舉 @{name}", "account.report": "檢舉 @{name}",
@ -216,6 +219,7 @@
"report.target": "通報中", "report.target": "通報中",
"search.placeholder": "搜尋", "search.placeholder": "搜尋",
"search_popout.search_format": "Advanced search format", "search_popout.search_format": "Advanced search format",
"search_popout.tips.full_text": "Simple text returns statuses you have written, favourited, boosted, or have been mentioned in, as well as matching usernames, display names, and hashtags.",
"search_popout.tips.hashtag": "hashtag", "search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "status", "search_popout.tips.status": "status",
"search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags", "search_popout.tips.text": "Simple text returns matching display names, usernames and hashtags",
@ -238,6 +242,7 @@
"status.mute_conversation": "消音對話", "status.mute_conversation": "消音對話",
"status.open": "展開這個狀態", "status.open": "展開這個狀態",
"status.pin": "置頂到個人資訊頁", "status.pin": "置頂到個人資訊頁",
"status.pinned": "Pinned toot",
"status.reblog": "轉推", "status.reblog": "轉推",
"status.reblogged_by": "{name} 轉推了", "status.reblogged_by": "{name} 轉推了",
"status.reply": "回應", "status.reply": "回應",
@ -250,7 +255,6 @@
"status.show_more": "看更多", "status.show_more": "看更多",
"status.unmute_conversation": "不消音對話", "status.unmute_conversation": "不消音對話",
"status.unpin": "解除置頂", "status.unpin": "解除置頂",
"tabs_bar.compose": "編輯",
"tabs_bar.federated_timeline": "聯盟", "tabs_bar.federated_timeline": "聯盟",
"tabs_bar.home": "家", "tabs_bar.home": "家",
"tabs_bar.local_timeline": "本地", "tabs_bar.local_timeline": "本地",

@ -102,7 +102,7 @@ const initialState = ImmutableMap();
export default function accounts(state = initialState, action) { export default function accounts(state = initialState, action) {
switch(action.type) { switch(action.type) {
case STORE_HYDRATE: case STORE_HYDRATE:
return state.merge(action.state.get('accounts')); return normalizeAccounts(state, Object.values(action.state.get('accounts').toJS()));
case ACCOUNT_FETCH_SUCCESS: case ACCOUNT_FETCH_SUCCESS:
case NOTIFICATIONS_UPDATE: case NOTIFICATIONS_UPDATE:
return normalizeAccount(state, action.account); return normalizeAccount(state, action.account);

@ -16,6 +16,8 @@ import {
COMPOSE_SUGGESTIONS_CLEAR, COMPOSE_SUGGESTIONS_CLEAR,
COMPOSE_SUGGESTIONS_READY, COMPOSE_SUGGESTIONS_READY,
COMPOSE_SUGGESTION_SELECT, COMPOSE_SUGGESTION_SELECT,
COMPOSE_SUGGESTION_TAGS_UPDATE,
COMPOSE_TAG_HISTORY_UPDATE,
COMPOSE_SENSITIVITY_CHANGE, COMPOSE_SENSITIVITY_CHANGE,
COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILERNESS_CHANGE,
COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE,
@ -54,6 +56,7 @@ const initialState = ImmutableMap({
default_sensitive: false, default_sensitive: false,
resetFileKey: Math.floor((Math.random() * 0x10000)), resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null, idempotencyKey: null,
tagHistory: ImmutableList(),
}); });
function statusToTextMentions(state, status) { function statusToTextMentions(state, status) {
@ -87,7 +90,6 @@ function appendMedia(state, media) {
map.update('media_attachments', list => list.push(media)); map.update('media_attachments', list => list.push(media));
map.set('is_uploading', false); map.set('is_uploading', false);
map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
map.set('focusDate', new Date()); map.set('focusDate', new Date());
map.set('idempotencyKey', uuid()); map.set('idempotencyKey', uuid());
@ -98,12 +100,10 @@ function appendMedia(state, media) {
}; };
function removeMedia(state, mediaId) { function removeMedia(state, mediaId) {
const media = state.get('media_attachments').find(item => item.get('id') === mediaId);
const prevSize = state.get('media_attachments').size; const prevSize = state.get('media_attachments').size;
return state.withMutations(map => { return state.withMutations(map => {
map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId)); map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
map.update('text', text => text.replace(media.get('text_url'), '').trim());
map.set('idempotencyKey', uuid()); map.set('idempotencyKey', uuid());
if (prevSize === 1) { if (prevSize === 1) {
@ -122,6 +122,18 @@ const insertSuggestion = (state, position, token, completion) => {
}); });
}; };
const updateSuggestionTags = (state, token) => {
const prefix = token.slice(1);
return state.merge({
suggestions: state.get('tagHistory')
.filter(tag => tag.startsWith(prefix))
.slice(0, 4)
.map(tag => '#' + tag),
suggestion_token: token,
});
};
const insertEmoji = (state, position, emojiData) => { const insertEmoji = (state, position, emojiData) => {
const emoji = emojiData.native; const emoji = emojiData.native;
@ -252,6 +264,10 @@ export default function compose(state = initialState, action) {
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token); return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token);
case COMPOSE_SUGGESTION_SELECT: case COMPOSE_SUGGESTION_SELECT:
return insertSuggestion(state, action.position, action.token, action.completion); return insertSuggestion(state, action.position, action.token, action.completion);
case COMPOSE_SUGGESTION_TAGS_UPDATE:
return updateSuggestionTags(state, action.token);
case COMPOSE_TAG_HISTORY_UPDATE:
return state.set('tagHistory', fromJS(action.tags));
case TIMELINE_DELETE: case TIMELINE_DELETE:
if (action.id === state.get('in_reply_to')) { if (action.id === state.get('in_reply_to')) {
return state.set('in_reply_to', null); return state.set('in_reply_to', null);

@ -0,0 +1,18 @@
import Immutable from 'immutable';
import {
DROPDOWN_MENU_OPEN,
DROPDOWN_MENU_CLOSE,
} from '../actions/dropdown_menu';
const initialState = Immutable.Map({ openId: null, placement: null });
export default function dropdownMenu(state = initialState, action) {
switch (action.type) {
case DROPDOWN_MENU_OPEN:
return state.merge({ openId: action.id, placement: action.placement });
case DROPDOWN_MENU_CLOSE:
return state.get('openId') === action.id ? state.set('openId', null) : state;
default:
return state;
}
}

@ -1,4 +1,5 @@
import { combineReducers } from 'redux-immutable'; import { combineReducers } from 'redux-immutable';
import dropdown_menu from './dropdown_menu';
import timelines from './timelines'; import timelines from './timelines';
import meta from './meta'; import meta from './meta';
import alerts from './alerts'; import alerts from './alerts';
@ -26,6 +27,7 @@ import lists from './lists';
import listEditor from './list_editor'; import listEditor from './list_editor';
const reducers = { const reducers = {
dropdown_menu,
timelines, timelines,
meta, meta,
alerts, alerts,

@ -44,3 +44,4 @@ export default class Settings {
} }
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
export const tagHistory = new Settings('mastodon_tag_history');

@ -194,6 +194,28 @@ $small-breakpoint: 960px;
} }
} }
.closed-registrations-message {
margin-top: 20px;
&,
p {
text-align: center;
font-size: 12px;
line-height: 18px;
color: $ui-primary-color;
margin-bottom: 0;
a {
color: $ui-highlight-color;
text-decoration: underline;
}
}
p:last-child {
margin-bottom: 0;
}
}
em { em {
display: inline; display: inline;
margin: 0; margin: 0;
@ -832,8 +854,13 @@ $small-breakpoint: 960px;
} }
&__features { &__features {
& > p {
padding-right: 60px;
}
.features-list { .features-list {
margin: 40px 0 !important; margin: 40px 0;
margin-top: 30px;
} }
&__action { &__action {
@ -842,17 +869,11 @@ $small-breakpoint: 960px;
} }
.features-list { .features-list {
margin-top: 20px;
.features-list__row { .features-list__row {
display: flex; display: flex;
padding: 10px 0; padding: 10px 0;
justify-content: space-between; justify-content: space-between;
&:first-child {
padding-top: 0;
}
.visual { .visual {
flex: 0 0 auto; flex: 0 0 auto;
display: flex; display: flex;
@ -878,6 +899,14 @@ $small-breakpoint: 960px;
} }
} }
} }
@media screen and (min-width: $small-breakpoint) {
display: grid;
grid-gap: 30px;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 50%;
grid-auto-rows: max-content;
}
} }
.extended-description { .extended-description {

@ -105,6 +105,16 @@
margin-bottom: 30px; margin-bottom: 30px;
} }
h4 {
text-transform: uppercase;
font-size: 13px;
font-weight: 500;
color: $ui-primary-color;
padding-bottom: 8px;
margin-bottom: 8px;
border-bottom: 1px solid lighten($ui-base-color, 8%);
}
h6 { h6 {
font-size: 16px; font-size: 16px;
color: $ui-secondary-color; color: $ui-secondary-color;

@ -862,12 +862,27 @@
border-bottom: 1px solid $ui-secondary-color; border-bottom: 1px solid $ui-secondary-color;
display: flex; display: flex;
.status-check-box__status {
margin: 10px 0 10px 10px;
flex: 1;
.media-gallery {
max-width: 250px;
}
.status__content { .status__content {
flex: 1 1 auto; padding: 0;
padding: 10px; white-space: normal;
overflow: hidden; }
text-overflow: ellipsis;
white-space: nowrap; .video-player {
margin-top: 8px;
max-width: 250px;
}
.media-gallery__item-thumbnail {
cursor: default;
}
} }
} }
@ -1418,36 +1433,29 @@
.image-loader { .image-loader {
position: relative; position: relative;
width: 100%;
height: 100%;
&.image-loader--loading { &.image-loader--loading {
display: flex;
align-content: center;
.image-loader__preview-canvas { .image-loader__preview-canvas {
filter: blur(2px); filter: blur(2px);
} }
} }
.image-loader__img { &.image-loader--amorphous .image-loader__preview-canvas {
position: absolute;
top: 0;
left: 0;
right: 0;
max-width: 100%;
max-height: 100%;
background-image: none;
}
&.image-loader--amorphous {
position: static;
.image-loader__preview-canvas {
display: none; display: none;
} }
}
.image-loader__img { .zoomable-image {
position: static; position: relative;
width: auto; width: 100%;
height: auto; height: 100%;
} display: flex;
} align-content: center;
} }
.navigation-bar { .navigation-bar {
@ -2784,33 +2792,26 @@ a.status-card {
} }
} }
.modal-container__nav { .account--follows-info {
align-items: center;
background: rgba($base-overlay-background, 0.5);
box-sizing: border-box;
border: 0;
color: $primary-text-color; color: $primary-text-color;
cursor: pointer;
display: flex;
font-size: 24px;
height: 100%;
padding: 30px 15px;
position: absolute; position: absolute;
top: 0; top: 10px;
} left: 10px;
opacity: 0.7;
.modal-container__nav--left { display: inline-block;
left: -61px; vertical-align: top;
} background-color: rgba($base-overlay-background, 0.4);
text-transform: uppercase;
.modal-container__nav--right { font-size: 11px;
right: -61px; font-weight: 500;
padding: 4px;
border-radius: 4px;
} }
.account--follows-info { .account--muting-info {
color: $primary-text-color; color: $primary-text-color;
position: absolute; position: absolute;
top: 10px; top: 40px;
left: 10px; left: 10px;
opacity: 0.7; opacity: 0.7;
display: inline-block; display: inline-block;
@ -3403,29 +3404,27 @@ a.status-card {
z-index: 9999; z-index: 9999;
} }
.video-modal {
max-width: 100vw;
max-height: 100vh;
position: relative;
}
.media-modal { .media-modal {
max-width: 80vw; width: 100%;
max-height: 80vh; height: 100%;
position: relative; position: relative;
.extended-video-player,
img, img,
canvas, canvas,
video { video {
max-width: 80vw; max-width: 100vw;
max-height: 80vh; max-height: 100vh;
width: auto; width: auto;
height: auto; height: auto;
margin: auto; margin: auto;
} }
.extended-video-player,
video {
display: flex;
width: 80vw;
height: 80vh;
}
img, img,
canvas { canvas {
display: block; display: block;
@ -3434,12 +3433,65 @@ a.status-card {
} }
.react-swipeable-view-container { .react-swipeable-view-container {
max-width: 80vw; width: 100vw;
height: 100%;
} }
} }
.media-modal__content { .media-modal__closer {
background: $base-overlay-background; position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.media-modal__navigation {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
transition: opacity 0.3s linear;
will-change: opacity;
* {
pointer-events: auto;
}
&.media-modal__navigation--hidden {
opacity: 0;
* {
pointer-events: none;
}
}
}
.media-modal__nav {
background: rgba($base-overlay-background, 0.5);
box-sizing: border-box;
border: 0;
color: $primary-text-color;
cursor: pointer;
display: flex;
align-items: center;
font-size: 24px;
height: 20vmax;
margin: auto 0;
padding: 30px 15px;
position: absolute;
top: 0;
bottom: 0;
}
.media-modal__nav--left {
left: 0;
}
.media-modal__nav--right {
right: 0;
} }
.media-modal__pagination { .media-modal__pagination {
@ -3447,7 +3499,8 @@ a.status-card {
text-align: center; text-align: center;
position: absolute; position: absolute;
left: 0; left: 0;
bottom: -40px; bottom: 20px;
pointer-events: none;
} }
.media-modal__page-dot { .media-modal__page-dot {
@ -3471,8 +3524,8 @@ a.status-card {
.media-modal__close { .media-modal__close {
position: absolute; position: absolute;
right: 4px; right: 8px;
top: 4px; top: 8px;
z-index: 100; z-index: 100;
} }
@ -4169,9 +4222,8 @@ a.status-card {
border-radius: 4px; border-radius: 4px;
margin-top: 14px; margin-top: 14px;
overflow: hidden; overflow: hidden;
}
.attachment-list__icon { &__icon {
flex: 0 0 auto; flex: 0 0 auto;
color: $ui-base-lighter-color; color: $ui-base-lighter-color;
padding: 8px 18px; padding: 8px 18px;
@ -4186,9 +4238,9 @@ a.status-card {
.fa { .fa {
display: block; display: block;
} }
} }
.attachment-list__list { &__list {
list-style: none; list-style: none;
padding: 4px 0; padding: 4px 0;
padding-left: 8px; padding-left: 8px;
@ -4210,6 +4262,21 @@ a.status-card {
text-decoration: underline; text-decoration: underline;
} }
} }
}
&.compact {
border: 0;
margin-top: 4px;
.attachment-list__list {
padding: 0;
display: block;
}
.fa {
color: $ui-base-lighter-color;
}
}
} }
/* Media Gallery */ /* Media Gallery */
@ -4613,7 +4680,7 @@ a.status-card {
background-size: cover; background-size: cover;
background-position: center; background-position: center;
position: absolute; position: absolute;
color: inherit; color: $ui-primary-color;
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 4px;
@ -4621,6 +4688,7 @@ a.status-card {
&:active, &:active,
&:focus { &:focus {
outline: 0; outline: 0;
color: $ui-secondary-color;
&::before { &::before {
content: ""; content: "";
@ -4632,6 +4700,14 @@ a.status-card {
} }
} }
} }
&__icons {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 24px;
}
} }
.account__section-headline { .account__section-headline {

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

Loading…
Cancel
Save