Merge pull request #1265 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
This commit is contained in:
ThibG 2020-01-24 17:28:22 +01:00 committed by GitHub
commit 0be67df4f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
272 changed files with 3839 additions and 797 deletions

View File

@ -275,3 +275,13 @@ STREAMING_CLUSTER_NUM=1
# http_proxy=http://gateway.local:8118
# Access control for hidden service.
# ALLOW_ACCESS_TO_HIDDEN_SERVICE=true
# Authorized fetch mode (optional)
# Require remote servers to authentify when fetching toots, see
# https://docs.joinmastodon.org/admin/config/#authorized_fetch
# AUTHORIZED_FETCH=true
# Whitelist mode (optional)
# Only allow federation with whitelisted domains, see
# https://docs.joinmastodon.org/admin/config/#whitelist_mode
# WHITELIST_MODE=true

2
.nvmrc
View File

@ -1 +1 @@
8
12

View File

@ -0,0 +1,69 @@
# frozen_string_literal: true
class Admin::AnnouncementsController < Admin::BaseController
before_action :set_announcements, only: :index
before_action :set_announcement, except: [:index, :new, :create]
def index
authorize :announcement, :index?
end
def new
authorize :announcement, :create?
@announcement = Announcement.new
end
def create
authorize :announcement, :create?
@announcement = Announcement.new(resource_params)
if @announcement.save
log_action :create, @announcement
redirect_to admin_announcements_path
else
render :new
end
end
def edit
authorize :announcement, :update?
end
def update
authorize :announcement, :update?
if @announcement.update(resource_params)
log_action :update, @announcement
redirect_to admin_announcements_path
else
render :edit
end
end
def destroy
authorize :announcement, :destroy?
@announcement.destroy!
log_action :destroy, @announcement
redirect_to admin_announcements_path
end
private
def set_announcements
@announcements = AnnouncementFilter.new(filter_params).results.page(params[:page])
end
def set_announcement
@announcement = Announcement.find(params[:id])
end
def filter_params
params.slice(*AnnouncementFilter::KEYS).permit(*AnnouncementFilter::KEYS)
end
def resource_params
params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
end
end

View File

@ -1,18 +0,0 @@
# frozen_string_literal: true
module Admin
class FollowersController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@followers = @account.followers.local.recent.page(params[:page]).per(PER_PAGE)
end
def set_account
@account = Account.find(params[:account_id])
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module Admin
class RelationshipsController < BaseController
before_action :set_account
PER_PAGE = 40
def index
authorize :account, :index?
@accounts = RelationshipFilter.new(@account, filter_params).results.page(params[:page]).per(PER_PAGE)
end
private
def set_account
@account = Account.find(params[:account_id])
end
def filter_params
params.slice(*RelationshipFilter::KEYS).permit(*RelationshipFilter::KEYS)
end
end
end

View File

@ -85,7 +85,7 @@ class Api::BaseController < ApplicationController
end
def require_authenticated_user!
render json: { error: 'This API requires an authenticated user' }, status: 401 unless current_user
render json: { error: 'This method requires an authenticated user' }, status: 401 unless current_user
end
def require_user!

View File

@ -1,17 +1,25 @@
# frozen_string_literal: true
class Api::OEmbedController < Api::BaseController
respond_to :json
skip_before_action :require_authenticated_user!
before_action :set_status
before_action :require_public_status!
def show
@status = status_finder.status
render json: @status, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
end
private
def set_status
@status = status_finder.status
end
def require_public_status!
not_found if @status.hidden?
end
def status_finder
StatusFinder.new(params[:url])
end

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class Api::V1::Announcements::ReactionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user!
before_action :set_announcement
before_action :set_reaction, except: :update
def update
@announcement.announcement_reactions.create!(account: current_account, name: params[:id])
render_empty
end
def destroy
@reaction.destroy!
render_empty
end
private
def set_reaction
@reaction = @announcement.announcement_reactions.where(account: current_account).find_by!(name: params[:id])
end
def set_announcement
@announcement = Announcement.published.find(params[:announcement_id])
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
class Api::V1::AnnouncementsController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: :dismiss
before_action :require_user!
before_action :set_announcements, only: :index
before_action :set_announcement, except: :index
def index
render json: @announcements, each_serializer: REST::AnnouncementSerializer
end
def dismiss
AnnouncementMute.create!(account: current_account, announcement: @announcement)
render_empty
end
private
def set_announcements
@announcements = begin
scope = Announcement.published
scope.merge!(Announcement.without_muted(current_account)) unless truthy_param?(:with_dismissed)
scope.chronological
end
end
def set_announcement
@announcement = Announcement.published.find(params[:id])
end
end

View File

@ -7,6 +7,12 @@ class Auth::PasswordsController < Devise::PasswordsController
layout 'auth'
def update
super do |resource|
resource.session_activations.destroy_all if resource.errors.empty?
end
end
private
def check_validity_of_reset_password_token

View File

@ -23,10 +23,17 @@ class Auth::RegistrationsController < Devise::RegistrationsController
not_found
end
def update
super do |resource|
resource.clear_other_sessions(current_session.session_id) if resource.saved_change_to_encrypted_password?
end
end
protected
def update_resource(resource, params)
params[:password] = nil if Devise.pam_authentication && resource.encrypted_password.blank?
super
end

View File

@ -20,53 +20,13 @@ class RelationshipsController < ApplicationController
rescue ActionController::ParameterMissing
# Do nothing
ensure
redirect_to relationships_path(current_params)
redirect_to relationships_path(filter_params)
end
private
def set_accounts
@accounts = relationships_scope.page(params[:page]).per(40)
end
def relationships_scope
scope = begin
if following_relationship?
current_account.following.eager_load(:account_stat).reorder(nil)
else
current_account.followers.eager_load(:account_stat).reorder(nil)
end
end
scope.merge!(Follow.recent) if params[:order].blank? || params[:order] == 'recent'
scope.merge!(Account.by_recent_status) if params[:order] == 'active'
scope.merge!(mutual_relationship_scope) if mutual_relationship?
scope.merge!(moved_account_scope) if params[:status] == 'moved'
scope.merge!(primary_account_scope) if params[:status] == 'primary'
scope.merge!(by_domain_scope) if params[:by_domain].present?
scope.merge!(dormant_account_scope) if params[:activity] == 'dormant'
scope
end
def mutual_relationship_scope
Account.where(id: current_account.following)
end
def moved_account_scope
Account.where.not(moved_to_account_id: nil)
end
def primary_account_scope
Account.where(moved_to_account_id: nil)
end
def dormant_account_scope
AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago)))
end
def by_domain_scope
Account.where(domain: params[:by_domain])
@accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
end
def form_account_batch_params
@ -85,7 +45,7 @@ class RelationshipsController < ApplicationController
params[:relationship] == 'followed_by'
end
def current_params
def filter_params
params.slice(:page, *RelationshipFilter::KEYS).permit(:page, *RelationshipFilter::KEYS)
end

View File

@ -49,7 +49,7 @@ class StatusesController < ApplicationController
def embed
use_pack 'embed'
raise ActiveRecord::RecordNotFound if @status.hidden?
return not_found if @status.hidden?
expires_in 180, public: true
response.headers['X-Frame-Options'] = 'ALLOWALL'
@ -71,7 +71,7 @@ class StatusesController < ApplicationController
@status = @account.statuses.find(params[:id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound
not_found
end
def set_instance_presenter

View File

@ -22,6 +22,8 @@ module Admin::ActionLogsHelper
log.recorded_changes.slice('severity', 'reject_media')
elsif log.target_type == 'Status' && log.action == :update
log.recorded_changes.slice('sensitive')
elsif log.target_type == 'Announcement' && log.action == :update
log.recorded_changes.slice('text', 'starts_at', 'ends_at', 'all_day')
end
end
@ -52,6 +54,8 @@ module Admin::ActionLogsHelper
'pencil'
when 'AccountWarning'
'warning'
when 'Announcement'
'bullhorn'
end
end
@ -94,6 +98,8 @@ module Admin::ActionLogsHelper
link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
when 'AccountWarning'
link_to record.target_account.acct, admin_account_path(record.target_account_id)
when 'Announcement'
link_to "##{record.id}", edit_admin_announcement_path(record.id)
end
end
@ -111,6 +117,8 @@ module Admin::ActionLogsHelper
else
I18n.t('admin.action_logs.deleted_status')
end
when 'Announcement'
"##{attributes['id']}"
end
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Admin::AnnouncementsHelper
def time_range(announcement)
if announcement.all_day?
safe_join([l(announcement.starts_at.to_date), ' - ', l(announcement.ends_at.to_date)])
else
safe_join([l(announcement.starts_at), ' - ', l(announcement.ends_at)])
end
end
end

View File

@ -9,6 +9,7 @@ module Admin::FilterHelper
InstanceFilter::KEYS,
InviteFilter::KEYS,
RelationshipFilter::KEYS,
AnnouncementFilter::KEYS,
].flatten.freeze
def filter_link_to(text, link_to_params, link_class_params = link_to_params)

View File

@ -0,0 +1,133 @@
import api from 'flavours/glitch/util/api';
import { normalizeAnnouncement } from './importer/normalizer';
export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
export const ANNOUNCEMENTS_FETCH_FAIL = 'ANNOUNCEMENTS_FETCH_FAIL';
export const ANNOUNCEMENTS_UPDATE = 'ANNOUNCEMENTS_UPDATE';
export const ANNOUNCEMENTS_DISMISS = 'ANNOUNCEMENTS_DISMISS';
export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
export const ANNOUNCEMENTS_REACTION_ADD_FAIL = 'ANNOUNCEMENTS_REACTION_ADD_FAIL';
export const ANNOUNCEMENTS_REACTION_REMOVE_REQUEST = 'ANNOUNCEMENTS_REACTION_REMOVE_REQUEST';
export const ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS = 'ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS';
export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL = 'ANNOUNCEMENTS_REACTION_REMOVE_FAIL';
export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
const noOp = () => {};
export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
dispatch(fetchAnnouncementsRequest());
api(getState).get('/api/v1/announcements').then(response => {
dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
}).catch(error => {
dispatch(fetchAnnouncementsFail(error));
}).finally(() => {
done();
});
};
export const fetchAnnouncementsRequest = () => ({
type: ANNOUNCEMENTS_FETCH_REQUEST,
skipLoading: true,
});
export const fetchAnnouncementsSuccess = announcements => ({
type: ANNOUNCEMENTS_FETCH_SUCCESS,
announcements,
skipLoading: true,
});
export const fetchAnnouncementsFail= error => ({
type: ANNOUNCEMENTS_FETCH_FAIL,
error,
skipLoading: true,
skipAlert: true,
});
export const updateAnnouncements = announcement => ({
type: ANNOUNCEMENTS_UPDATE,
announcement: normalizeAnnouncement(announcement),
});
export const dismissAnnouncement = announcementId => (dispatch, getState) => {
dispatch({
type: ANNOUNCEMENTS_DISMISS,
id: announcementId,
});
api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
};
export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name));
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
dispatch(addReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(addReactionFail(announcementId, name, err));
});
};
export const addReactionRequest = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_ADD_REQUEST,
id: announcementId,
name,
skipLoading: true,
});
export const addReactionSuccess = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_ADD_SUCCESS,
id: announcementId,
name,
skipLoading: true,
});
export const addReactionFail = (announcementId, name, error) => ({
type: ANNOUNCEMENTS_REACTION_ADD_FAIL,
id: announcementId,
name,
error,
skipLoading: true,
});
export const removeReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(removeReactionRequest(announcementId, name));
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(removeReactionFail(announcementId, name, err));
});
};
export const removeReactionRequest = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
id: announcementId,
name,
skipLoading: true,
});
export const removeReactionSuccess = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS,
id: announcementId,
name,
skipLoading: true,
});
export const removeReactionFail = (announcementId, name, error) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
id: announcementId,
name,
error,
skipLoading: true,
});
export const updateReaction = reaction => ({
type: ANNOUNCEMENTS_REACTION_UPDATE,
reaction,
});

View File

@ -74,7 +74,6 @@ export function normalizeStatus(status, normalOldStatus) {
export function normalizePoll(poll) {
const normalPoll = { ...poll };
const emojiMap = makeEmojiMap(normalPoll);
normalPoll.options = poll.options.map((option, index) => ({
@ -85,3 +84,12 @@ export function normalizePoll(poll) {
return normalPoll;
}
export function normalizeAnnouncement(announcement) {
const normalAnnouncement = { ...announcement };
const emojiMap = makeEmojiMap(normalAnnouncement);
normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
return normalAnnouncement;
}

View File

@ -168,9 +168,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
fetchRelatedRelationships(dispatch, response.data);
done();
}).catch(error => {
dispatch(expandNotificationsFail(error, isLoadingMore));
}).finally(() => {
done();
});
};
@ -199,6 +199,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
type: NOTIFICATIONS_EXPAND_FAIL,
error,
skipLoading: !isLoadingMore,
skipAlert: !isLoadingMore,
};
};

View File

@ -8,6 +8,7 @@ import {
} from './timelines';
import { updateNotifications, expandNotifications } from './notifications';
import { updateConversations } from './conversations';
import { fetchAnnouncements, updateAnnouncements, updateReaction as updateAnnouncementsReaction } from './announcements';
import { fetchFilters } from './filters';
import { getLocale } from 'mastodon/locales';
@ -44,6 +45,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
case 'filters_changed':
dispatch(fetchFilters());
break;
case 'announcement':
dispatch(updateAnnouncements(JSON.parse(data.payload)));
break;
case 'announcement.reaction':
dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
break;
}
},
};
@ -51,7 +58,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
}
const refreshHomeTimelineAndNotification = (dispatch, done) => {
dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
dispatch(expandHomeTimeline({}, () =>
dispatch(expandNotifications({}, () =>
dispatch(fetchAnnouncements(done))))));
};
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);

View File

@ -112,9 +112,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
done();
}).catch(error => {
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
}).finally(() => {
done();
});
};

View File

@ -112,6 +112,7 @@ class AccountTimeline extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
bindToDocument={!multiColumn}
timelineId='account'
/>
</Column>
);

View File

@ -22,6 +22,7 @@ const messages = defineMessages({
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
});
const makeMapStateToProps = () => {

View File

@ -372,6 +372,7 @@ class EmojiPickerDropdown extends React.PureComponent {
onPickEmoji: PropTypes.func.isRequired,
onSkinTone: PropTypes.func.isRequired,
skinTone: PropTypes.number.isRequired,
button: PropTypes.node,
};
state = {
@ -432,18 +433,18 @@ class EmojiPickerDropdown extends React.PureComponent {
}
render () {
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
const title = intl.formatMessage(messages.emoji);
const { active, loading, placement } = this.state;
return (
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
<img
{button || <img
className={classNames('emojione', { 'pulse-loading': active && loading })}
alt='🙂'
src={`${assetHost}/emoji/1f602.svg`}
/>
/>}
</div>
<Overlay show={active} placement={placement} target={this.findTarget}>

View File

@ -0,0 +1,395 @@
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import IconButton from 'flavours/glitch/components/icon_button';
import Icon from 'flavours/glitch/components/icon';
import { defineMessages, injectIntl, FormattedMessage, FormattedDate, FormattedNumber } from 'react-intl';
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
import { mascot } from 'flavours/glitch/util/initial_state';
import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light';
import classNames from 'classnames';
import EmojiPickerDropdown from 'flavours/glitch/features/emoji_picker';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
next: { id: 'lightbox.next', defaultMessage: 'Next' },
});
class Content extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
announcement: ImmutablePropTypes.map.isRequired,
};
setRef = c => {
this.node = c;
}
componentDidMount () {
this._updateLinks();
this._updateEmojis();
}
componentDidUpdate () {
this._updateLinks();
this._updateEmojis();
}
_updateEmojis () {
const node = this.node;
if (!node || autoPlayGif) {
return;
}
const emojis = node.querySelectorAll('.custom-emoji');
for (var i = 0; i < emojis.length; i++) {
let emoji = emojis[i];
if (emoji.classList.contains('status-emoji')) {
continue;
}
emoji.classList.add('status-emoji');
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
}
}
_updateLinks () {
const node = this.node;
if (!node) {
return;
}
const links = node.querySelectorAll('a');
for (var i = 0; i < links.length; ++i) {
let link = links[i];
if (link.classList.contains('status-link')) {
continue;
}
link.classList.add('status-link');
let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url'));
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
link.setAttribute('title', mention.get('acct'));
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
} else {
link.setAttribute('title', link.href);
link.classList.add('unhandled-link');
}
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}
}
onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/accounts/${mention.get('id')}`);
}
}
onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, '');
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/timelines/tag/${hashtag}`);
}
}
handleEmojiMouseEnter = ({ target }) => {
target.src = target.getAttribute('data-original');
}
handleEmojiMouseLeave = ({ target }) => {
target.src = target.getAttribute('data-static');
}
render () {
const { announcement } = this.props;
return (
<div
className='announcements__item__content'
ref={this.setRef}
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
/>
);
}
}
const assetHost = process.env.CDN_HOST || '';
class Emoji extends React.PureComponent {
static propTypes = {
emoji: PropTypes.string.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
hovered: PropTypes.bool.isRequired,
};
render () {
const { emoji, emojiMap, hovered } = this.props;
if (unicodeMapping[emoji]) {
const { filename, shortCode } = unicodeMapping[this.props.emoji];
const title = shortCode ? `:${shortCode}:` : '';
return (
<img
draggable='false'
className='emojione'
alt={emoji}
title={title}
src={`${assetHost}/emoji/${filename}.svg`}
/>
);
} else if (emojiMap.get(emoji)) {
const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
const shortCode = `:${emoji}:`;
return (
<img
draggable='false'
className='emojione custom-emoji'
alt={shortCode}
title={shortCode}
src={filename}
/>
);
} else {
return null;
}
}
}
class Reaction extends ImmutablePureComponent {
static propTypes = {
announcementId: PropTypes.string.isRequired,
reaction: ImmutablePropTypes.map.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
};
state = {
hovered: false,
};
handleClick = () => {
const { reaction, announcementId, addReaction, removeReaction } = this.props;
if (reaction.get('me')) {
removeReaction(announcementId, reaction.get('name'));
} else {
addReaction(announcementId, reaction.get('name'));
}
}
handleMouseEnter = () => this.setState({ hovered: true })
handleMouseLeave = () => this.setState({ hovered: false })
render () {
const { reaction } = this.props;
let shortCode = reaction.get('name');
if (unicodeMapping[shortCode]) {
shortCode = unicodeMapping[shortCode].shortCode;
}
return (
<button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`}>
<span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} /></span>
<span className='reactions-bar__item__count'><FormattedNumber value={reaction.get('count')} /></span>
</button>
);
}
}
class ReactionsBar extends ImmutablePureComponent {
static propTypes = {
announcementId: PropTypes.string.isRequired,
reactions: ImmutablePropTypes.list.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
};
handleEmojiPick = data => {
const { addReaction, announcementId } = this.props;
addReaction(announcementId, data.native.replace(/:/g, ''));
}
render () {
const { reactions } = this.props;
const visibleReactions = reactions.filter(x => x.get('count') > 0);
return (
<div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
{visibleReactions.map(reaction => (
<Reaction
key={reaction.get('name')}
reaction={reaction}
announcementId={this.props.announcementId}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
emojiMap={this.props.emojiMap}
/>
))}
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' />} />
</div>
);
}
}
class Announcement extends ImmutablePureComponent {
static propTypes = {
announcement: ImmutablePropTypes.map.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
dismissAnnouncement: PropTypes.func.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleDismissClick = () => {
const { dismissAnnouncement, announcement } = this.props;
dismissAnnouncement(announcement.get('id'));
}
render () {
const { announcement, intl } = this.props;
const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
const now = new Date();
const hasTimeRange = startsAt && endsAt;
const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
const skipTime = announcement.get('all_day');
return (
<div className='announcements__item'>
<strong className='announcements__item__range'>
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
{hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
</strong>
<Content announcement={announcement} />
<ReactionsBar
reactions={announcement.get('reactions')}
announcementId={announcement.get('id')}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
emojiMap={this.props.emojiMap}
/>
<IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
</div>
);
}
}
export default @injectIntl
class Announcements extends ImmutablePureComponent {
static propTypes = {
announcements: ImmutablePropTypes.list,
emojiMap: ImmutablePropTypes.map.isRequired,
fetchAnnouncements: PropTypes.func.isRequired,
dismissAnnouncement: PropTypes.func.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
state = {
index: 0,
};
componentDidMount () {
const { fetchAnnouncements } = this.props;
fetchAnnouncements();
}
handleChangeIndex = index => {
this.setState({ index: index % this.props.announcements.size });
}
handleNextClick = () => {
this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
}
handlePrevClick = () => {
this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
}
render () {
const { announcements, intl } = this.props;
const { index } = this.state;
if (announcements.isEmpty()) {
return null;
}
return (
<div className='announcements'>
<img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
<div className='announcements__container'>
<ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
{announcements.map(announcement => (
<Announcement
key={announcement.get('id')}
announcement={announcement}
emojiMap={this.props.emojiMap}
dismissAnnouncement={this.props.dismissAnnouncement}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
intl={intl}
/>
))}
</ReactSwipeableViews>
<div className='announcements__pagination'>
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' onClick={this.handlePrevClick} size={13} />
<span>{index + 1} / {announcements.size}</span>
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' onClick={this.handleNextClick} size={13} />
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
import Announcements from '../components/announcements';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
const mapStateToProps = state => ({
announcements: state.getIn(['announcements', 'items']),
emojiMap: customEmojiMap(state),
});
const mapDispatchToProps = dispatch => ({
fetchAnnouncements: () => dispatch(fetchAnnouncements()),
dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
addReaction: (id, name) => dispatch(addReaction(id, name)),
removeReaction: (id, name) => dispatch(removeReaction(id, name)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Announcements);

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { fetchTrends } from '../../../actions/trends';
import { fetchTrends } from 'mastodon/actions/trends';
import Trends from '../components/trends';
const mapStateToProps = state => ({

View File

@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { Link } from 'react-router-dom';
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
@ -112,6 +113,8 @@ class HomeTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
prepend={<AnnouncementsContainer />}
alwaysPrepend
trackScroll={!pinned}
scrollKey={`home_timeline-${columnId}`}
onLoadMore={this.handleLoadMore}

View File

@ -191,7 +191,6 @@ class MediaModal extends ImmutablePureComponent {
style={swipeableViewsStyle}
containerStyle={containerStyle}
onChangeIndex={this.handleSwipe}
onSwitching={this.handleSwitching}
index={index}
>
{content}

View File

@ -0,0 +1,72 @@
import {
ANNOUNCEMENTS_FETCH_REQUEST,
ANNOUNCEMENTS_FETCH_SUCCESS,
ANNOUNCEMENTS_FETCH_FAIL,
ANNOUNCEMENTS_UPDATE,
ANNOUNCEMENTS_DISMISS,
ANNOUNCEMENTS_REACTION_UPDATE,
ANNOUNCEMENTS_REACTION_ADD_REQUEST,
ANNOUNCEMENTS_REACTION_ADD_FAIL,
ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
} from '../actions/announcements';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableMap({
items: ImmutableList(),
isLoading: false,
});
const updateReaction = (state, id, name, updater) => state.update('items', list => list.map(announcement => {
if (announcement.get('id') === id) {
return announcement.update('reactions', reactions => {
if (reactions.find(reaction => reaction.get('name') === name)) {
return reactions.map(reaction => {
if (reaction.get('name') === name) {
return updater(reaction);
}
return reaction;
});
}
return reactions.push(updater(fromJS({ name, count: 0 })));
});
}
return announcement;
}));
const updateReactionCount = (state, reaction) => updateReaction(state, reaction.announcement_id, reaction.name, x => x.set('count', reaction.count));
const addReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', true).update('count', y => y + 1));
const removeReaction = (state, id, name) => updateReaction(state, id, name, x => x.set('me', false).update('count', y => y - 1));
export default function announcementsReducer(state = initialState, action) {
switch(action.type) {
case ANNOUNCEMENTS_FETCH_REQUEST:
return state.set('isLoading', true);
case ANNOUNCEMENTS_FETCH_SUCCESS:
return state.withMutations(map => {
map.set('items', fromJS(action.announcements));
map.set('isLoading', false);
});
case ANNOUNCEMENTS_FETCH_FAIL:
return state.set('isLoading', false);
case ANNOUNCEMENTS_UPDATE:
return state.update('items', list => list.unshift(fromJS(action.announcement)).sortBy(announcement => announcement.get('starts_at')));
case ANNOUNCEMENTS_DISMISS:
return state.update('items', list => list.filterNot(announcement => announcement.get('id') === action.id));
case ANNOUNCEMENTS_REACTION_UPDATE:
return updateReactionCount(state, action.reaction);
case ANNOUNCEMENTS_REACTION_ADD_REQUEST:
case ANNOUNCEMENTS_REACTION_REMOVE_FAIL:
return addReaction(state, action.id, action.name);
case ANNOUNCEMENTS_REACTION_REMOVE_REQUEST:
case ANNOUNCEMENTS_REACTION_ADD_FAIL:
return removeReaction(state, action.id, action.name);
default:
return state;
}
};

View File

@ -35,8 +35,10 @@ import pinnedAccountsEditor from './pinned_accounts_editor';
import polls from './polls';
import identity_proofs from './identity_proofs';
import trends from './trends';
import announcements from './announcements';
const reducers = {
announcements,
dropdown_menu,
timelines,
meta,

View File

@ -27,6 +27,7 @@ export const toServerSideType = columnType => {
case 'notifications':
case 'public':
case 'thread':
case 'account':
return columnType;
default:
if (columnType.indexOf('list:') > -1) {

View File

@ -0,0 +1,212 @@
.announcements__item__content {
word-wrap: break-word;
.emojione {
width: 20px;
height: 20px;
margin: -3px 0 0;
}
p {
margin-bottom: 10px;
white-space: pre-wrap;
&:last-child {
margin-bottom: 0;
}
}
a {
color: $highlight-text-color;
text-decoration: none;
&:hover {
text-decoration: underline;
}
&.mention {
&:hover {
text-decoration: none;
span {
text-decoration: underline;
}
}
}
}
}
.announcements {
background: lighten($ui-base-color, 4%);
border-top: 1px solid $ui-base-color;
font-size: 13px;
display: flex;
align-items: flex-end;
&__mastodon {
width: 124px;
flex: 0 0 auto;
@media screen and (max-width: 124px + 300px) {
display: none;
}
}
&__container {
width: calc(100% - 124px);
flex: 0 0 auto;
position: relative;
@media screen and (max-width: 124px + 300px) {
width: 100%;
}
}
&__item {
box-sizing: border-box;
width: 100%;
padding: 15px;
padding-right: 15px + 18px;
position: relative;
&__range {
display: block;
font-weight: 500;
margin-bottom: 10px;
}
&__dismiss-icon {
position: absolute;
top: 12px;
right: 12px;
}
}
&__pagination {
padding: 15px;
color: $darker-text-color;
position: absolute;
bottom: 3px;
right: 0;
}
}
.layout-multiple-columns .announcements__mastodon {
display: none;
}
.layout-multiple-columns .announcements__container {
width: 100%;
}
.reactions-bar {
display: flex;
flex-wrap: wrap;
align-items: center;
margin-top: 15px;
margin-left: -2px;
width: calc(100% - (90px - 33px));
&__item {
flex-shrink: 0;
background: lighten($ui-base-color, 12%);
border: 0;
border-radius: 3px;
margin: 2px;
cursor: pointer;
user-select: none;
padding: 0 6px;
display: flex;
align-items: center;
transition: all 100ms ease-in;
transition-property: background-color, color;
&__emoji {
display: block;
margin: 3px 0;
width: 16px;
height: 16px;
img {
display: block;
margin: 0;
width: 100%;
height: 100%;
min-width: auto;
min-height: auto;
vertical-align: bottom;
object-fit: contain;
}
}
&__count {
display: block;
min-width: 9px;
font-size: 13px;
font-weight: 500;
text-align: center;
margin-left: 6px;
color: $darker-text-color;
}
&:hover,
&:focus,
&:active {
background: lighten($ui-base-color, 16%);
transition: all 200ms ease-out;
transition-property: background-color, color;
&__count {
color: lighten($darker-text-color, 4%);
}
}
&.active {
transition: all 100ms ease-in;
transition-property: background-color, color;
background-color: mix(lighten($ui-base-color, 12%), $ui-highlight-color, 90%);
.reactions-bar__item__count {
color: $highlight-text-color;
}
}
}
.emoji-picker-dropdown {
margin: 2px;
}
&:hover .emoji-button {
opacity: 0.85;
}
.emoji-button {
color: $darker-text-color;
margin: 0;
font-size: 16px;
width: auto;
flex-shrink: 0;
padding: 0 6px;
height: 22px;
display: flex;
align-items: center;
opacity: 0.5;
transition: all 100ms ease-in;
transition-property: background-color, color;
&:hover,
&:active,
&:focus {
opacity: 1;
color: lighten($darker-text-color, 4%);
transition: all 200ms ease-out;
transition-property: background-color, color;
}
}
&--empty {
.emoji-button {
padding: 0;
}
}
}

View File

@ -1,5 +1,16 @@
.composer {
padding: 10px;
.emoji-picker-dropdown {
position: absolute;
right: 5px;
top: 5px;
::-webkit-scrollbar-track:hover,
::-webkit-scrollbar-track:active {
background-color: rgba($base-overlay-background, 0.3);
}
}
}
.character-counter {
@ -235,17 +246,6 @@
}
}
.emoji-picker-dropdown {
position: absolute;
right: 5px;
top: 5px;
::-webkit-scrollbar-track:hover,
::-webkit-scrollbar-track:active {
background-color: rgba($base-overlay-background, 0.3);
}
}
.compose-form__autosuggest-wrapper,
.autosuggest-input {
position: relative;

View File

@ -1649,3 +1649,4 @@ noscript {
@import 'local_settings';
@import 'error_boundary';
@import 'single_column';
@import 'announcements';

View File

@ -213,6 +213,12 @@ code {
}
}
.input.datetime .label_input select {
display: inline-block;
width: auto;
flex: 0;
}
.required abbr {
text-decoration: none;
color: lighten($error-value-color, 12%);

View File

@ -2,6 +2,14 @@ import WebSocketClient from '@gamestdio/websocket';
const randomIntUpTo = max => Math.floor(Math.random() * Math.floor(max));
const knownEventTypes = [
'update',
'delete',
'notification',
'conversation',
'filters_changed',
];
export function connectStream(path, pollingRefresh = null, callbacks = () => ({ onConnect() {}, onDisconnect() {}, onReceive() {} })) {
return (dispatch, getState) => {
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
@ -69,14 +77,42 @@ export function connectStream(path, pollingRefresh = null, callbacks = () => ({
export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
const params = [ `stream=${stream}` ];
const params = stream.split('&');
stream = params.shift();
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
if (streamingAPIBaseURL.startsWith('ws')) {
params.unshift(`stream=${stream}`);
const ws = new WebSocketClient(`${streamingAPIBaseURL}/api/v1/streaming/?${params.join('&')}`, accessToken);
ws.onopen = connected;
ws.onmessage = e => received(JSON.parse(e.data));
ws.onclose = disconnected;
ws.onreconnect = reconnected;
ws.onopen = connected;
ws.onmessage = e => received(JSON.parse(e.data));
ws.onclose = disconnected;
ws.onreconnect = reconnected;
return ws;
return ws;
}
params.push(`access_token=${accessToken}`);
const es = new EventSource(`${streamingAPIBaseURL}/api/v1/streaming/${stream}?${params.join('&')}`);
let firstConnect = true;
es.onopen = () => {
if (firstConnect) {
firstConnect = false;
connected();
} else {
reconnected();
}
};
for (let type of knownEventTypes) {
es.addEventListener(type, (e) => {
received({
event: e.type,
payload: e.data,
});
});
}
es.onerror = disconnected;
return es;
};

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,133 @@
import api from '../api';
import { normalizeAnnouncement } from './importer/normalizer';
export const ANNOUNCEMENTS_FETCH_REQUEST = 'ANNOUNCEMENTS_FETCH_REQUEST';
export const ANNOUNCEMENTS_FETCH_SUCCESS = 'ANNOUNCEMENTS_FETCH_SUCCESS';
export const ANNOUNCEMENTS_FETCH_FAIL = 'ANNOUNCEMENTS_FETCH_FAIL';
export const ANNOUNCEMENTS_UPDATE = 'ANNOUNCEMENTS_UPDATE';
export const ANNOUNCEMENTS_DISMISS = 'ANNOUNCEMENTS_DISMISS';
export const ANNOUNCEMENTS_REACTION_ADD_REQUEST = 'ANNOUNCEMENTS_REACTION_ADD_REQUEST';
export const ANNOUNCEMENTS_REACTION_ADD_SUCCESS = 'ANNOUNCEMENTS_REACTION_ADD_SUCCESS';
export const ANNOUNCEMENTS_REACTION_ADD_FAIL = 'ANNOUNCEMENTS_REACTION_ADD_FAIL';
export const ANNOUNCEMENTS_REACTION_REMOVE_REQUEST = 'ANNOUNCEMENTS_REACTION_REMOVE_REQUEST';
export const ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS = 'ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS';
export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL = 'ANNOUNCEMENTS_REACTION_REMOVE_FAIL';
export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
const noOp = () => {};
export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
dispatch(fetchAnnouncementsRequest());
api(getState).get('/api/v1/announcements').then(response => {
dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
}).catch(error => {
dispatch(fetchAnnouncementsFail(error));
}).finally(() => {
done();
});
};
export const fetchAnnouncementsRequest = () => ({
type: ANNOUNCEMENTS_FETCH_REQUEST,
skipLoading: true,
});
export const fetchAnnouncementsSuccess = announcements => ({
type: ANNOUNCEMENTS_FETCH_SUCCESS,
announcements,
skipLoading: true,
});
export const fetchAnnouncementsFail= error => ({
type: ANNOUNCEMENTS_FETCH_FAIL,
error,
skipLoading: true,
skipAlert: true,
});
export const updateAnnouncements = announcement => ({
type: ANNOUNCEMENTS_UPDATE,
announcement: normalizeAnnouncement(announcement),
});
export const dismissAnnouncement = announcementId => (dispatch, getState) => {
dispatch({
type: ANNOUNCEMENTS_DISMISS,
id: announcementId,
});
api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`);
};
export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name));
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
dispatch(addReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(addReactionFail(announcementId, name, err));
});
};
export const addReactionRequest = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_ADD_REQUEST,
id: announcementId,
name,
skipLoading: true,
});
export const addReactionSuccess = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_ADD_SUCCESS,
id: announcementId,
name,
skipLoading: true,
});
export const addReactionFail = (announcementId, name, error) => ({
type: ANNOUNCEMENTS_REACTION_ADD_FAIL,
id: announcementId,
name,
error,
skipLoading: true,
});
export const removeReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(removeReactionRequest(announcementId, name));
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${name}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(removeReactionFail(announcementId, name, err));
});
};
export const removeReactionRequest = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_REQUEST,
id: announcementId,
name,
skipLoading: true,
});
export const removeReactionSuccess = (announcementId, name) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_SUCCESS,
id: announcementId,
name,
skipLoading: true,
});
export const removeReactionFail = (announcementId, name, error) => ({
type: ANNOUNCEMENTS_REACTION_REMOVE_FAIL,
id: announcementId,
name,
error,
skipLoading: true,
});
export const updateReaction = reaction => ({
type: ANNOUNCEMENTS_REACTION_UPDATE,
reaction,
});

View File

@ -76,7 +76,6 @@ export function normalizeStatus(status, normalOldStatus) {
export function normalizePoll(poll) {
const normalPoll = { ...poll };
const emojiMap = makeEmojiMap(normalPoll);
normalPoll.options = poll.options.map((option, index) => ({
@ -87,3 +86,12 @@ export function normalizePoll(poll) {
return normalPoll;
}
export function normalizeAnnouncement(announcement) {
const normalAnnouncement = { ...announcement };
const emojiMap = makeEmojiMap(normalAnnouncement);
normalAnnouncement.contentHtml = emojify(normalAnnouncement.content, emojiMap);
return normalAnnouncement;
}

View File

@ -157,9 +157,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
fetchRelatedRelationships(dispatch, response.data);
done();
}).catch(error => {
dispatch(expandNotificationsFail(error, isLoadingMore));
}).finally(() => {
done();
});
};
@ -188,6 +188,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
type: NOTIFICATIONS_EXPAND_FAIL,
error,
skipLoading: !isLoadingMore,
skipAlert: !isLoadingMore,
};
};

View File

@ -8,6 +8,7 @@ import {
} from './timelines';
import { updateNotifications, expandNotifications } from './notifications';
import { updateConversations } from './conversations';
import { fetchAnnouncements, updateAnnouncements, updateReaction as updateAnnouncementsReaction } from './announcements';
import { fetchFilters } from './filters';
import { getLocale } from '../locales';
@ -44,6 +45,12 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
case 'filters_changed':
dispatch(fetchFilters());
break;
case 'announcement':
dispatch(updateAnnouncements(JSON.parse(data.payload)));
break;
case 'announcement.reaction':
dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
break;
}
},
};
@ -51,7 +58,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
}
const refreshHomeTimelineAndNotification = (dispatch, done) => {
dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
dispatch(expandHomeTimeline({}, () =>
dispatch(expandNotifications({}, () =>
dispatch(fetchAnnouncements(done))))));
};
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);

View File

@ -98,9 +98,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
done();
}).catch(error => {
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
}).finally(() => {
done();
});
};

View File

@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.PureComponent {
<div>
<p className='error-boundary__error'><FormattedMessage id='error.unexpected_crash.explanation' defaultMessage='Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.' /></p>
<p><FormattedMessage id='error.unexpected_crash.next_steps' defaultMessage='Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.' /></p>
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied && 'copied'}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
<p className='error-boundary__footer'>Mastodon v{version} · <a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='errors.unexpected_crash.report_issue' defaultMessage='Report issue' /></a> · <button onClick={this.handleCopyStackTrace} className={copied ? 'copied' : ''}><FormattedMessage id='errors.unexpected_crash.copy_stacktrace' defaultMessage='Copy stacktrace to clipboard' /></button></p>
</div>
</div>
);

View File

@ -115,6 +115,7 @@ class AccountTimeline extends ImmutablePureComponent {
shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
timelineId='account'
/>
</Column>
);

View File

@ -290,6 +290,7 @@ class EmojiPickerDropdown extends React.PureComponent {
onPickEmoji: PropTypes.func.isRequired,
onSkinTone: PropTypes.func.isRequired,
skinTone: PropTypes.number.isRequired,
button: PropTypes.node,
};
state = {
@ -350,18 +351,18 @@ class EmojiPickerDropdown extends React.PureComponent {
}
render () {
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis, button } = this.props;
const title = intl.formatMessage(messages.emoji);
const { active, loading, placement } = this.state;
return (
<div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
<div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
<img
{button || <img
className={classNames('emojione', { 'pulse-loading': active && loading })}
alt='🙂'
src={`${assetHost}/emoji/1f602.svg`}
/>
/>}
</div>
<Overlay show={active} placement={placement} target={this.findTarget}>

View File

@ -22,6 +22,7 @@ const messages = defineMessages({
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
});
const makeMapStateToProps = () => {

View File

@ -0,0 +1,395 @@
import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeableViews from 'react-swipeable-views';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import IconButton from 'mastodon/components/icon_button';
import Icon from 'mastodon/components/icon';
import { defineMessages, injectIntl, FormattedMessage, FormattedDate, FormattedNumber } from 'react-intl';
import { autoPlayGif } from 'mastodon/initial_state';
import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg';
import { mascot } from 'mastodon/initial_state';
import unicodeMapping from 'mastodon/features/emoji/emoji_unicode_mapping_light';
import classNames from 'classnames';
import EmojiPickerDropdown from 'mastodon/features/compose/containers/emoji_picker_dropdown_container';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
previous: { id: 'lightbox.previous', defaultMessage: 'Previous' },
next: { id: 'lightbox.next', defaultMessage: 'Next' },
});
class Content extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
announcement: ImmutablePropTypes.map.isRequired,
};
setRef = c => {
this.node = c;
}
componentDidMount () {
this._updateLinks();
this._updateEmojis();
}
componentDidUpdate () {
this._updateLinks();
this._updateEmojis();
}
_updateEmojis () {
const node = this.node;
if (!node || autoPlayGif) {
return;
}
const emojis = node.querySelectorAll('.custom-emoji');
for (var i = 0; i < emojis.length; i++) {
let emoji = emojis[i];
if (emoji.classList.contains('status-emoji')) {
continue;
}
emoji.classList.add('status-emoji');
emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
}
}
_updateLinks () {
const node = this.node;
if (!node) {
return;
}
const links = node.querySelectorAll('a');
for (var i = 0; i < links.length; ++i) {
let link = links[i];
if (link.classList.contains('status-link')) {
continue;
}
link.classList.add('status-link');
let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url'));
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
link.setAttribute('title', mention.get('acct'));
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
} else {
link.setAttribute('title', link.href);
link.classList.add('unhandled-link');
}
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}
}
onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/accounts/${mention.get('id')}`);
}
}
onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, '');
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/timelines/tag/${hashtag}`);
}
}
handleEmojiMouseEnter = ({ target }) => {
target.src = target.getAttribute('data-original');
}
handleEmojiMouseLeave = ({ target }) => {
target.src = target.getAttribute('data-static');
}
render () {
const { announcement } = this.props;
return (
<div
className='announcements__item__content'
ref={this.setRef}
dangerouslySetInnerHTML={{ __html: announcement.get('contentHtml') }}
/>
);
}
}
const assetHost = process.env.CDN_HOST || '';
class Emoji extends React.PureComponent {
static propTypes = {
emoji: PropTypes.string.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
hovered: PropTypes.bool.isRequired,
};
render () {
const { emoji, emojiMap, hovered } = this.props;
if (unicodeMapping[emoji]) {
const { filename, shortCode } = unicodeMapping[this.props.emoji];
const title = shortCode ? `:${shortCode}:` : '';
return (
<img
draggable='false'
className='emojione'
alt={emoji}
title={title}
src={`${assetHost}/emoji/${filename}.svg`}
/>
);
} else if (emojiMap.get(emoji)) {
const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
const shortCode = `:${emoji}:`;
return (
<img
draggable='false'
className='emojione custom-emoji'
alt={shortCode}
title={shortCode}
src={filename}
/>
);
} else {
return null;
}
}
}
class Reaction extends ImmutablePureComponent {
static propTypes = {
announcementId: PropTypes.string.isRequired,
reaction: ImmutablePropTypes.map.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
};
state = {
hovered: false,
};
handleClick = () => {
const { reaction, announcementId, addReaction, removeReaction } = this.props;
if (reaction.get('me')) {
removeReaction(announcementId, reaction.get('name'));
} else {
addReaction(announcementId, reaction.get('name'));
}
}
handleMouseEnter = () => this.setState({ hovered: true })
handleMouseLeave = () => this.setState({ hovered: false })
render () {
const { reaction } = this.props;
let shortCode = reaction.get('name');
if (unicodeMapping[shortCode]) {
shortCode = unicodeMapping[shortCode].shortCode;
}
return (
<button className={classNames('reactions-bar__item', { active: reaction.get('me') })} onClick={this.handleClick} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} title={`:${shortCode}:`}>
<span className='reactions-bar__item__emoji'><Emoji hovered={this.state.hovered} emoji={reaction.get('name')} emojiMap={this.props.emojiMap} /></span>
<span className='reactions-bar__item__count'><FormattedNumber value={reaction.get('count')} /></span>
</button>
);
}
}
class ReactionsBar extends ImmutablePureComponent {
static propTypes = {
announcementId: PropTypes.string.isRequired,
reactions: ImmutablePropTypes.list.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
};
handleEmojiPick = data => {
const { addReaction, announcementId } = this.props;
addReaction(announcementId, data.native.replace(/:/g, ''));
}
render () {
const { reactions } = this.props;
const visibleReactions = reactions.filter(x => x.get('count') > 0);
return (
<div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
{visibleReactions.map(reaction => (
<Reaction
key={reaction.get('name')}
reaction={reaction}
announcementId={this.props.announcementId}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
emojiMap={this.props.emojiMap}
/>
))}
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={<Icon id='plus' />} />
</div>
);
}
}
class Announcement extends ImmutablePureComponent {
static propTypes = {
announcement: ImmutablePropTypes.map.isRequired,
emojiMap: ImmutablePropTypes.map.isRequired,
dismissAnnouncement: PropTypes.func.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleDismissClick = () => {
const { dismissAnnouncement, announcement } = this.props;
dismissAnnouncement(announcement.get('id'));
}
render () {
const { announcement, intl } = this.props;
const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at'));
const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
const now = new Date();
const hasTimeRange = startsAt && endsAt;
const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
const skipTime = announcement.get('all_day');
return (
<div className='announcements__item'>
<strong className='announcements__item__range'>
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
{hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>}
</strong>
<Content announcement={announcement} />
<ReactionsBar
reactions={announcement.get('reactions')}
announcementId={announcement.get('id')}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
emojiMap={this.props.emojiMap}
/>
<IconButton title={intl.formatMessage(messages.close)} icon='times' className='announcements__item__dismiss-icon' onClick={this.handleDismissClick} />
</div>
);
}
}
export default @injectIntl
class Announcements extends ImmutablePureComponent {
static propTypes = {
announcements: ImmutablePropTypes.list,
emojiMap: ImmutablePropTypes.map.isRequired,
fetchAnnouncements: PropTypes.func.isRequired,
dismissAnnouncement: PropTypes.func.isRequired,
addReaction: PropTypes.func.isRequired,
removeReaction: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
state = {
index: 0,
};
componentDidMount () {
const { fetchAnnouncements } = this.props;
fetchAnnouncements();
}
handleChangeIndex = index => {
this.setState({ index: index % this.props.announcements.size });
}
handleNextClick = () => {
this.setState({ index: (this.state.index + 1) % this.props.announcements.size });
}
handlePrevClick = () => {
this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size });
}
render () {
const { announcements, intl } = this.props;
const { index } = this.state;
if (announcements.isEmpty()) {
return null;
}
return (
<div className='announcements'>
<img className='announcements__mastodon' alt='' draggable='false' src={mascot || elephantUIPlane} />
<div className='announcements__container'>
<ReactSwipeableViews index={index} onChangeIndex={this.handleChangeIndex}>
{announcements.map(announcement => (
<Announcement
key={announcement.get('id')}
announcement={announcement}
emojiMap={this.props.emojiMap}
dismissAnnouncement={this.props.dismissAnnouncement}
addReaction={this.props.addReaction}
removeReaction={this.props.removeReaction}
intl={intl}
/>
))}
</ReactSwipeableViews>
<div className='announcements__pagination'>
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.previous)} icon='chevron-left' onClick={this.handlePrevClick} size={13} />
<span>{index + 1} / {announcements.size}</span>
<IconButton disabled={announcements.size === 1} title={intl.formatMessage(messages.next)} icon='chevron-right' onClick={this.handleNextClick} size={13} />
</div>
</div>
</div>
);
}
}

View File

@ -0,0 +1,21 @@
import { connect } from 'react-redux';
import { fetchAnnouncements, dismissAnnouncement, addReaction, removeReaction } from 'mastodon/actions/announcements';
import Announcements from '../components/announcements';
import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
const mapStateToProps = state => ({
announcements: state.getIn(['announcements', 'items']),
emojiMap: customEmojiMap(state),
});
const mapDispatchToProps = dispatch => ({
fetchAnnouncements: () => dispatch(fetchAnnouncements()),
dismissAnnouncement: id => dispatch(dismissAnnouncement(id)),
addReaction: (id, name) => dispatch(addReaction(id, name)),
removeReaction: (id, name) => dispatch(removeReaction(id, name)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Announcements);

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import { fetchTrends } from '../../../actions/trends';
import { fetchTrends } from 'mastodon/actions/trends';
import Trends from '../components/trends';
const mapStateToProps = state => ({

View File

@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { Link } from 'react-router-dom';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' },
@ -113,6 +114,8 @@ class HomeTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
prepend={<AnnouncementsContainer />}
alwaysPrepend
trackScroll={!pinned}
scrollKey={`home_timeline-${columnId}`}
onLoadMore={this.handleLoadMore}

View File

@ -211,7 +211,6 @@ class MediaModal extends ImmutablePureComponent {
style={swipeableViewsStyle}
containerStyle={containerStyle}
onChangeIndex={this.handleSwipe}
onSwitching={this.handleSwitching}
index={index}
>
{content}

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "المعدل محدود",
"alert.unexpected.message": "لقد طرأ هناك خطأ غير متوقّع.",
"alert.unexpected.title": "المعذرة!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} في الأسبوع",
"boost_modal.combo": "يمكنك/ي ضغط {combo} لتخطّي هذه في المرّة القادمة",
"bundle_column_error.body": "لقد وقع هناك خطأ أثناء عملية تحميل هذا العنصر.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "مدة استطلاع الرأي",
"compose_form.poll.option_placeholder": "الخيار {number}",
"compose_form.poll.remove_option": "إزالة هذا الخيار",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "عدّل استطلاع الرأي وغيّره لإتاحة الخيارات المتعددة",
"compose_form.poll.switch_to_single": "عدّل استطلاع الرأي وغيّره لإتاحة خيار واحد فقط",
"compose_form.publish": "بوّق",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "تحديد الوسائط كحساسة",
@ -142,7 +143,7 @@
"empty_column.account_timeline": "ليس هناك تبويقات!",
"empty_column.account_unavailable": "الملف التعريفي غير متوفر",
"empty_column.blocks": "لم تقم بحظر أي مستخدِم بعد.",
"empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
"empty_column.bookmarked_statuses": "ليس لديك أية تبويقات في الفواصل المرجعية بعد. عندما ستقوم بإضافة البعض منها، ستظهر هنا.",
"empty_column.community": "الخط العام المحلي فارغ. أكتب شيئا ما للعامة كبداية!",
"empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
"empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.",

View File

@ -43,9 +43,10 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "Asocedió un fallu inesperáu.",
"alert.unexpected.title": "¡Meca!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per selmana",
"boost_modal.combo": "Pues primir {combo} pa saltar esto la próxima vegada",
"bundle_column_error.body": "Something went wrong while loading this component.",
"bundle_column_error.body": "Asocedió daqué malo mentanto se cargaba esti componente.",
"bundle_column_error.retry": "Try again",
"bundle_column_error.title": "Network error",
"bundle_modal_error.close": "Close",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "হার সীমিত",
"alert.unexpected.message": "সমস্যা অপ্রত্যাশিত.",
"alert.unexpected.title": "ওহো!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "প্রতি সপ্তাহে {count}",
"boost_modal.combo": "পরেরবার আপনি {combo} টিপলে এটি আর আসবে না",
"bundle_column_error.body": "এই অংশটি দেখতে যেয়ে কোনো সমস্যা হয়েছে।.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "Ur fazi dic'hortozet zo degouezhet.",
"alert.unexpected.title": "C'hem !",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} bep sizhun",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Límit de freqüència",
"alert.unexpected.message": "S'ha produït un error inesperat.",
"alert.unexpected.title": "Vaja!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per setmana",
"boost_modal.combo": "Pots premer {combo} per saltar-te això el proper cop",
"bundle_column_error.body": "S'ha produït un error en carregar aquest component.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Durada de l'enquesta",
"compose_form.poll.option_placeholder": "Opció {number}",
"compose_form.poll.remove_option": "Elimina aquesta opció",
"compose_form.poll.switch_to_multiple": "Canvía lenquesta per a permetre diverses opcions",
"compose_form.poll.switch_to_single": "Canvía lenquesta per a permetre una sola opció",
"compose_form.poll.switch_to_multiple": "Canvia lenquesta per a permetre diverses opcions",
"compose_form.poll.switch_to_single": "Canvia lenquesta per a permetre una única opció",
"compose_form.publish": "Tut",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Marcar mèdia com a sensible",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Ghjettu limitatu",
"alert.unexpected.message": "Un prublemu inaspettatu hè accadutu.",
"alert.unexpected.title": "Uups!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per settimana",
"boost_modal.combo": "Pudete appughjà nant'à {combo} per saltà quessa a prussima volta",
"bundle_column_error.body": "C'hè statu un prublemu caricandu st'elementu.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rychlost omezena",
"alert.unexpected.message": "Objevila se neočekávaná chyba.",
"alert.unexpected.title": "Jejda!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} za týden",
"boost_modal.combo": "Příště můžete pro přeskočení stisknout {combo}",
"bundle_column_error.body": "Při načítání této komponenty se něco pokazilo.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Doba trvání ankety",
"compose_form.poll.option_placeholder": "Volba {number}",
"compose_form.poll.remove_option": "Odstranit tuto volbu",
"compose_form.poll.switch_to_multiple": "Povolit u ankety více možností",
"compose_form.poll.switch_to_single": "Povolit u ankety jedinou možnost",
"compose_form.poll.switch_to_multiple": "Povolit u ankety výběr více možností",
"compose_form.poll.switch_to_single": "Povolit u ankety výběr jediné možnosti",
"compose_form.publish": "Tootnout",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Označit média jako citlivá",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "Ychwanegu neu Dileu o'r rhestrau",
"account.badges.bot": "Bot",
"account.badges.group": "Group",
"account.badges.group": "Grŵp",
"account.block": "Blocio @{name}",
"account.block_domain": "Cuddio popeth rhag {domain}",
"account.blocked": "Blociwyd",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Cyfradd gyfyngedig",
"alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
"alert.unexpected.title": "Wps!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} yr wythnos",
"boost_modal.combo": "Mae modd gwasgu {combo} er mwyn sgipio hyn tro nesa",
"bundle_column_error.body": "Aeth rhywbeth o'i le tra'n llwytho'r elfen hon.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Cyfnod pleidlais",
"compose_form.poll.option_placeholder": "Dewisiad {number}",
"compose_form.poll.remove_option": "Tynnu'r dewisiad",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "Newid pleidlais i adael mwy nag un dewis",
"compose_form.poll.switch_to_single": "Newid pleidlais i gyfyngu i un dewis",
"compose_form.publish": "Tŵt",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Marcio cyfryngau fel eu bod yn sensitif",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Gradsbegrænset",
"alert.unexpected.message": "Der opstod en uventet fejl.",
"alert.unexpected.title": "Ups!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per uge",
"boost_modal.combo": "Du kan trykke {combo} for at springe dette over næste gang",
"bundle_column_error.body": "Noget gik galt under indlæsningen af dette komponent.",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "Hinzufügen oder Entfernen von Listen",
"account.badges.bot": "Bot",
"account.badges.group": "Group",
"account.badges.group": "Gruppe",
"account.block": "@{name} blockieren",
"account.block_domain": "Alles von {domain} verstecken",
"account.blocked": "Blockiert",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Anfragelimit überschritten",
"alert.unexpected.message": "Ein unerwarteter Fehler ist aufgetreten.",
"alert.unexpected.title": "Hoppla!",
"announcement.announcement": "Ankündigung",
"autosuggest_hashtag.per_week": "{count} pro Woche",
"boost_modal.combo": "Drücke {combo}, um dieses Fenster zu überspringen",
"bundle_column_error.body": "Etwas ist beim Laden schiefgelaufen.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Umfragedauer",
"compose_form.poll.option_placeholder": "Wahl {number}",
"compose_form.poll.remove_option": "Wahl entfernen",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "Umfrage ändern, um mehrere Optionen zu erlauben",
"compose_form.poll.switch_to_single": "Umfrage ändern, um eine einzige Wahl zu ermöglichen",
"compose_form.publish": "Tröt",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Medien als heikel markieren",

View File

@ -1407,6 +1407,10 @@
"defaultMessage": "Unmute @{name}",
"id": "account.unmute"
},
{
"defaultMessage": "Unfollow",
"id": "confirmations.unfollow.confirm"
},
{
"defaultMessage": "Are you sure you want to unfollow {name}?",
"id": "confirmations.unfollow.message"
@ -1563,6 +1567,10 @@
{
"defaultMessage": "Next",
"id": "lightbox.next"
},
{
"defaultMessage": "Announcement",
"id": "announcement.announcement"
}
],
"path": "app/javascript/mastodon/features/getting_started/components/announcements.json"

View File

@ -1,9 +1,9 @@
{
"account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
"account.badges.bot": "Μποτ",
"account.badges.group": "Group",
"account.badges.group": "Ομάδα",
"account.block": "Αποκλεισμός @{name}",
"account.block_domain": "Απόκρυψε τα πάντα από το {domain}",
"account.block_domain": "Απόκρυψη όλων από {domain}",
"account.blocked": "Αποκλεισμένος/η",
"account.cancel_follow_request": "Ακύρωση αιτήματος παρακολούθησης",
"account.direct": "Προσωπικό μήνυμα προς @{name}",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Περιορισμός συχνότητας",
"alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
"alert.unexpected.title": "Εεπ!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} ανα εβδομάδα",
"boost_modal.combo": "Μπορείς να πατήσεις {combo} για να το προσπεράσεις αυτό την επόμενη φορά",
"bundle_column_error.body": "Κάτι πήγε στραβά ενώ φορτωνόταν αυτό το στοιχείο.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
"compose_form.poll.option_placeholder": "Επιλογή {number}",
"compose_form.poll.remove_option": "Αφαίρεση επιλογής",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "Ενημέρωση δημοσκόπησης με πολλαπλές επιλογές",
"compose_form.poll.switch_to_single": "Ενημέρωση δημοσκόπησης με μοναδική επιλογή",
"compose_form.publish": "Τουτ",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Σημείωσε τα πολυμέσα ως ευαίσθητα",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Mesaĝkvante limigita",
"alert.unexpected.message": "Neatendita eraro okazis.",
"alert.unexpected.title": "Ups!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} semajne",
"boost_modal.combo": "Vi povas premi {combo} por preterpasi sekvafoje",
"bundle_column_error.body": "Io misfunkciis en la ŝargado de ĉi tiu elemento.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Tarifa limitada",
"alert.unexpected.message": "Ocurrió un error.",
"alert.unexpected.title": "¡Epa!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} por semana",
"boost_modal.combo": "Podés hacer clic en {combo} para saltar esto la próxima vez",
"bundle_column_error.body": "Algo salió mal al cargar este componente.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Tasa limitada",
"alert.unexpected.message": "Hubo un error inesperado.",
"alert.unexpected.title": "¡Ups!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} por semana",
"boost_modal.combo": "Puedes hacer clic en {combo} para saltar este aviso la próxima vez",
"bundle_column_error.body": "Algo salió mal al cargar este componente.",
@ -57,7 +58,7 @@
"column.direct": "Mensajes directos",
"column.directory": "Buscar perfiles",
"column.domain_blocks": "Dominios ocultados",
"column.favourites": "Levantar la trompa",
"column.favourites": "Favoritos",
"column.follow_requests": "Solicitudes de seguimiento",
"column.home": "Inicio",
"column.lists": "Listas",
@ -86,7 +87,7 @@
"compose_form.poll.remove_option": "Eliminar esta opción",
"compose_form.poll.switch_to_multiple": "Modificar encuesta para permitir múltiples opciones",
"compose_form.poll.switch_to_single": "Modificar encuesta para permitir una única opción",
"compose_form.publish": "Ipoxta",
"compose_form.publish": "Ipotxa",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Marcar multimedia como sensible",
"compose_form.sensitive.marked": "Material marcado como sensible",
@ -131,7 +132,7 @@
"emoji_button.food": "Comida y bebida",
"emoji_button.label": "Insertar emoji",
"emoji_button.nature": "Naturaleza",
"emoji_button.not_found": "No hay emojos!! (╯°□°)╯︵ ┻━┻",
"emoji_button.not_found": "No hay emojis!! ¯\\_(ツ)_/¯",
"emoji_button.objects": "Objetos",
"emoji_button.people": "Gente",
"emoji_button.recent": "Usados frecuentemente",
@ -205,7 +206,7 @@
"introduction.welcome.text": "¡Bienvenido al fediverso! En unos momentos, podrás transmitir mensajes y hablar con tus amigos a través de una amplia variedad de servidores. Pero este servidor, {domain}, es especial, alberga tu perfil, así que recuerda su nombre.",
"keyboard_shortcuts.back": "volver atrás",
"keyboard_shortcuts.blocked": "abrir una lista de usuarios bloqueados",
"keyboard_shortcuts.boost": "reipoxta",
"keyboard_shortcuts.boost": "retootear",
"keyboard_shortcuts.column": "enfocar un estado en una de las columnas",
"keyboard_shortcuts.compose": "enfocar el área de texto de redacción",
"keyboard_shortcuts.description": "Descripción",
@ -356,7 +357,7 @@
"status.block": "Bloquear a @{name}",
"status.bookmark": "Marcador",
"status.cancel_reblog_private": "Des-impulsar",
"status.cannot_reblog": "Este bramido no puede rebarritarse",
"status.cannot_reblog": "Este toot no puede retootearse",
"status.copy": "Copiar enlace al estado",
"status.delete": "Borrar",
"status.detailed_status": "Vista de conversación detallada",
@ -374,7 +375,7 @@
"status.pin": "Fijar",
"status.pinned": "Toot fijado",
"status.read_more": "Leer más",
"status.reblog": "ReIpoxta",
"status.reblog": "Retootear",
"status.reblog_private": "Implusar a la audiencia original",
"status.reblogged_by": "Retooteado por {name}",
"status.reblogs.empty": "Nadie impulsó este toot todavía. Cuando alguien lo haga, aparecerá aqui.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Piiratud",
"alert.unexpected.message": "Tekkis ootamatu viga.",
"alert.unexpected.title": "Oih!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} nädalas",
"boost_modal.combo": "Võite vajutada {combo}, et see järgmine kord vahele jätta",
"bundle_column_error.body": "Midagi läks valesti selle komponendi laadimisel.",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "Gehitu edo kendu zerrendetatik",
"account.badges.bot": "Bot-a",
"account.badges.group": "Group",
"account.badges.group": "Taldea",
"account.block": "Blokeatu @{name}",
"account.block_domain": "Ezkutatu {domain} domeinuko guztia",
"account.blocked": "Blokeatuta",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Abiadura mugatua",
"alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
"alert.unexpected.title": "Ene!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} asteko",
"boost_modal.combo": "{combo} sakatu dezakezu hurrengoan hau saltatzeko",
"bundle_column_error.body": "Zerbait okerra gertatu da osagai hau kargatzean.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Inkestaren iraupena",
"compose_form.poll.option_placeholder": "{number}. aukera",
"compose_form.poll.remove_option": "Kendu aukera hau",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "Aldatu inkesta hainbat aukera onartzeko",
"compose_form.poll.switch_to_single": "Aldatu inkesta aukera bakarra onartzeko",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Markatu multimedia hunkigarri gisa",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "افزودن یا برداشتن از فهرست",
"account.badges.bot": "ربات",
"account.badges.group": "Group",
"account.badges.group": "گروه",
"account.block": "مسدودسازی @{name}",
"account.block_domain": "پنهان‌سازی همه چیز از سرور {domain}",
"account.blocked": "مسدود",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "محدود شده",
"alert.unexpected.message": "خطایی غیرمنتظره رخ داد.",
"alert.unexpected.title": "وای!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} در هفته",
"boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
"bundle_column_error.body": "هنگام بازکردن این بخش خطایی رخ داد.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "مدت نظرسنجی",
"compose_form.poll.option_placeholder": "گزینهٔ {number}",
"compose_form.poll.remove_option": "برداشتن این گزینه",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "تبدیل به نظرسنجی چندگزینه‌ای",
"compose_form.poll.switch_to_single": "تبدیل به نظرسنجی تک‌گزینه‌ای",
"compose_form.publish": "بوق",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "علامت‌گذاری رسانه به عنوان حساس",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Määrää rajoitettu",
"alert.unexpected.message": "Tapahtui odottamaton virhe.",
"alert.unexpected.title": "Hups!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} viikossa",
"boost_modal.combo": "Ensi kerralla voit ohittaa tämän painamalla {combo}",
"bundle_column_error.body": "Jokin meni vikaan komponenttia ladattaessa.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Débit limité",
"alert.unexpected.message": "Une erreur inattendue sest produite.",
"alert.unexpected.title": "Oups!",
"announcement.announcement": "Annonce",
"autosuggest_hashtag.per_week": "{count} par semaine",
"boost_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci, la prochaine fois",
"bundle_column_error.body": "Une erreur sest produite lors du chargement de ce composant.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -4,32 +4,32 @@
"account.badges.group": "Grupo",
"account.block": "Bloquear @{name}",
"account.block_domain": "Agochar todo de {domain}",
"account.blocked": "Bloqueado",
"account.blocked": "Bloqueada",
"account.cancel_follow_request": "Desbotar solicitude de seguimento",
"account.direct": "Mensaxe directa @{name}",
"account.domain_blocked": "Dominio agochado",
"account.edit_profile": "Editar perfil",
"account.endorse": "Amosar no perfil",
"account.follow": "Seguir",
"account.followers": "Seguidores",
"account.followers.empty": "Aínda ninguén segue este usuario.",
"account.followers": "Seguidoras",
"account.followers.empty": "Aínda ninguén segue esta usuaria.",
"account.follows": "Seguindo",
"account.follows.empty": "Este usuario aínda non segue a ninguén.",
"account.follows.empty": "Esta usuaria aínda non segue a ninguén.",
"account.follows_you": "Séguete",
"account.hide_reblogs": "Agochar compartidos de @{name}",
"account.hide_reblogs": "Agochar repeticións de @{name}",
"account.last_status": "Última actividade",
"account.link_verified_on": "A propiedade desta ligazón foi verificada o {date}",
"account.locked_info": "Esta é unha conta privada. O dono revisa de xeito manual quen pode seguilo.",
"account.locked_info": "Esta é unha conta privada. A propietaria revisa de xeito manual quen pode seguila.",
"account.media": "Multimedia",
"account.mention": "Mencionar @{name}",
"account.moved_to": "{name} mudouse a:",
"account.mute": "Silenciar @{name}",
"account.mute_notifications": "Silenciar notificacións de @{name}",
"account.muted": "Silenciado",
"account.mute": "Acalar @{name}",
"account.mute_notifications": "Acalar as notificacións de @{name}",
"account.muted": "Acalada",
"account.never_active": "Nunca",
"account.posts": "Toots",
"account.posts_with_replies": "Toots e respostas",
"account.report": "Denunciar @{name}",
"account.report": "Informar sobre @{name}",
"account.requested": "Agardando aprovación. Preme para desbotar a solicitude de seguimento",
"account.share": "Compartir o perfil de @{name}",
"account.show_reblogs": "Amosar compartidos de @{name}",
@ -43,15 +43,16 @@
"alert.rate_limited.title": "Límite de intentos",
"alert.unexpected.message": "Ocorreu un erro non agardado.",
"alert.unexpected.title": "Vaites!",
"announcement.announcement": "Anuncio",
"autosuggest_hashtag.per_week": "{count} por semana",
"boost_modal.combo": "Preme {combo} para ignorar isto na seguinte vez",
"bundle_column_error.body": "Ocorreu un erro ó cargar este compoñente.",
"bundle_column_error.retry": "Téntao de novo",
"bundle_column_error.title": "Erro na rede",
"bundle_column_error.title": "Fallo na rede",
"bundle_modal_error.close": "Pechar",
"bundle_modal_error.message": "Ocorreu un erro ó cargar este compoñente.",
"bundle_modal_error.retry": "Téntao de novo",
"column.blocks": "Usuarios bloqueados",
"column.blocks": "Usuarias bloqueadas",
"column.bookmarks": "Marcadores",
"column.community": "Cronoloxía local",
"column.direct": "Mensaxes directas",
@ -59,9 +60,9 @@
"column.domain_blocks": "Dominios agochados",
"column.favourites": "Favoritos",
"column.follow_requests": "Peticións de seguimento",
"column.home": "Páxina inicial",
"column.home": "Inicio",
"column.lists": "Listaxes",
"column.mutes": "Usuarios silenciados",
"column.mutes": "Usuarias acaladas",
"column.notifications": "Notificacións",
"column.pins": "Toots fixados",
"column.public": "Cronoloxía federada",
@ -74,7 +75,7 @@
"column_header.unpin": "Desafixar",
"column_subheading.settings": "Axustes",
"community.column_settings.media_only": "Só multimedia",
"compose_form.direct_message_warning": "Este toot só será enviado ós usuarios mencionados.",
"compose_form.direct_message_warning": "Este toot só será enviado ás usuarias mencionadas.",
"compose_form.direct_message_warning_learn_more": "Coñecer máis",
"compose_form.hashtag_warning": "Este toot non se amosará baixo cancelos (hashtags) porque non é público. Só os toots públicos poden ser procurados por cancelos.",
"compose_form.lock_disclaimer": "A túa conta non está {locked}. Todos poden seguirche para ollar os teus toots só para seguidores.",
@ -86,7 +87,7 @@
"compose_form.poll.remove_option": "Eliminar esta opción",
"compose_form.poll.switch_to_multiple": "Mudar a enquisa para permitir múltiples escollas",
"compose_form.poll.switch_to_single": "Mudar a enquisa para permitir unha soa escolla",
"compose_form.publish": "Toot",
"compose_form.publish": "Tootear",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Marcar coma contido multimedia sensíbel",
"compose_form.sensitive.marked": "Contido multimedia marcado coma sensíbel",
@ -111,7 +112,7 @@
"confirmations.mute.message": "Tes a certeza de querer silenciar a {name}?",
"confirmations.redraft.confirm": "Eliminar e reescribir",
"confirmations.redraft.message": "Tes a certeza de querer eliminar este estado e reescribilo? Perderás os compartidos e favoritos, e as respostas á publicación orixinal ficarán orfas.",
"confirmations.reply.confirm": "Respostar",
"confirmations.reply.confirm": "Responder",
"confirmations.reply.message": "Respostar agora sobrescribirá a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
"confirmations.unfollow.confirm": "Deixar de seguir",
"confirmations.unfollow.message": "Desexas deixar de seguir a {name}?",
@ -141,7 +142,7 @@
"emoji_button.travel": "Viaxes e lugares",
"empty_column.account_timeline": "Non hai toots aquí!",
"empty_column.account_unavailable": "Perfil non dispoñíbel",
"empty_column.blocks": "Aínda non bloqueaches a ningún usuario.",
"empty_column.blocks": "Aínda non bloqueaches a ningún usuaria.",
"empty_column.bookmarked_statuses": "Aínda non marcaches ningún toot. Cando o fagas, amosaranse aquí.",
"empty_column.community": "A cronoloxía local está baleira. Escribe algo de xeito público para espallalo!",
"empty_column.direct": "Aínda non tes mensaxes directas. Cando envíes ou recibas unha, amosarase aquí.",
@ -150,13 +151,13 @@
"empty_column.favourites": "A ninguén lle gostou este toot polo momento. Cando a alguén lle goste, aparecerá aquí.",
"empty_column.follow_requests": "Non tes peticións de seguimento. Cando recibas unha, amosarase aquí.",
"empty_column.hashtag": "Aínda non hai nada con este cancelo.",
"empty_column.home": "A túa cronoloxía inicial está baleira! Visita {public} ou emprega a procura para atopar outros usuarios.",
"empty_column.home": "A túa cronoloxía inicial está baleira! Visita {public} ou emprega a procura para atopar outras usuarias.",
"empty_column.home.public_timeline": "a cronoloxía pública",
"empty_column.list": "Aínda non hai nada nesta listaxe. Cando os usuarios incluídas na listaxe publiquen mensaxes, amosaranse aquí.",
"empty_column.list": "Aínda non hai nada en esta lista. Cando as usuarias incluídas na lista publiquen mensaxes, aparecerán aquí.",
"empty_column.lists": "Aínda non tes listaxes. Cando crees unha, amosarase aquí.",
"empty_column.mutes": "Aínda non silenciaches a ningún usuario.",
"empty_column.mutes": "Aínda non silenciaches a ningúnha usuaria.",
"empty_column.notifications": "Aínda non tes notificacións. Interactúa con outros para comezar unha conversa.",
"empty_column.public": "Nada por aquí! Escribe algo de xeito público, ou sigue de xeito manual usuarios doutros servidores para ir enchéndoo",
"empty_column.public": "Nada por aquí! Escribe algo de xeito público, ou segue de xeito manual usuarias doutros servidores para ir enchéndoo",
"error.unexpected_crash.explanation": "Debido a un erro no noso código ou a unha compatilidade co teu navegador, esta páxina non pode ser amosada correctamente.",
"error.unexpected_crash.next_steps": "Tenta actualizar a páxina. Se esto non axuda podes tamén empregar o Mastodon noutro navegador ou aplicación nativa.",
"errors.unexpected_crash.copy_stacktrace": "Copiar trazas (stacktrace) ó portapapeis",
@ -196,16 +197,16 @@
"introduction.interactions.action": "Rematar titorial!",
"introduction.interactions.favourite.headline": "Favorito",
"introduction.interactions.favourite.text": "Podes gardar un toot para depois e facer saber ó autor que che gostou marcandoo coma favorito.",
"introduction.interactions.reblog.headline": "Compartir na cronoloxía",
"introduction.interactions.reblog.text": "Podes compartir os toots doutras persoas cos teus seguidores.",
"introduction.interactions.reblog.headline": "Promover",
"introduction.interactions.reblog.text": "Podes compartir os toots doutras persoas coas túas seguidoras.",
"introduction.interactions.reply.headline": "Respostar",
"introduction.interactions.reply.text": "Podes respostar ós toots doutras persoas e ós teus propios, así ficarán encadeados nunha conversa.",
"introduction.welcome.action": "Imos!",
"introduction.welcome.headline": "Primeiros pasos",
"introduction.welcome.text": "Benvido ó fediverso! Nun intre poderás difundir mensaxes e falar coas túas amizades nun grande número de servidores. Mais este servidor, {domain}, é especial—hospeda o teu perfil, por iso lémbrate do seu nome.",
"keyboard_shortcuts.back": "para voltar atrás",
"keyboard_shortcuts.blocked": "para abrir a listaxe de usuarios bloqueados",
"keyboard_shortcuts.boost": "para compartir na cronoloxía",
"keyboard_shortcuts.blocked": "abrir lista de usuarias bloqueadas",
"keyboard_shortcuts.boost": "promover",
"keyboard_shortcuts.column": "para destacar un estado nunha das columnas",
"keyboard_shortcuts.compose": "para destacar a área de escritura",
"keyboard_shortcuts.description": "Descrición",
@ -221,7 +222,7 @@
"keyboard_shortcuts.legend": "para amosar esta lenda",
"keyboard_shortcuts.local": "para abrir a cronoloxía local",
"keyboard_shortcuts.mention": "para mencionar ó autor",
"keyboard_shortcuts.muted": "para abrir a listaxe dos usuarios silenciados",
"keyboard_shortcuts.muted": "abrir lista de usuarias acaladas",
"keyboard_shortcuts.my_profile": "para abrir o teu perfil",
"keyboard_shortcuts.notifications": "para abrir a columna das notificacións",
"keyboard_shortcuts.open_media": "para abrir o contido multimedia",
@ -254,9 +255,9 @@
"media_gallery.toggle_visible": "Trocar visibilidade",
"missing_indicator.label": "Non atopado",
"missing_indicator.sublabel": "Este recurso non foi atopado",
"mute_modal.hide_notifications": "Agochar notificacións deste usuario?",
"mute_modal.hide_notifications": "Agochar notificacións desta usuaria?",
"navigation_bar.apps": "Aplicacións móbiles",
"navigation_bar.blocks": "Usuarios bloqueados",
"navigation_bar.blocks": "Usuarias bloqueadas",
"navigation_bar.bookmarks": "Marcadores",
"navigation_bar.community_timeline": "Cronoloxía local",
"navigation_bar.compose": "Escribir un novo toot",
@ -272,7 +273,7 @@
"navigation_bar.keyboard_shortcuts": "Atallos do teclado",
"navigation_bar.lists": "Listaxes",
"navigation_bar.logout": "Pechar sesión",
"navigation_bar.mutes": "Usuarios silenciados",
"navigation_bar.mutes": "Usuarias silenciadas",
"navigation_bar.personal": "Persoal",
"navigation_bar.pins": "Toots fixados",
"navigation_bar.preferences": "Preferencias",
@ -284,7 +285,7 @@
"notification.mention": "{name} mencionoute",
"notification.own_poll": "A túa enquisa rematou",
"notification.poll": "Unha enquisa na que votaches rematou",
"notification.reblog": "{name} compartiu o teu estado",
"notification.reblog": "{name} promoveu o teu estado",
"notifications.clear": "Limpar notificacións",
"notifications.clear_confirmation": "Tes a certeza de querer limpar de xeito permanente todas as túas notificacións?",
"notifications.column_settings.alert": "Notificacións de escritorio",
@ -297,7 +298,7 @@
"notifications.column_settings.mention": "Mencións:",
"notifications.column_settings.poll": "Resultados da enquisa:",
"notifications.column_settings.push": "Notificacións emerxentes",
"notifications.column_settings.reblog": "Compartidos:",
"notifications.column_settings.reblog": "Promocións:",
"notifications.column_settings.show": "Amosar en columna",
"notifications.column_settings.sound": "Reproducir son",
"notifications.filter.all": "Todo",
@ -316,7 +317,7 @@
"poll_button.add_poll": "Engadir unha enquisa",
"poll_button.remove_poll": "Eliminar enquisa",
"privacy.change": "Axustar privacidade",
"privacy.direct.long": "Só para os usuarios mencionados",
"privacy.direct.long": "Só para as usuarias mencionadas",
"privacy.direct.short": "Directo",
"privacy.private.long": "Só para os seguidores",
"privacy.private.short": "Só seguidores",
@ -341,11 +342,11 @@
"report.target": "Denunciar a {target}",
"search.placeholder": "Procurar",
"search_popout.search_format": "Formato de procura avanzada",
"search_popout.tips.full_text": "Texto sinxelo que devolve estados que ti escribiches, compartiches, marcaches favorito, ou foches mencionado, así como nomes de usuario coincidentes, nomes públicos e cancelos.",
"search_popout.tips.full_text": "Texto simple devolve estados que ti escribiches, promoviches, marcaches favoritos, ou foches mencionada, así como nomes de usuaria coincidentes, nomes públicos e etiquetas.",
"search_popout.tips.hashtag": "cancelo",
"search_popout.tips.status": "estado",
"search_popout.tips.text": "Texto sinxelo que devolve coincidencias con nomes públicos, nomes de usuario e cancelos",
"search_popout.tips.user": "usuario",
"search_popout.tips.text": "Texto simple devolve coincidencias con nomes públicos, nomes de usuaria e etiquetas",
"search_popout.tips.user": "usuaria",
"search_results.accounts": "Persoas",
"search_results.hashtags": "Cancelos",
"search_results.statuses": "Toots",
@ -356,7 +357,7 @@
"status.block": "Bloquear @{name}",
"status.bookmark": "Marcar",
"status.cancel_reblog_private": "Desfacer compartido",
"status.cannot_reblog": "Esta publicación non pode ser compartida",
"status.cannot_reblog": "Esta publicación non pode ser promovida",
"status.copy": "Copiar ligazón ó estado",
"status.delete": "Eliminar",
"status.detailed_status": "Vista detallada da conversa",
@ -374,10 +375,10 @@
"status.pin": "Fixar no perfil",
"status.pinned": "Toot fixado",
"status.read_more": "Ler máis",
"status.reblog": "Compartir",
"status.reblog": "Promover",
"status.reblog_private": "Compartir á audiencia orixinal",
"status.reblogged_by": "{name} compartiu",
"status.reblogs.empty": "Aínda ninguén compartiu este toot. Cando alguén o faga, amosarase aquí.",
"status.reblogged_by": "{name} promoveu",
"status.reblogs.empty": "Aínda ninguén promoveu este toot. Cando alguén o faga, amosarase aquí.",
"status.redraft": "Eliminar e reescribir",
"status.remove_bookmark": "Eliminar marcador",
"status.reply": "Respostar",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
"alert.unexpected.title": "אופס!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
"bundle_column_error.body": "משהו השתבש בעת הצגת הרכיב הזה.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "सीमित दर",
"alert.unexpected.message": "एक अप्रत्याशित त्रुटि हुई है!",
"alert.unexpected.title": "उफ़!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} हर सप्ताह",
"boost_modal.combo": "अगली बार स्किप करने के लिए आप {combo} दबा सकते है",
"bundle_column_error.body": "इस कॉम्पोनेन्ट को लोड करते वक्त कुछ गलत हो गया",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "Hozzáadás vagy eltávolítás a listáról",
"account.badges.bot": "Bot",
"account.badges.group": "Group",
"account.badges.group": "Csoport",
"account.block": "@{name} letiltása",
"account.block_domain": "Minden elrejtése innen: {domain}",
"account.blocked": "Letiltva",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Forgalomkorlátozás",
"alert.unexpected.message": "Váratlan hiba történt.",
"alert.unexpected.title": "Hoppá!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count}/hét",
"boost_modal.combo": "Hogy átugord ezt következő alkalommal, használd {combo}",
"bundle_column_error.body": "Hiba történt a komponens betöltése közben.",
@ -84,8 +85,8 @@
"compose_form.poll.duration": "Szavazás időtartama",
"compose_form.poll.option_placeholder": "{number}. lehetőség",
"compose_form.poll.remove_option": "Lehetőség törlése",
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
"compose_form.poll.switch_to_multiple": "Szavazás megváltoztatása több választásosra",
"compose_form.poll.switch_to_single": "Szavazás megváltoztatása egyetlen választásosra",
"compose_form.publish": "Tülk",
"compose_form.publish_loud": "{publish}!",
"compose_form.sensitive.hide": "Média megjelölése szenzitívként",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "Աւելացնել կամ հեռացնել ցանկերից",
"account.badges.bot": "Բոտ",
"account.badges.group": "Group",
"account.badges.group": "Խումբ",
"account.block": "Արգելափակել @{name}֊ին",
"account.block_domain": "Թաքցնել ամենը հետեւյալ տիրույթից՝ {domain}",
"account.blocked": "Արգելափակուած է",
@ -27,7 +27,7 @@
"account.mute_notifications": "Անջատել ծանուցումները @{name}֊ից",
"account.muted": "Լռեցուած",
"account.never_active": "Երբեք",
"account.posts": "Գրառումներ",
"account.posts": "Թութ",
"account.posts_with_replies": "Toots with replies",
"account.report": "Բողոքել @{name}֊ից",
"account.requested": "Հաստատման կարիք ունի։ Սեղմիր՝ հետեւելու հայցը չեղարկելու համար։",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "Անսպասելի սխալ տեղի ունեցաւ։",
"alert.unexpected.title": "Վա՜յ",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "շաբաթը՝ {count}",
"boost_modal.combo": "Կարող ես սեղմել {combo}՝ սա հաջորդ անգամ բաց թողնելու համար",
"bundle_column_error.body": "Այս բաղադրիչը բեռնելու ընթացքում ինչ֊որ բան խափանվեց։",
@ -401,25 +402,25 @@
"tabs_bar.notifications": "Ծանուցումներ",
"tabs_bar.search": "Փնտրել",
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
"time_remaining.hours": "{number, plural, one {# ժամ} other {# ժամ}} անց",
"time_remaining.minutes": "{number, plural, one {# րոպե} other {# րոպե}} անց",
"time_remaining.moments": "Moments remaining",
"time_remaining.seconds": "{number, plural, one {# second} other {# seconds}} left",
"time_remaining.seconds": "{number, plural, one {# վայրկյան} other {# վայրկյան}} անց",
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
"trends.trending_now": "Trending now",
"trends.trending_now": "Այժմ արդիական",
"ui.beforeunload": "Քո սեւագիրը կկորի, եթե լքես Մաստոդոնը։",
"upload_area.title": "Քաշիր ու նետիր՝ վերբեռնելու համար",
"upload_button.label": "Ավելացնել մեդիա",
"upload_error.limit": "File upload limit exceeded.",
"upload_error.limit": "Ֆայլի վերբեռնման սահմանաչափը գերազանցված է։",
"upload_error.poll": "File upload not allowed with polls.",
"upload_form.audio_description": "Describe for people with hearing loss",
"upload_form.description": "Նկարագրություն ավելացրու տեսողական խնդիրներ ունեցողների համար",
"upload_form.edit": "Խմբագրել",
"upload_form.undo": "Հետարկել",
"upload_form.video_description": "Describe for people with hearing loss or visual impairment",
"upload_modal.analyzing_picture": "Analyzing picture…",
"upload_modal.analyzing_picture": "Լուսանկարի վերլուծում…",
"upload_modal.apply": "Կիրառել",
"upload_modal.description_placeholder": "A quick brown fox jumps over the lazy dog",
"upload_modal.description_placeholder": "Ճկուն շագանակագույն աղվեսը ցատկում է ծույլ շան վրայով",
"upload_modal.detect_text": "Հայտնբերել տեքստը նկարից",
"upload_modal.edit_media": "Խմբագրել մեդիան",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Batasan tingkat",
"alert.unexpected.message": "Terjadi kesalahan yang tidak terduga.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per minggu",
"boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
"bundle_column_error.body": "Kesalahan terjadi saat memuat komponen ini.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -10,10 +10,10 @@
"account.domain_blocked": "Lén falið",
"account.edit_profile": "Breyta notandasniði",
"account.endorse": "Birta á notandasniði",
"account.follow": "Fylgja",
"account.follow": "Fylgjast með",
"account.followers": "Fylgjendur",
"account.followers.empty": "Ennþá fylgist enginn með þessum notanda.",
"account.follows": "Fylgir",
"account.follows": "Fylgist með",
"account.follows.empty": "Þessi notandi fylgist ennþá ekki með neinum.",
"account.follows_you": "Fylgir þér",
"account.hide_reblogs": "Fela endurbirtingar fyrir @{name}",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Með takmörkum",
"alert.unexpected.message": "Upp kom óvænt villa.",
"alert.unexpected.title": "Úbbs!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} á viku",
"boost_modal.combo": "Þú getur ýtt á {combo} til að sleppa þessu næst",
"bundle_column_error.body": "Eitthvað fór úrskeiðis við að hlaða inn þessari einingu.",
@ -266,7 +267,7 @@
"navigation_bar.edit_profile": "Breyta notandasniði",
"navigation_bar.favourites": "Eftirlæti",
"navigation_bar.filters": "Þögguð orð",
"navigation_bar.follow_requests": "Fylgja beiðnum",
"navigation_bar.follow_requests": "Beiðnir um að fylgjast með",
"navigation_bar.follows_and_followers": "Fylgist með og fylgjendur",
"navigation_bar.info": "Um þennan vefþjón",
"navigation_bar.keyboard_shortcuts": "Flýtilyklar",
@ -279,7 +280,7 @@
"navigation_bar.public_timeline": "Sameiginleg tímalína",
"navigation_bar.security": "Öryggi",
"notification.favourite": "{name} setti stöðufærslu þína í eftirlæti",
"notification.follow": "{name} fylgdist með þér",
"notification.follow": "{name} fylgist með þér",
"notification.follow_request": "{name} hefur beðið um að fylgjast með þér",
"notification.mention": "{name} minntist á þig",
"notification.own_poll": "Könnuninni þinni er lokið",

View File

@ -1,7 +1,7 @@
{
"account.add_or_remove_from_list": "Aggiungi o togli dalle liste",
"account.badges.bot": "Bot",
"account.badges.group": "Group",
"account.badges.group": "Gruppo",
"account.block": "Blocca @{name}",
"account.block_domain": "Nascondi tutto da {domain}",
"account.blocked": "Bloccato",
@ -43,6 +43,7 @@
"alert.rate_limited.title": "Numero massimo di richieste superato",
"alert.unexpected.message": "Si è verificato un errore inatteso.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per settimana",
"boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
"bundle_column_error.body": "E' avvenuto un errore durante il caricamento di questo componente.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "制限に達しました",
"alert.unexpected.message": "不明なエラーが発生しました。",
"alert.unexpected.title": "エラー!",
"announcement.announcement": "告知",
"autosuggest_hashtag.per_week": "{count} 回 / 週",
"boost_modal.combo": "次からは{combo}を押せばスキップできます",
"bundle_column_error.body": "コンポーネントの読み込み中に問題が発生しました。",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "წარმოიშვა მოულოდნელი შეცდომა.",
"alert.unexpected.title": "უპს!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "კვირაში {count}",
"boost_modal.combo": "შეგიძლიათ დააჭიროთ {combo}-ს რათა შემდეგ ჯერზე გამოტოვოთ ეს",
"bundle_column_error.body": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.",

View File

@ -1,15 +1,15 @@
{
"account.add_or_remove_from_list": "Rnu neɣ kkes seg tebdarin",
"account.add_or_remove_from_list": "Rnu neγ kkes seg tebdarin",
"account.badges.bot": "Aṛubut",
"account.badges.group": "Group",
"account.badges.group": "Agraw",
"account.block": "Seḥbes @{name}",
"account.block_domain": "Ffer kra i d-yekkan seg {domain}",
"account.blocked": "Yettuseḥbes",
"account.cancel_follow_request": "Sefsex asuter n weḍfaṛ",
"account.direct": "Izen usrid i @{name}",
"account.domain_blocked": "Taɣult yeffren",
"account.edit_profile": "Ẓreg amaɣnu",
"account.endorse": "Welleh fell-as deg umaɣnu-inek",
"account.domain_blocked": "Taγult yeffren",
"account.edit_profile": "Ẓreg amaγnu",
"account.endorse": "Welleh fell-as deg umaγnu-inek",
"account.follow": "Ḍfeṛ",
"account.followers": "Imeḍfaṛen",
"account.followers.empty": "Ar tura, ulac yiwen i yeṭṭafaṛen amseqdac-agi.",
@ -18,33 +18,34 @@
"account.follows_you": "Yeṭṭafaṛ-ik",
"account.hide_reblogs": "Ffer ayen i ibeṭṭu @{name}",
"account.last_status": "Armud aneggaru",
"account.link_verified_on": "Taɣara n useɣwen-a tettwasenqed de {date}",
"account.link_verified_on": "Taγara n useγwen-a tettwasenqed ass n {date}",
"account.locked_info": "Amiḍan-agi uslig isekweṛ. D bab-is kan i izemren ad yeǧǧ, s ufus-is, win ara t-iḍefṛen.",
"account.media": "Allal n teywalt",
"account.mention": "Bder-d @{name}",
"account.moved_to": "{name} ibeddel ɣer:",
"account.moved_to": "{name} ibeddel γer:",
"account.mute": "Sgugem @{name}",
"account.mute_notifications": "Ḥbes ilɣa sɣur @{name}",
"account.mute_notifications": "Susem ilγa sγur @{name}",
"account.muted": "Yettwasgugem",
"account.never_active": "Werǧin",
"account.posts": "Tiberraḥin",
"account.posts_with_replies": "Tibarraḥin d tririyin",
"account.report": "Sewɛed @{name}",
"account.requested": "Di laɛḍil ad yettwaqbel. Ssit iwakken ad yefsex usuter n weḍfar",
"account.share": "Bḍu amaɣnu n @{name}",
"account.share": "Bḍu amaγnu n @{name}",
"account.show_reblogs": "Sken-d inebḍa n @{name}",
"account.unblock": "Serreḥ i @{name}",
"account.unblock_domain": "Kkes tuffra i {domain}",
"account.unendorse": "Ur ttwellih ara fell-as deg umaɣnu-inek",
"account.unendorse": "Ur ttwellih ara fell-as deg umaγnu-inek",
"account.unfollow": "Ur ṭṭafaṛ ara",
"account.unmute": "Kkes asgugem ɣef @{name}",
"account.unmute_notifications": "Serreḥ ilɣa sɣur @{name}",
"alert.rate_limited.message": "Ma ulac aɣilif ɛreḍ tikelt-nniḍen mbeɛd {retry_time, time, medium}.",
"account.unmute": "Kkes asgugem γef @{name}",
"account.unmute_notifications": "Serreḥ ilγa sγur @{name}",
"alert.rate_limited.message": "Ma ulac aγilif ɛreḍ tikelt-nniḍen mbeɛd {retry_time, time, medium}.",
"alert.rate_limited.title": "Aktum s talast",
"alert.unexpected.message": "Tella-d tuccḍa i ɣef ur nedmi ara.",
"alert.unexpected.title": "Ayhuh!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} i yimalas",
"boost_modal.combo": "Tzemreḍ ad tetekkiḍ ɣef {combo} akken ad tessurfeḍ aya tikelt-nniḍen",
"boost_modal.combo": "Tzemreḍ ad tetekkiḍ γef {combo} akken ad tessurfeḍ aya tikelt-nniḍen",
"bundle_column_error.body": "Tella-d kra n tuccḍa mi d-yettali ugbur-agi.",
"bundle_column_error.retry": "Ɛreḍ tikelt-nniḍen",
"bundle_column_error.title": "Tuccḍa deg uẓeṭṭa",
@ -55,24 +56,24 @@
"column.bookmarks": "Ticraḍ",
"column.community": "Tasuddemt tadigant",
"column.direct": "Iznan usriden",
"column.directory": "Qelleb deg yimaɣnuten",
"column.domain_blocks": "Tiɣula yettwaffren",
"column.directory": "Qelleb deg imaγnuten",
"column.domain_blocks": "Tiγula yettwaffren",
"column.favourites": "Ismenyifen",
"column.follow_requests": "Isuturen n teḍfeṛt",
"column.home": "Agejdan",
"column.lists": "Tibdarin",
"column.mutes": "Imiḍanen yettwasgugmen",
"column.notifications": "Tilɣa",
"column.notifications": "Tilγa",
"column.pins": "Tiberraḥin yettwasenṭḍen",
"column.public": "Tasuddemt tamatut",
"column_back_button.label": "Tuɣalin",
"column_header.hide_settings": "Ffer iɣewwaṛen",
"column_header.moveLeft_settings": "Err ajgu ɣer tama tazelmaḍt",
"column_header.moveRight_settings": "Err ajgu ɣer tama tayfust",
"column_back_button.label": "Tuγalin",
"column_header.hide_settings": "Ffer iγewwaṛen",
"column_header.moveLeft_settings": "Err ajgu γer tama tazelmaḍt",
"column_header.moveRight_settings": "Err ajgu γer tama tayfust",
"column_header.pin": "Senteḍ",
"column_header.show_settings": "Sken iɣewwaṛen",
"column_header.show_settings": "Sken iγewwaṛen",
"column_header.unpin": "Kkes asenteḍ",
"column_subheading.settings": "Iɣewwaṛen",
"column_subheading.settings": "Iγewwaṛen",
"community.column_settings.media_only": "Allal n teywalt kan",
"compose_form.direct_message_warning": "Taberraḥt-a ad d-tettwasken kan i yimseqdacen i d-yettwabedren.",
"compose_form.direct_message_warning_learn_more": "Issin ugar",
@ -91,39 +92,39 @@
"compose_form.sensitive.hide": "Creḍ allal n teywalt d anafri",
"compose_form.sensitive.marked": "Allal n teywalt yettwacreḍ d anafri",
"compose_form.sensitive.unmarked": "Allal n teywalt ur yettwacreḍ ara d anafri",
"compose_form.spoiler.marked": "Aḍris yeffer deffir n walɣu",
"compose_form.spoiler.marked": "Aḍris yeffer deffir n walγu",
"compose_form.spoiler.unmarked": "Aḍris ur yettwaffer ara",
"compose_form.spoiler_placeholder": "Aru alɣu-inek da",
"compose_form.spoiler_placeholder": "Aru alγu-inek da",
"confirmation_modal.cancel": "Sefsex",
"confirmations.block.block_and_report": "Sewḥel & sewɛed",
"confirmations.block.confirm": "Sewḥel",
"confirmations.block.message": "Tebɣiḍ s tidet ad tesḥebseḍ {name}?",
"confirmations.block.message": "Tebγiḍ s tidet ad tesḥebseḍ {name}?",
"confirmations.delete.confirm": "Kkes",
"confirmations.delete.message": "Tebɣiḍ s tidet ad tekkseḍ tasuffeɣt-agi?",
"confirmations.delete.message": "Tebγiḍ s tidet ad tekkseḍ tasuffeγt-agi?",
"confirmations.delete_list.confirm": "Kkes",
"confirmations.delete_list.message": "Tebɣiḍ s tidet ad tekkseḍ tabdert-agi i lebda?",
"confirmations.domain_block.confirm": "Ffer taɣult meṛṛa",
"confirmations.delete_list.message": "Tebγiḍ s tidet ad tekkseḍ tabdert-agi i lebda?",
"confirmations.domain_block.confirm": "Ffer taγult meṛṛa",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
"confirmations.logout.confirm": "Ffeɣ",
"confirmations.logout.message": "D tidet tebɣiḍ ad teffɣeḍ?",
"confirmations.logout.confirm": "Ffeγ",
"confirmations.logout.message": "D tidet tebγiḍ ad teffγeḍ?",
"confirmations.mute.confirm": "Sgugem",
"confirmations.mute.explanation": "Aya ad yeffer iznan-is d wid i deg d-yettwabder neɣ d-tettwabder, maca xas akka yezmer neɣ tezmer awali n yiznan-inek d uḍfaṛ-ik.",
"confirmations.mute.message": "Tetḥeqqeḍ belli tebɣiḍ asɛuggen n {name}?",
"confirmations.mute.explanation": "Aya ad yeffer iznan-is d wid i deg d-yettwabder neγ d-tettwabder, maca xas akka yezmer neγ tezmer awali n yiznan-inek d uḍfaṛ-ik.",
"confirmations.mute.message": "Tetḥeqqeḍ belli tebγiḍ asɛuggen n {name}?",
"confirmations.redraft.confirm": "Sfeḍ & Ɛiwed tira",
"confirmations.redraft.message": "Tetḥeqqeḍ belli tebɣiḍ asfaḍ n waddad-agi iwakken ad s-tɛiwdeḍ tira? Ismenyifen d beḍḍuwat ad ṛuḥen, ma d tiririyin-is ad uɣalent d tigujilin.",
"confirmations.redraft.message": "Tetḥeqqeḍ belli tebγiḍ asfaḍ n waddad-agi iwakken ad s-tɛiwdeḍ tira? Ismenyifen d beḍḍuwat ad ṛuḥen, ma d tiririyin-is ad uγalent d tigujilin.",
"confirmations.reply.confirm": "Err",
"confirmations.reply.message": "Tiririt akka tura ad k-degger izen-agi i tettaruḍ. Tebɣiḍ ad tkemmleḍ?",
"confirmations.reply.message": "Tiririt akka tura ad k-degger izen-agi i tettaruḍ. Tebγiḍ ad tkemmleḍ?",
"confirmations.unfollow.confirm": "Ur ḍḍafaṛ ara",
"confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teḍḍafaṛeḍ ara {name}?",
"confirmations.unfollow.message": "Tetḥeqqeḍ belli tebγiḍ ur teḍḍafaṛeḍ ara {name}?",
"conversation.delete": "Sfeḍ adiwenni",
"conversation.mark_as_read": "Creḍ yettwaɣṛa",
"conversation.mark_as_read": "Creḍ yettwaγṛa",
"conversation.open": "Sken adiwenni",
"conversation.with": "Akked {names}",
"directory.federated": "Seg fedivers yettwasnen",
"directory.local": "Seg {domain} kan",
"directory.new_arrivals": "Inebgawen imaynuten",
"directory.recently_active": "Yermed xas melmi kan",
"embed.instructions": "Ẓẓu addad-agi deg usmel-inek s wenɣal n tangalt yellan sdaw-agi.",
"embed.instructions": "Ẓẓu addad-agi deg usmel-inek s wenγal n tangalt yellan sdaw-agi.",
"embed.preview": "Akka ara d-iban:",
"emoji_button.activity": "Aqeddic",
"emoji_button.custom": "Udmawan",
@ -132,7 +133,7 @@
"emoji_button.label": "Sekcem imuji",
"emoji_button.nature": "Agama",
"emoji_button.not_found": "Ulac izamulen n yiḥulfan !! (╯°□°)╯︵ ┻━┻",
"emoji_button.objects": "Tiɣawsiwin",
"emoji_button.objects": "Tiγawsiwin",
"emoji_button.people": "Medden",
"emoji_button.recent": "Wid yettuseqdacen s waṭas",
"emoji_button.search": "Nadi…",
@ -140,35 +141,35 @@
"emoji_button.symbols": "Izamulen",
"emoji_button.travel": "Imeḍqan d Yinigen",
"empty_column.account_timeline": "Ulac tiberraḥin dagi!",
"empty_column.account_unavailable": "Ur nufi ara amaɣnu-a",
"empty_column.account_unavailable": "Ur nufi ara amaγnu-a",
"empty_column.blocks": "Ur tesḥebseḍ ula yiwen n umseqdac ar tura.",
"empty_column.bookmarked_statuses": "Ulac tiberraḥin i terniḍ ɣer yismenyifen-ik ar tura. Ticki terniḍ yiwet, ad d-tettwasken da.",
"empty_column.bookmarked_statuses": "Ulac tiberraḥin i terniḍ γer yismenyifen-ik ar tura. Ticki terniḍ yiwet, ad d-tettwasken da.",
"empty_column.community": "Tasuddemt tazayezt tadigant n yisallen d tilemt. Aru ihi kra akken ad tt-teččareḍ!",
"empty_column.direct": "Ulac ɣur-k ula yiwen n yizen usrid. Ad d-yettwasken da, ticki tuzneḍ neɣ teṭṭfeḍ-d yiwen.",
"empty_column.domain_blocks": "Ulac kra n taɣult yettwaffren ar tura.",
"empty_column.direct": "Ulac γur-k ula yiwen n yizen usrid. Ad d-yettwasken da, ticki tuzneḍ neγ teṭṭfeḍ-d yiwen.",
"empty_column.domain_blocks": "Ulac kra n taγult yettwaffren ar tura.",
"empty_column.favourited_statuses": "Ulac ula yiwet n tberraḥt deg yismenyifen-ik ar tura. Ticki Tella-d yiwet, ad d-ban da.",
"empty_column.favourites": "Ula yiwen ur yerri taberraḥt-agi deg yismenyifen-is. Melmi i d-yella waya, ad d-yettwasken da.",
"empty_column.follow_requests": "Ulac ɣur-k ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.",
"empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaɣ ɣer uhacṭag-agi.",
"empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neɣ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.",
"empty_column.follow_requests": "Ulac γur-k ula yiwen n usuter n teḍfeṛt. Ticki teṭṭfeḍ-d yiwen ad d-yettwasken da.",
"empty_column.hashtag": "Ar tura ulac kra n ugbur yesɛan assaγ γer uhacṭag-agi.",
"empty_column.home": "Tasuddemt tagejdant n yisallen d tilemt! Ẓer {public} neγ nadi ad tafeḍ imseqdacen-nniḍen ad ten-ḍefṛeḍ.",
"empty_column.home.public_timeline": "tasuddemt tazayezt n yisallen",
"empty_column.list": "Ar tura ur yelli kra deg tebdert-a. Ad d-yettwasken da ticki iɛeggalen n tebdert-a suffɣen-d kra.",
"empty_column.lists": "Ulac ɣur-k kra n tebdert yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.",
"empty_column.mutes": "Ulac ɣur-k imseqdacen i yettwasgugmen.",
"empty_column.notifications": "Ulac ɣur-k tilɣa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.",
"empty_column.public": "Ulac kra da! Aru kra, neɣ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt",
"empty_column.list": "Ar tura ur yelli kra deg tebdert-a. Ad d-yettwasken da ticki iɛeggalen n tebdert-a suffγen-d kra.",
"empty_column.lists": "Ulac γur-k kra n tebdert yakan. Ad d-tettwasken da ticki tesluleḍ-d yiwet.",
"empty_column.mutes": "Ulac γur-k imseqdacen i yettwasgugmen.",
"empty_column.notifications": "Ulac γur-k tilγa. Sedmer akked yemdanen-nniḍen akken ad tebduḍ adiwenni.",
"empty_column.public": "Ulac kra da! Aru kra, neγ ḍfeṛ imdanen i yellan deg yiqeddacen-nniḍen akken ad d-teččar tsuddemt tazayezt",
"error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
"error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Mastudun deg yiminig-nniḍen neɣ deg usnas anaṣli.",
"error.unexpected_crash.next_steps": "Smiren asebter-a, ma ur yekkis ara wugur, ẓer d akken tzemreḍ ad tesqedceḍ Masṭudun deg yiminig-nniḍen neγ deg usnas anaṣli.",
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
"errors.unexpected_crash.report_issue": "Mmel ugur",
"follow_request.authorize": "Ssireg",
"follow_request.reject": "Agi",
"getting_started.developers": "Ineflayen",
"getting_started.directory": "Imaɣnuten",
"getting_started.directory": "Imaγnuten",
"getting_started.documentation": "Amnir",
"getting_started.heading": "Bdu",
"getting_started.invite": "Snebgi-d imdanen",
"getting_started.open_source_notice": "Mastudun d aseɣzan s uɣbalu yeldin. Tzemreḍ ad tɛiwneḍ neɣ ad temmleḍ uguren seg GitHub {github}.",
"getting_started.open_source_notice": "Mastudun d aseγzan s uγbalu yeldin. Tzemreḍ ad tɛiwneḍ neγ ad temmleḍ uguren deg GitHub {github}.",
"getting_started.security": "Iγewwaṛen n umiḍan",
"getting_started.terms": "Tiwetlin n useqdec",
"hashtag.column_header.tag_mode.all": "d {additional}",
@ -190,29 +191,29 @@
"introduction.federation.federated.headline": "Amatu",
"introduction.federation.federated.text": "Iznan izuyaz i d-yekkan seg yiqeddacen-nniḍen n fediverse ad banen deg tsuddemt tazayezt tamatut n yisallen.",
"introduction.federation.home.headline": "Agejdan",
"introduction.federation.home.text": "Iznan n yemdanen i teṭṭafaṛeḍ ad banen deg tsuddemt n umagger. Tzemreḍ ad tḍefṛeḍ win tebɣiḍ deg uqeddac i tebɣiḍ!",
"introduction.federation.home.text": "Iznan n yemdanen i teṭṭafaṛeḍ ad banen deg tsuddemt n umagger. Tzemreḍ ad tḍefṛeḍ win tebγiḍ deg uqeddac i tebγiḍ!",
"introduction.federation.local.headline": "Adigan",
"introduction.federation.local.text": "Iznan izuyaz n yemdanen i yellan deg yiwen uqeddac akked kečč ad d-banen deg tsuddemt tazayezt tadigant.",
"introduction.interactions.action": "Fakk tameskant!",
"introduction.interactions.favourite.headline": "Ismenyifen",
"introduction.interactions.favourite.text": "Tzemreḍ ad teǧǧeḍ kra n tberraḥt i ticki, daɣen ad tiniḍ i bab-is d akken taɛǧeb-ik, s tmerna-ines ɣer yismenyifen-ik.",
"introduction.interactions.favourite.text": "Tzemreḍ ad teǧǧeḍ kra n tberraḥt i ticki, daγen ad tiniḍ i bab-is d akken taɛǧeb-ik, s tmerna-ines γer yismenyifen-ik.",
"introduction.interactions.reblog.headline": "Bḍu tikelt-nniḍen",
"introduction.interactions.reblog.text": "Tzemreḍ ad tebḍuḍ akked yimeḍfaṛen-ik tiberraḥin n yemdanen-nniḍen s beṭṭu-nsent tikelt-nniḍen.",
"introduction.interactions.reply.headline": "Err",
"introduction.interactions.reply.text": "Tzemreḍ ad terreḍ ɣef tberraḥin-ik d tid n medden-nniḍen, d acu ara tent-id-iɛeqden ta deffir ta deg udiwenni.",
"introduction.interactions.reply.text": "Tzemreḍ ad terreḍ γef tberraḥin-ik d tid n medden-nniḍen, d acu ara tent-id-iɛeqden ta deffir ta deg udiwenni.",
"introduction.welcome.action": "Bdu!",
"introduction.welcome.headline": "Isurifen imenza",
"introduction.welcome.text": "Anṣuf ɣer fediverse! Deg kra n yimiren, ad tizmireḍ ad tzzuzreḍ iznan neɣ ad tmeslayeḍ i yemddukkal deg waṭas n yiqeddacen. Maca aqeddac-agi, {domain}, mačči am wiyaḍ - deg-s i yella umaɣnu-ik, ihi cfu ɣef yisem-is.",
"keyboard_shortcuts.back": "uɣal ar deffir",
"introduction.welcome.text": "Anṣuf γer fediverse! Deg kra n yimiren, ad tizmireḍ ad tzzuzreḍ iznan neɣ ad tmeslayeḍ i yemddukkal deg waṭas n yiqeddacen. Maca aqeddac-agi, {domain}, mačči am wiyaḍ - deg-s i yella umaγnu-ik, ihi cfu γef yisem-is.",
"keyboard_shortcuts.back": "uγal ar deffir",
"keyboard_shortcuts.blocked": "akken ad teldiḍ tabdert n yimseqdacen yettwasḥebsen",
"keyboard_shortcuts.boost": "i beṭṭu tikelt-nniḍen",
"keyboard_shortcuts.column": "to focus a status in one of the columns",
"keyboard_shortcuts.compose": "to focus the compose textarea",
"keyboard_shortcuts.description": "Aglam",
"keyboard_shortcuts.direct": "akken ad teldiḍ ajgu n yiznan usriden",
"keyboard_shortcuts.down": "i kennu ɣer wadda n tebdert",
"keyboard_shortcuts.enter": "i tildin n tsuffeɣt",
"keyboard_shortcuts.favourite": "akken ad ternuḍ ɣer yismenyifen",
"keyboard_shortcuts.down": "i kennu γer wadda n tebdert",
"keyboard_shortcuts.enter": "i tildin n tsuffeγt",
"keyboard_shortcuts.favourite": "akken ad ternuḍ γer yismenyifen",
"keyboard_shortcuts.favourites": "i tildin n tebdert n yismenyifen",
"keyboard_shortcuts.federated": "i tildin n tsuddemt tamatut n yisallen",
"keyboard_shortcuts.heading": "Inegzumen n unasiw",
@ -222,11 +223,11 @@
"keyboard_shortcuts.local": "i tildin n tsuddemt tadigant n yisallen",
"keyboard_shortcuts.mention": "akken ad d-bedreḍ ameskar",
"keyboard_shortcuts.muted": "akken ad teldiḍ tabdert n yimseqdacen yettwasgugmen",
"keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaɣnu-ik",
"keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n tilɣa",
"keyboard_shortcuts.my_profile": "akken ad d-teldiḍ amaγnu-ik",
"keyboard_shortcuts.notifications": "akken ad d-teldiḍ ajgu n tilγa",
"keyboard_shortcuts.open_media": "to open media",
"keyboard_shortcuts.pinned": "i tildin n tebdert n tberraḥin yettwasentḍen",
"keyboard_shortcuts.profile": "akken ad d-teldiḍ amaɣnu n umeskar",
"keyboard_shortcuts.profile": "akken ad d-teldiḍ amaγnu n umeskar",
"keyboard_shortcuts.reply": "i tririt",
"keyboard_shortcuts.requests": "akken ad d-teldiḍ tabdert n yisuturen n teḍfeṛt",
"keyboard_shortcuts.search": "to focus search",
@ -235,7 +236,7 @@
"keyboard_shortcuts.toggle_sensitivity": "i teskent/tuffra n yimidyaten",
"keyboard_shortcuts.toot": "i beddu n tberraḥt tamaynut",
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "i tulin ɣer ufella n tebdert",
"keyboard_shortcuts.up": "i tulin γer ufella n tebdert",
"lightbox.close": "Mdel",
"lightbox.next": "Γer zdat",
"lightbox.previous": "Γer deffir",
@ -253,8 +254,8 @@
"loading_indicator.label": "Asali...",
"media_gallery.toggle_visible": "Sken / Ffer",
"missing_indicator.label": "Ulac-it",
"missing_indicator.sublabel": "Ur nufi ara aɣbalu-a",
"mute_modal.hide_notifications": "Tebɣiḍ ad teffreḍ talɣutin n umseqdac-a?",
"missing_indicator.sublabel": "Ur nufi ara aγbalu-a",
"mute_modal.hide_notifications": "Tebγiḍ ad teffreḍ talγutin n umseqdac-a?",
"navigation_bar.apps": "Isnasen izirazen",
"navigation_bar.blocks": "Imseqdacen yettusḥebsen",
"navigation_bar.bookmarks": "Ticraḍ",
@ -262,13 +263,13 @@
"navigation_bar.compose": "Aru taberraḥt tamaynut",
"navigation_bar.direct": "Iznan usridden",
"navigation_bar.discover": "Ẓer",
"navigation_bar.domain_blocks": "Tiɣula yeffren",
"navigation_bar.edit_profile": "Ẓreg amaɣnu",
"navigation_bar.domain_blocks": "Tiγula yeffren",
"navigation_bar.edit_profile": "Ẓreg amaγnu",
"navigation_bar.favourites": "Ismenyifen",
"navigation_bar.filters": "Awalen i yettwasgugmen",
"navigation_bar.follow_requests": "Isuturen n teḍfeṛt",
"navigation_bar.follows_and_followers": "Imeḍfaṛen akked wid i teṭṭafaṛeḍ",
"navigation_bar.info": "Ɣef uqeddac-agi",
"navigation_bar.info": "Γef uqeddac-a",
"navigation_bar.keyboard_shortcuts": "Inegzumen n unasiw",
"navigation_bar.lists": "Tibdarin",
"navigation_bar.logout": "Ffeγ",
@ -278,16 +279,16 @@
"navigation_bar.preferences": "Imenyafen",
"navigation_bar.public_timeline": "Tasuddemt tazayezt tamatut",
"navigation_bar.security": "Taγellist",
"notification.favourite": "{name} yesmenyef tasuffeɣt-ik",
"notification.favourite": "{name} yesmenyef tasuffeγt-ik",
"notification.follow": "{name} yeṭṭafaṛ-ik",
"notification.follow_request": "{name} yessuter-d ad k-yeḍfeṛ",
"notification.mention": "{name} yebder-ik-id",
"notification.own_poll": "Your poll has ended",
"notification.poll": "A poll you have voted in has ended",
"notification.reblog": "{name} yebḍa taberraḥ-ik i tikelt-nniḍen",
"notifications.clear": "Sfeḍ tilɣa",
"notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-ik i lebda?",
"notifications.column_settings.alert": "Tilɣa n tnarit",
"notifications.clear": "Sfeḍ tilγa",
"notifications.clear_confirmation": "Tebγiḍ s tidet ad tekkseḍ akk tilγa-ik i lebda?",
"notifications.column_settings.alert": "Tilγa n tnarit",
"notifications.column_settings.favourite": "Ismenyifen:",
"notifications.column_settings.filter_bar.advanced": "Sken-d meṛṛa tiggayin",
"notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib",
@ -295,26 +296,26 @@
"notifications.column_settings.follow": "Imeḍfaṛen imaynuten:",
"notifications.column_settings.follow_request": "Isuturen imaynuten n teḍfeṛt:",
"notifications.column_settings.mention": "Abdar:",
"notifications.column_settings.poll": "Poll results:",
"notifications.column_settings.push": "Tilɣa yettudemmren",
"notifications.column_settings.poll": "Igemmaḍ n usenqed:",
"notifications.column_settings.push": "Tilγa yettudemmren",
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "Sken-d tilɣa deg ujgu",
"notifications.column_settings.show": "Sken-d tilγa deg ujgu",
"notifications.column_settings.sound": "Rmed imesli",
"notifications.filter.all": "Akk",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Ismenyifen",
"notifications.filter.follows": "Yeṭafaṛ",
"notifications.filter.mentions": "Abdar",
"notifications.filter.polls": "Poll results",
"notifications.group": "{count} n tilɣa",
"notifications.filter.polls": "Igemmaḍ n usenqed",
"notifications.group": "{count} n tilγa",
"poll.closed": "Ifukk",
"poll.refresh": "Smiren",
"poll.total_people": "{count, plural, one {# n wemdan} other {# n yemdanen}}",
"poll.total_votes": "{count, plural, one {# n udɣaṛ} other {# n yedɣaṛen}}",
"poll.vote": "Dɣeṛ",
"poll.voted": "Tdeɣṛeḍ ɣef tririt-agi",
"poll_button.add_poll": "Add a poll",
"poll_button.remove_poll": "Remove poll",
"poll.total_votes": "{count, plural, one {# n udγaṛ} other {# n yedγaṛen}}",
"poll.vote": "Dγeṛ",
"poll.voted": "Tdeγṛeḍ γef tririt-agi",
"poll_button.add_poll": "Rnu asenqed",
"poll_button.remove_poll": "Kkes asenqed",
"privacy.change": "Adjust status privacy",
"privacy.direct.long": "Bḍu gar yimseqdacen i tbedreḍ kan",
"privacy.direct.short": "Usrid",
@ -356,13 +357,13 @@
"status.block": "Seḥbes @{name}",
"status.bookmark": "Creḍ",
"status.cancel_reblog_private": "Sefsex beṭṭu",
"status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
"status.copy": "Nɣel assaɣ ɣer tasuffeɣt",
"status.cannot_reblog": "Tasuffeγt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
"status.copy": "Nγel assaγ γer tasuffeγt",
"status.delete": "Kkes",
"status.detailed_status": "Detailed conversation view",
"status.direct": "Izen usrid i @{name}",
"status.embed": "Embed",
"status.favourite": "Rnu ɣer yismenyifen",
"status.favourite": "Rnu γer yismenyifen",
"status.filtered": "Yettwasizdeg",
"status.load_more": "Sali ugar",
"status.media_hidden": "Media hidden",
@ -370,8 +371,8 @@
"status.more": "Ugar",
"status.mute": "Sussem @{name}",
"status.mute_conversation": "Mute conversation",
"status.open": "Semɣeṛ tasuffeɣt-agi",
"status.pin": "Senteḍ-itt deg umaɣnu",
"status.open": "Semγeṛ tasuffeγt-agi",
"status.pin": "Senteḍ-itt deg umaγnu",
"status.pinned": "Tiberraḥin yettwasentḍen",
"status.read_more": "Issin ugar",
"status.reblog": "Bḍu",
@ -386,13 +387,13 @@
"status.sensitive_warning": "Agbur amḥulfu",
"status.share": "Bḍu",
"status.show_less": "Sken-d drus",
"status.show_less_all": "Semẓi akk tisuffɣin",
"status.show_less_all": "Semẓi akk tisuffγin",
"status.show_more": "Sken-ed ugar",
"status.show_more_all": "Ẓerr ugar lebda",
"status.show_thread": "Show thread",
"status.uncached_media_warning": "Ulac-it",
"status.unmute_conversation": "Kkes asgugem n udiwenni",
"status.unpin": "Kkes asenteḍ seg umaɣnu",
"status.unpin": "Kkes asenteḍ seg umaγnu",
"suggestions.dismiss": "Dismiss suggestion",
"suggestions.header": "Ahat ad tcelgeḍ deg…",
"tabs_bar.federated_timeline": "Amatu",
@ -416,10 +417,10 @@
"upload_form.description": "Glem-d i yemdaneni yesɛan ugur deg yiẓri",
"upload_form.edit": "Ẓreg",
"upload_form.undo": "Kkes",
"upload_form.video_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt neɣ deg yiẓri",
"upload_form.video_description": "Glem-d i yemdanen i yesɛan ugur deg tmesliwt neγ deg yiẓri",
"upload_modal.analyzing_picture": "Tasleḍt n tugna tetteddu…",
"upload_modal.apply": "Snes",
"upload_modal.description_placeholder": "Aberraɣ arurad ineggez nnig n uqjun amuṭṭis",
"upload_modal.description_placeholder": "Aberraγ arurad ineggez nnig n uqjun amuṭṭis",
"upload_modal.detect_text": "Detect text from picture",
"upload_modal.edit_media": "Edit media",
"upload_modal.hint": "Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.",
@ -427,8 +428,8 @@
"upload_progress.label": "Asali iteddu...",
"video.close": "Mdel tabidyutt",
"video.download": "Sidered afaylu",
"video.exit_fullscreen": "Ffeɣ seg ugdil aččuran",
"video.expand": "Semɣeṛ tavidyut",
"video.exit_fullscreen": "Ffeγ seg ugdil aččuran",
"video.expand": "Semγeṛ tavidyut",
"video.fullscreen": "Agdil aččuran",
"video.hide": "Ffer tabidyutt",
"video.mute": "Gzem imesli",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Бағалау шектеулі",
"alert.unexpected.message": "Бір нәрсе дұрыс болмады.",
"alert.unexpected.title": "Өй!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} аптасына",
"boost_modal.combo": "Келесіде өткізіп жіберу үшін басыңыз {combo}",
"bundle_column_error.body": "Бұл компонентті жүктеген кезде бір қате пайда болды.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "빈도 제한",
"alert.unexpected.message": "예측하지 못한 에러가 발생했습니다.",
"alert.unexpected.title": "앗!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "주간 {count}회",
"boost_modal.combo": "{combo}를 누르면 다음부터 이 과정을 건너뛸 수 있습니다",
"bundle_column_error.body": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "Negaidīta kļūda.",
"alert.unexpected.title": "Ups!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "Nospied {combo} lai izlaistu šo nākamreiz",
"bundle_column_error.body": "Kaut kas nogāja greizi ielādējot šo komponenti.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "Неочекувана грешка.",
"alert.unexpected.title": "Упс!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} неделно",
"boost_modal.combo": "Кликни {combo} за да го прескокниш ова нареден пат",
"bundle_column_error.body": "Се случи проблем при вчитувањето.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "തോത് പരിമിതപ്പെടുത്തിയിരിക്കുന്നു",
"alert.unexpected.message": "അപ്രതീക്ഷിതമായി എന്തോ സംഭവിച്ചു.",
"alert.unexpected.title": "ശ്ശോ!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "ആഴ്ച തോറും {count}",
"boost_modal.combo": "അടുത്ത തവണ ഇത് ഒഴിവാക്കുവാൻ {combo} ഞെക്കാവുന്നതാണ്",
"bundle_column_error.body": "ഈ ഘടകം പ്രദശിപ്പിക്കുമ്പോൾ എന്തോ കുഴപ്പം സംഭവിച്ചു.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "अरेरे!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} प्रतिसप्ताह",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "हा घटक लोड करतांना काहीतरी चुकले आहे.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Rate limited",
"alert.unexpected.message": "An unexpected error occurred.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.body": "Something went wrong while loading this component.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Beperkt te gebruiken",
"alert.unexpected.message": "Er deed zich een onverwachte fout voor",
"alert.unexpected.title": "Oeps!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per week",
"boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
"bundle_column_error.body": "Tijdens het laden van dit onderdeel is er iets fout gegaan.",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Begrensa rate",
"alert.unexpected.message": "Eit uventa problem oppstod.",
"alert.unexpected.title": "Oi sann!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per veke",
"boost_modal.combo": "Du kan trykkja {combo} for å hoppa over dette neste gong",
"bundle_column_error.body": "Noko gjekk gale mens denne komponenten vart lasta ned.",
@ -170,7 +171,7 @@
"getting_started.invite": "Byd folk inn",
"getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidraga eller rapportera problem med GitHub på {github}.",
"getting_started.security": "Kontoinnstillingar",
"getting_started.terms": "Brukarvillkår",
"getting_started.terms": "Brukarvilkår",
"hashtag.column_header.tag_mode.all": "og {additional}",
"hashtag.column_header.tag_mode.any": "eller {additional}",
"hashtag.column_header.tag_mode.none": "utan {additional}",
@ -354,7 +355,7 @@
"status.admin_account": "Opne moderasjonsgrensesnitt for @{name}",
"status.admin_status": "Opne denne statusen i moderasjonsgrensesnittet",
"status.block": "Blokker @{name}",
"status.bookmark": "Bokmerke",
"status.bookmark": "Bokmerk",
"status.cancel_reblog_private": "Opphev framheving",
"status.cannot_reblog": "Denne posten kan ikkje framhevast",
"status.copy": "Kopier lenke til status",

View File

@ -43,6 +43,7 @@
"alert.rate_limited.title": "Hastighetsbegrenset",
"alert.unexpected.message": "En uventet feil oppstod.",
"alert.unexpected.title": "Oops!",
"announcement.announcement": "Announcement",
"autosuggest_hashtag.per_week": "{count} per uke",
"boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
"bundle_column_error.body": "Noe gikk galt mens denne komponenten lastet.",

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