diff --git a/.env.production.sample b/.env.production.sample index 6036095e4f..f573a37def 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -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 diff --git a/.nvmrc b/.nvmrc index 45a4fb75db..48082f72f0 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -8 +12 diff --git a/app/controllers/admin/announcements_controller.rb b/app/controllers/admin/announcements_controller.rb new file mode 100644 index 0000000000..02198f0b52 --- /dev/null +++ b/app/controllers/admin/announcements_controller.rb @@ -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 diff --git a/app/controllers/admin/followers_controller.rb b/app/controllers/admin/followers_controller.rb deleted file mode 100644 index d826f47c5a..0000000000 --- a/app/controllers/admin/followers_controller.rb +++ /dev/null @@ -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 diff --git a/app/controllers/admin/relationships_controller.rb b/app/controllers/admin/relationships_controller.rb new file mode 100644 index 0000000000..f8a95cfc8f --- /dev/null +++ b/app/controllers/admin/relationships_controller.rb @@ -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 diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 144fdd6ac9..68bf425f4d 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -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! diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb index c8c60b1cf6..66da65beda 100644 --- a/app/controllers/api/oembed_controller.rb +++ b/app/controllers/api/oembed_controller.rb @@ -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 diff --git a/app/controllers/api/v1/announcements/reactions_controller.rb b/app/controllers/api/v1/announcements/reactions_controller.rb new file mode 100644 index 0000000000..e4a72e595c --- /dev/null +++ b/app/controllers/api/v1/announcements/reactions_controller.rb @@ -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 diff --git a/app/controllers/api/v1/announcements_controller.rb b/app/controllers/api/v1/announcements_controller.rb new file mode 100644 index 0000000000..6724fac2ec --- /dev/null +++ b/app/controllers/api/v1/announcements_controller.rb @@ -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 diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb index a59806f0d4..c224e1a03b 100644 --- a/app/controllers/auth/passwords_controller.rb +++ b/app/controllers/auth/passwords_controller.rb @@ -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 diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index a9d075a451..531df7751c 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -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 diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb index 25dd0d2ad8..f1ab980c88 100644 --- a/app/controllers/relationships_controller.rb +++ b/app/controllers/relationships_controller.rb @@ -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 diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 1b00d38c98..588063d010 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -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 diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 608a99dd5b..6bc75aa566 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -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 diff --git a/app/helpers/admin/announcements_helper.rb b/app/helpers/admin/announcements_helper.rb new file mode 100644 index 0000000000..0c053ddec3 --- /dev/null +++ b/app/helpers/admin/announcements_helper.rb @@ -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 diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb index 130686a02e..6ab92939d8 100644 --- a/app/helpers/admin/filter_helper.rb +++ b/app/helpers/admin/filter_helper.rb @@ -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) diff --git a/app/javascript/flavours/glitch/actions/announcements.js b/app/javascript/flavours/glitch/actions/announcements.js new file mode 100644 index 0000000000..d0e5ee176f --- /dev/null +++ b/app/javascript/flavours/glitch/actions/announcements.js @@ -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, +}); diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index 2bc603930f..52ad177797 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -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; +} diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 940f3c3d44..b3de7b5bfc 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -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, }; }; diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index 21379f4923..8294fbf368 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -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); diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 097878c3bf..2ef78025e3 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -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(); }); }; diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 2ef4ff6027..f25c82a006 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -112,6 +112,7 @@ class AccountTimeline extends ImmutablePureComponent { onLoadMore={this.handleLoadMore} emptyMessage={} bindToDocument={!multiColumn} + timelineId='account' /> ); diff --git a/app/javascript/flavours/glitch/features/directory/components/account_card.js b/app/javascript/flavours/glitch/features/directory/components/account_card.js index d1c4069333..5571209602 100644 --- a/app/javascript/flavours/glitch/features/directory/components/account_card.js +++ b/app/javascript/flavours/glitch/features/directory/components/account_card.js @@ -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 = () => { diff --git a/app/javascript/flavours/glitch/features/emoji_picker/index.js b/app/javascript/flavours/glitch/features/emoji_picker/index.js index 6e5518b0c5..3717fcd828 100644 --- a/app/javascript/flavours/glitch/features/emoji_picker/index.js +++ b/app/javascript/flavours/glitch/features/emoji_picker/index.js @@ -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 (
- 🙂 + />}
diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js new file mode 100644 index 0000000000..0107787277 --- /dev/null +++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js @@ -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 ( +
+ ); + } + +} + +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 ( + {emoji} + ); + } else if (emojiMap.get(emoji)) { + const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); + const shortCode = `:${emoji}:`; + + return ( + {shortCode} + ); + } 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 ( + + ); + } + +} + +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 ( +
+ {visibleReactions.map(reaction => ( + + ))} + + } /> +
+ ); + } + +} + +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 ( +
+ + + {hasTimeRange && · - } + + + + + + + +
+ ); + } + +} + +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 ( +
+ + +
+ + {announcements.map(announcement => ( + + ))} + + +
+ + {index + 1} / {announcements.size} + +
+
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js new file mode 100644 index 0000000000..b10d1d4cea --- /dev/null +++ b/app/javascript/flavours/glitch/features/getting_started/containers/announcements_container.js @@ -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); diff --git a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js index 1df3fb4fe2..7a52687808 100644 --- a/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js +++ b/app/javascript/flavours/glitch/features/getting_started/containers/trends_container.js @@ -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 => ({ diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.js b/app/javascript/flavours/glitch/features/home_timeline/index.js index 9b71a44040..263371b066 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.js +++ b/app/javascript/flavours/glitch/features/home_timeline/index.js @@ -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 { } + alwaysPrepend trackScroll={!pinned} scrollKey={`home_timeline-${columnId}`} onLoadMore={this.handleLoadMore} diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.js b/app/javascript/flavours/glitch/features/ui/components/media_modal.js index c7d6c374c9..23e8dac7e6 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.js @@ -191,7 +191,6 @@ class MediaModal extends ImmutablePureComponent { style={swipeableViewsStyle} containerStyle={containerStyle} onChangeIndex={this.handleSwipe} - onSwitching={this.handleSwitching} index={index} > {content} diff --git a/app/javascript/flavours/glitch/reducers/announcements.js b/app/javascript/flavours/glitch/reducers/announcements.js new file mode 100644 index 0000000000..aa674e516f --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/announcements.js @@ -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; + } +}; diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index 7dbca3a29a..586b847499 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -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, diff --git a/app/javascript/flavours/glitch/selectors/index.js b/app/javascript/flavours/glitch/selectors/index.js index 8ceb71d032..ab7dac66ae 100644 --- a/app/javascript/flavours/glitch/selectors/index.js +++ b/app/javascript/flavours/glitch/selectors/index.js @@ -27,6 +27,7 @@ export const toServerSideType = columnType => { case 'notifications': case 'public': case 'thread': + case 'account': return columnType; default: if (columnType.indexOf('list:') > -1) { diff --git a/app/javascript/flavours/glitch/styles/components/announcements.scss b/app/javascript/flavours/glitch/styles/components/announcements.scss new file mode 100644 index 0000000000..0d1f1837ba --- /dev/null +++ b/app/javascript/flavours/glitch/styles/components/announcements.scss @@ -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; + } + } +} diff --git a/app/javascript/flavours/glitch/styles/components/composer.scss b/app/javascript/flavours/glitch/styles/components/composer.scss index 51287f62e9..9437760103 100644 --- a/app/javascript/flavours/glitch/styles/components/composer.scss +++ b/app/javascript/flavours/glitch/styles/components/composer.scss @@ -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; diff --git a/app/javascript/flavours/glitch/styles/components/index.scss b/app/javascript/flavours/glitch/styles/components/index.scss index 8e576fd863..abe933860d 100644 --- a/app/javascript/flavours/glitch/styles/components/index.scss +++ b/app/javascript/flavours/glitch/styles/components/index.scss @@ -1649,3 +1649,4 @@ noscript { @import 'local_settings'; @import 'error_boundary'; @import 'single_column'; +@import 'announcements'; diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 1920c33ea2..396e87c6c1 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -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%); diff --git a/app/javascript/flavours/glitch/util/stream.js b/app/javascript/flavours/glitch/util/stream.js index 50f90d44cb..fe965bcb0e 100644 --- a/app/javascript/flavours/glitch/util/stream.js +++ b/app/javascript/flavours/glitch/util/stream.js @@ -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; }; diff --git a/app/javascript/images/elephant_ui_plane.svg b/app/javascript/images/elephant_ui_plane.svg index a2624d170e..ca675c9eb3 100644 --- a/app/javascript/images/elephant_ui_plane.svg +++ b/app/javascript/images/elephant_ui_plane.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/javascript/mastodon/actions/announcements.js b/app/javascript/mastodon/actions/announcements.js new file mode 100644 index 0000000000..c65bc052ec --- /dev/null +++ b/app/javascript/mastodon/actions/announcements.js @@ -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, +}); diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index 78f321da4d..f7cbe4c1cd 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -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; +} diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 798f9b37ea..8a066b896a 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -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, }; }; diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index c678e93932..ac325f74cd 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -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); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index bc2ac5e823..0546686559 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -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(); }); }; diff --git a/app/javascript/mastodon/components/error_boundary.js b/app/javascript/mastodon/components/error_boundary.js index 800b1c2706..4e1c882e22 100644 --- a/app/javascript/mastodon/components/error_boundary.js +++ b/app/javascript/mastodon/components/error_boundary.js @@ -58,7 +58,7 @@ export default class ErrorBoundary extends React.PureComponent {

-

Mastodon v{version} · ·

+

Mastodon v{version} · ·

); diff --git a/app/javascript/mastodon/features/account_timeline/index.js b/app/javascript/mastodon/features/account_timeline/index.js index 8d0cbe5a19..37622d4c0b 100644 --- a/app/javascript/mastodon/features/account_timeline/index.js +++ b/app/javascript/mastodon/features/account_timeline/index.js @@ -115,6 +115,7 @@ class AccountTimeline extends ImmutablePureComponent { shouldUpdateScroll={shouldUpdateScroll} emptyMessage={emptyMessage} bindToDocument={!multiColumn} + timelineId='account' /> ); diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js index e57c3c20c3..582bb0d39c 100644 --- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -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 (
- 🙂 + />}
diff --git a/app/javascript/mastodon/features/directory/components/account_card.js b/app/javascript/mastodon/features/directory/components/account_card.js index 50ad744501..cb47d9db4b 100644 --- a/app/javascript/mastodon/features/directory/components/account_card.js +++ b/app/javascript/mastodon/features/directory/components/account_card.js @@ -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 = () => { diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.js b/app/javascript/mastodon/features/getting_started/components/announcements.js new file mode 100644 index 0000000000..ee444e3f03 --- /dev/null +++ b/app/javascript/mastodon/features/getting_started/components/announcements.js @@ -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 ( +
+ ); + } + +} + +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 ( + {emoji} + ); + } else if (emojiMap.get(emoji)) { + const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); + const shortCode = `:${emoji}:`; + + return ( + {shortCode} + ); + } 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 ( + + ); + } + +} + +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 ( +
+ {visibleReactions.map(reaction => ( + + ))} + + } /> +
+ ); + } + +} + +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 ( +
+ + + {hasTimeRange && · - } + + + + + + + +
+ ); + } + +} + +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 ( +
+ + +
+ + {announcements.map(announcement => ( + + ))} + + +
+ + {index + 1} / {announcements.size} + +
+
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/getting_started/containers/announcements_container.js b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js new file mode 100644 index 0000000000..b10d1d4cea --- /dev/null +++ b/app/javascript/mastodon/features/getting_started/containers/announcements_container.js @@ -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); diff --git a/app/javascript/mastodon/features/getting_started/containers/trends_container.js b/app/javascript/mastodon/features/getting_started/containers/trends_container.js index 1df3fb4fe2..7a52687808 100644 --- a/app/javascript/mastodon/features/getting_started/containers/trends_container.js +++ b/app/javascript/mastodon/features/getting_started/containers/trends_container.js @@ -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 => ({ diff --git a/app/javascript/mastodon/features/home_timeline/index.js b/app/javascript/mastodon/features/home_timeline/index.js index 1cafb88eda..b7f9d50952 100644 --- a/app/javascript/mastodon/features/home_timeline/index.js +++ b/app/javascript/mastodon/features/home_timeline/index.js @@ -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 { } + alwaysPrepend trackScroll={!pinned} scrollKey={`home_timeline-${columnId}`} onLoadMore={this.handleLoadMore} diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js index a785551c0f..d7f97f210c 100644 --- a/app/javascript/mastodon/features/ui/components/media_modal.js +++ b/app/javascript/mastodon/features/ui/components/media_modal.js @@ -211,7 +211,6 @@ class MediaModal extends ImmutablePureComponent { style={swipeableViewsStyle} containerStyle={containerStyle} onChangeIndex={this.handleSwipe} - onSwitching={this.handleSwitching} index={index} > {content} diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index dc4b8ce769..5ce5eb12e0 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -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": "ليس هناك نطاقات مخفية بعد.", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index ba344e7eaa..e570fc3b7f 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -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", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index d980df27bd..6a6f9a3093 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -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.", diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json index ed1a92dd25..283812fdf6 100644 --- a/app/javascript/mastodon/locales/bn.json +++ b/app/javascript/mastodon/locales/bn.json @@ -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": "এই অংশটি দেখতে যেয়ে কোনো সমস্যা হয়েছে।.", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index 89ea5043ef..967577f2ef 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index ceb5c39e45..6516a4a807 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -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 l’enquesta per a permetre diverses opcions", - "compose_form.poll.switch_to_single": "Canvía l’enquesta per a permetre una sola opció", + "compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre diverses opcions", + "compose_form.poll.switch_to_single": "Canvia l’enquesta 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", diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json index 2afd0f1fa9..091a73c85f 100644 --- a/app/javascript/mastodon/locales/co.json +++ b/app/javascript/mastodon/locales/co.json @@ -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.", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 8b9b5a86b2..f180b7f94b 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -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á", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index 923d1947b6..ee58c7168c 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -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", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index bd68381d91..a699aaf54b 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -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.", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0804d80331..aae5ad1c17 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -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", diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index fea31633a2..8cd2bb8a31 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -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" diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 9ae9e9a849..d9d2ef5901 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -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": "Σημείωσε τα πολυμέσα ως ευαίσθητα", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 2910eedbd3..5871819a92 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -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.", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index f921b263ca..e84e59b2e8 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -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.", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index ee46e24dd3..70bcef1338 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -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.", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index d056a843cf..31f190616c 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -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.", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index 94e73ae3d0..9a13b4ee01 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -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.", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 3ae39ffba1..e9fbcedac8 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -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", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 38a53e9b16..c2a8eb0de2 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -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": "علامت‌گذاری رسانه به عنوان حساس", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index b43f18d23b..8a069f2a3d 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -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.", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index ffb77d9f68..7cd5ec0e36 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Débit limité", "alert.unexpected.message": "Une erreur inattendue s’est 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 s’est produite lors du chargement de ce composant.", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index 4966946ccd..5084567361 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -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.", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 695017b0ef..b2715cc4b8 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -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", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e69fa5d8f7..07217364ab 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -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": "משהו השתבש בעת הצגת הרכיב הזה.", diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index 0e8830174f..ff7ea9e6a5 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -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": "इस कॉम्पोनेन्ट को लोड करते वक्त कुछ गलत हो गया", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index da01b92aef..31327e3d3c 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -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.", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 57511307ac..f4280e7400 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -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", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index b20b744d50..1ad3cb0fd3 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -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.", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 8e7b492feb..b1605ac60e 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -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.", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 673a0deb22..36bf1a588a 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -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.", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index c1f1b37200..53b2a150a1 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -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ð", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index 3d5353f2a6..1b31f90f68 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 76ad78012b..51eeb1eb64 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -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": "コンポーネントの読み込み中に問題が発生しました。", diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json index 1c6203c468..1222702a47 100644 --- a/app/javascript/mastodon/locales/ka.json +++ b/app/javascript/mastodon/locales/ka.json @@ -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": "ამ კომპონენტის ჩატვირთვისას რაღაც აირია.", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index edb539f4f1..ec77fa9e4f 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -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", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index 86f3626f96..b307a31616 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -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": "Бұл компонентті жүктеген кезде бір қате пайда болды.", diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json index 96872bbbda..278f6b14c5 100644 --- a/app/javascript/mastodon/locales/kn.json +++ b/app/javascript/mastodon/locales/kn.json @@ -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.", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 635a6c8bde..f7be26c095 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -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": "컴포넌트를 불러오는 과정에서 문제가 발생했습니다.", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 96872bbbda..278f6b14c5 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -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.", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index efdb234366..b2f8fedbf1 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -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.", diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json index 2a31a5c22e..90c8d2418b 100644 --- a/app/javascript/mastodon/locales/mk.json +++ b/app/javascript/mastodon/locales/mk.json @@ -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": "Се случи проблем при вчитувањето.", diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json index 0705f20e35..6a042e8c4e 100644 --- a/app/javascript/mastodon/locales/ml.json +++ b/app/javascript/mastodon/locales/ml.json @@ -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": "ഈ ഘടകം പ്രദശിപ്പിക്കുമ്പോൾ എന്തോ കുഴപ്പം സംഭവിച്ചു.", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index a4dcbb144f..f265042f24 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -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": "हा घटक लोड करतांना काहीतरी चुकले आहे.", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 4e858d59ec..3bd6e145ef 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -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.", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 7fbaf42044..d570f3612d 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -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.", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 00e323cc1e..d697172924 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -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", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index a6aa5db7c2..c6dc4ca0e2 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -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.", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 601ed93c6b..b7701c17e6 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Taus limitat", "alert.unexpected.message": "Una error s’es producha.", "alert.unexpected.title": "Ops !", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} per setmana", "boost_modal.combo": "Podètz botar {combo} per passar aquò lo còp que ven", "bundle_column_error.body": "Quicòm a fach mèuca pendent lo cargament d’aqueste compausant.", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 9972597d6e..3874f15963 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Ograniczony czasowo", "alert.unexpected.message": "Wystąpił nieoczekiwany błąd.", "alert.unexpected.title": "O nie!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} co tydzień", "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", "bundle_column_error.body": "Coś poszło nie tak podczas ładowania tego składnika.", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 7dedbe2cc8..f78c327fba 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Tentativas limitadas", "alert.unexpected.message": "Ocorreu um erro inesperado.", "alert.unexpected.title": "Eita!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} por semana", "boost_modal.combo": "Pressione {combo} para ignorar este diálogo na próxima vez", "bundle_column_error.body": "Ocorreu um problema ao carregar este componente.", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 72eebaa1fb..b2fb2a0128 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Limite de tentativas", "alert.unexpected.message": "Ocorreu um erro inesperado.", "alert.unexpected.title": "Bolas!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} por semana", "boost_modal.combo": "Pode clicar {combo} para não voltar a ver", "bundle_column_error.body": "Algo de errado aconteceu enquanto este componente era carregado.", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index 7703d46fbf..4d01ad3a53 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Rate limited", "alert.unexpected.message": "A apărut o eroare neașteptată.", "alert.unexpected.title": "Hopa!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} per week", "boost_modal.combo": "Poți apăsa {combo} pentru a omite asta data viitoare", "bundle_column_error.body": "Ceva nu a funcționat la încărcarea acestui component.", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index 89f3649474..1681da9681 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -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": "Что-то пошло не так при загрузке этого компонента.", @@ -365,7 +366,7 @@ "status.favourite": "Нравится", "status.filtered": "Отфильтровано", "status.load_more": "Загрузить остальное", - "status.media_hidden": "Медиа скрыто", + "status.media_hidden": "Файл скрыт", "status.mention": "Упомянуть @{name}", "status.more": "Больше", "status.mute": "Игнорировать @{name}", @@ -390,7 +391,7 @@ "status.show_more": "Развернуть", "status.show_more_all": "Развернуть все спойлеры в ветке", "status.show_thread": "Показать обсуждение", - "status.uncached_media_warning": "Недоступно", + "status.uncached_media_warning": "Файл недоступен", "status.unmute_conversation": "Не игнорировать обсуждение", "status.unpin": "Открепить от профиля", "suggestions.dismiss": "Удалить предложение", @@ -409,7 +410,7 @@ "trends.trending_now": "Самое актуальное", "ui.beforeunload": "Ваш черновик будет утерян, если вы покинете Mastodon.", "upload_area.title": "Перетащите сюда, чтобы загрузить", - "upload_button.label": "Добавить файл медиа ({formats})", + "upload_button.label": "Прикрепить фото, видео или аудио", "upload_error.limit": "Достигнут лимит загруженных файлов.", "upload_error.poll": "К опросам нельзя прикреплять файлы.", "upload_form.audio_description": "Опишите аудиофайл для людей с нарушением слуха", @@ -421,7 +422,7 @@ "upload_modal.apply": "Применить", "upload_modal.description_placeholder": "На дворе трава, на траве дрова", "upload_modal.detect_text": "Найти текст на картинке", - "upload_modal.edit_media": "Изменение медиа", + "upload_modal.edit_media": "Изменить файл", "upload_modal.hint": "Нажмите и перетащите круг в предпросмотре в точку фокуса, которая всегда будет видна на эскизах.", "upload_modal.preview_label": "Предпросмотр ({ratio})", "upload_progress.label": "Загрузка...", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index 689d6245c7..2410daf06a 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -1,7 +1,7 @@ { "account.add_or_remove_from_list": "Pridaj do, alebo odober zo zoznamov", "account.badges.bot": "Bot", - "account.badges.group": "Group", + "account.badges.group": "Skupina", "account.block": "Blokuj @{name}", "account.block_domain": "Ukry všetko z {domain}", "account.blocked": "Blokovaný/á", @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Tempo obmedzené", "alert.unexpected.message": "Vyskytla sa nečakaná chyba.", "alert.unexpected.title": "Ups!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} týždenne", "boost_modal.combo": "Nabudúce môžeš kliknúť {combo} pre preskočenie", "bundle_column_error.body": "Pri načítaní tohto prvku nastala nejaká chyba.", @@ -84,8 +85,8 @@ "compose_form.poll.duration": "Trvanie ankety", "compose_form.poll.option_placeholder": "Voľba {number}", "compose_form.poll.remove_option": "Odstráň túto voľbu", - "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": "Zmeň anketu pre povolenie viacerých možností", + "compose_form.poll.switch_to_single": "Zmeň anketu na takú s jedinou voľbou", "compose_form.publish": "Pošli", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.hide": "Označ médiá ako chúlostivé", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index da816837f5..bcf3b8d1a6 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Rate limited", "alert.unexpected.message": "Zgodila se je nepričakovana napaka.", "alert.unexpected.title": "Uups!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} per week", "boost_modal.combo": "Če želite preskočiti to, lahko pritisnete {combo}", "bundle_column_error.body": "Med nalaganjem te komponente je prišlo do napake.", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index 0b5a613c23..617c3aee36 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Rate limited", "alert.unexpected.message": "Ndodhi një gabim të papritur.", "alert.unexpected.title": "Hëm!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} per week", "boost_modal.combo": "Mund të shtypni {combo}, që të anashkalohet kjo herës tjetër", "bundle_column_error.body": "Diç shkoi ters teksa ngarkohej ky përbërës.", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index aab2dfa15e..09b30ff5f0 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -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žete pritisnuti {combo} da preskočite ovo sledeći put", "bundle_column_error.body": "Nešto je pošlo po zlu prilikom učitavanja ove komponente.", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 35436a93d7..5b04f9826d 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -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": "Нешто је пошло по злу приликом учитавања ове компоненте.", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 843ab9fc09..4e778a481f 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -1,7 +1,7 @@ { "account.add_or_remove_from_list": "Lägg till i eller ta bort från listor", "account.badges.bot": "Robot", - "account.badges.group": "Group", + "account.badges.group": "Grupp", "account.block": "Blockera @{name}", "account.block_domain": "Dölj allt från {domain}", "account.blocked": "Blockerad", @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Mängd begränsad", "alert.unexpected.message": "Ett oväntat fel uppstod.", "alert.unexpected.title": "Hoppsan!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{count} per vecka", "boost_modal.combo": "Du kan trycka {combo} för att slippa detta nästa gång", "bundle_column_error.body": "Något gick fel medan denna komponent laddades.", @@ -84,8 +85,8 @@ "compose_form.poll.duration": "Varaktighet för omröstning", "compose_form.poll.option_placeholder": "Val {number}", "compose_form.poll.remove_option": "Ta bort detta val", - "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": "Ändra enkät för att tillåta flera val", + "compose_form.poll.switch_to_single": "Ändra enkät för att tillåta ett enda val", "compose_form.publish": "Tut", "compose_form.publish_loud": "{publish}!", "compose_form.sensitive.hide": "Markera media som känsligt", diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json index ddd1d9fd72..8d1a8f14d2 100644 --- a/app/javascript/mastodon/locales/ta.json +++ b/app/javascript/mastodon/locales/ta.json @@ -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": "இக்கூற்றை ஏற்றம் செய்யும்பொழுது ஏதோ தவறு ஏற்பட்டுள்ளது.", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index 12af667c73..8b47fec522 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -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": "ఈ భాగం లోడ్ అవుతున్నప్పుడు ఏదో తప్పు జరిగింది.", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 56e383da79..8a72783a28 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -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": "ทำเครื่องหมายสื่อว่าละเอียดอ่อน", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index 514e7b956c..3ade929779 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Oran sınırlıdır", "alert.unexpected.message": "Beklenmedik bir hata oluştu.", "alert.unexpected.title": "Hay aksi!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "Haftada {count}", "boost_modal.combo": "Bir daha ki sefere {combo} tuşuna basabilirsiniz", "bundle_column_error.body": "Bu bileşen yüklenirken bir şeyler ters gitti.", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 1c37f8e443..3dc69d6d64 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -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": "Щось пішло не так під час завантаження компоненту.", diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index 6f14ad3c66..01477906ce 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -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": "اس عنصر کو برآمد کرتے وقت کچھ خرابی پیش آئی ہے.", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index d94be9f5bb..07dff79fa4 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -43,6 +43,7 @@ "alert.rate_limited.title": "Tỷ lệ giới hạn", "alert.unexpected.message": "Đã xảy ra lỗi không mong muốn.", "alert.unexpected.title": "Ốiii!", + "announcement.announcement": "Announcement", "autosuggest_hashtag.per_week": "{tính} mỗi tuần", "boost_modal.combo": "Bạn có thể nhấn {combo} để bỏ qua lần sau", "bundle_column_error.body": "Có gì đó sai sai trong khi tải nội dung này", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 84670d4b56..62a86e75b7 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -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": "载入这个组件时发生了错误。", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index a685409eea..4dcf70e6b1 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -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": "加載本組件出錯。", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index e5f740b462..ab680223eb 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -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": "載入此元件時發生錯誤。", diff --git a/app/javascript/mastodon/reducers/announcements.js b/app/javascript/mastodon/reducers/announcements.js new file mode 100644 index 0000000000..aa674e516f --- /dev/null +++ b/app/javascript/mastodon/reducers/announcements.js @@ -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; + } +}; diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js index b8d6088881..b9817cd384 100644 --- a/app/javascript/mastodon/reducers/index.js +++ b/app/javascript/mastodon/reducers/index.js @@ -34,8 +34,10 @@ import polls from './polls'; import identity_proofs from './identity_proofs'; import trends from './trends'; import missed_updates from './missed_updates'; +import announcements from './announcements'; const reducers = { + announcements, dropdown_menu, timelines, meta, diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 6f1ce9602a..6a48f3b3f4 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -26,6 +26,7 @@ const toServerSideType = columnType => { case 'notifications': case 'public': case 'thread': + case 'account': return columnType; default: if (columnType.indexOf('list:') > -1) { diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index 50f90d44cb..fe965bcb0e 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -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; }; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index ccfce14752..8d0a070d53 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -859,6 +859,44 @@ } } +.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; + } + } + } + } +} + .status__content.status__content--collapsed { max-height: 20px * 15; // 15 lines is roughly above 500 characters } @@ -6581,3 +6619,178 @@ noscript { } } } + +.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; + } + } +} diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index 8965ce6751..65cefbd7c5 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -222,6 +222,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%); diff --git a/app/lib/entity_cache.rb b/app/lib/entity_cache.rb index 8fff544a05..35a3773d2d 100644 --- a/app/lib/entity_cache.rb +++ b/app/lib/entity_cache.rb @@ -8,7 +8,7 @@ class EntityCache MAX_EXPIRATION = 7.days.freeze def mention(username, domain) - Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:username, :domain, :url).find_remote(username, domain) } + Rails.cache.fetch(to_key(:mention, username, domain), expires_in: MAX_EXPIRATION) { Account.select(:id, :username, :domain, :url).find_remote(username, domain) } end def emoji(shortcodes, domain) diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb index 36cdae9f7e..f1a751f84b 100644 --- a/app/lib/formatter.rb +++ b/app/lib/formatter.rb @@ -308,9 +308,9 @@ class Formatter end standard = Extractor.extract_entities_with_indices(text, options) - xmpp = Extractor.extract_xmpp_uris_with_indices(text, options) + extra = Extractor.extract_extra_uris_with_indices(text, options) - Extractor.remove_overlapping_entities(special + standard + xmpp) + Extractor.remove_overlapping_entities(special + standard + extra) end def html_friendly_extractor(html, options = {}) diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb index 761a8822df..27e334a4de 100644 --- a/app/lib/inline_renderer.rb +++ b/app/lib/inline_renderer.rb @@ -15,6 +15,10 @@ class InlineRenderer serializer = REST::NotificationSerializer when :conversation serializer = REST::ConversationSerializer + when :announcement + serializer = REST::AnnouncementSerializer + when :reaction + serializer = REST::ReactionSerializer else return end diff --git a/app/lib/sanitize_config.rb b/app/lib/sanitize_config.rb index 3554d34b97..2b5d554b5a 100644 --- a/app/lib/sanitize_config.rb +++ b/app/lib/sanitize_config.rb @@ -2,7 +2,7 @@ class Sanitize module Config - HTTP_PROTOCOLS ||= ['http', 'https', 'dat', 'dweb', 'ipfs', 'ipns', 'ssb', 'gopher', 'xmpp', :relative].freeze + HTTP_PROTOCOLS ||= ['http', 'https', 'dat', 'dweb', 'ipfs', 'ipns', 'ssb', 'gopher', 'xmpp', 'magnet', :relative].freeze CLASS_WHITELIST_TRANSFORMER = lambda do |env| node = env[:node] diff --git a/app/middleware/handle_bad_encoding_middleware.rb b/app/middleware/handle_bad_encoding_middleware.rb deleted file mode 100644 index 6fce84b152..0000000000 --- a/app/middleware/handle_bad_encoding_middleware.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true -# See: https://jamescrisp.org/2018/05/28/fixing-invalid-query-parameters-invalid-encoding-in-a-rails-app/ - -class HandleBadEncodingMiddleware - def initialize(app) - @app = app - end - - def call(env) - begin - Rack::Utils.parse_nested_query(env['QUERY_STRING'].to_s) - rescue Rack::Utils::InvalidParameterError - env['QUERY_STRING'] = '' - end - - @app.call(env) - end -end diff --git a/app/models/account.rb b/app/models/account.rb index 9b93616708..b856d1c766 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -478,6 +478,12 @@ class Account < ApplicationRecord records end + def from_text(text) + return [] if text.blank? + + text.scan(MENTION_RE).map { |match| match.first.split('@', 2) }.uniq.map { |(username, domain)| EntityCache.instance.mention(username, domain) } + end + private def generate_query_for_search(terms) diff --git a/app/models/announcement.rb b/app/models/announcement.rb new file mode 100644 index 0000000000..4da9f94d6d --- /dev/null +++ b/app/models/announcement.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: announcements +# +# id :bigint(8) not null, primary key +# text :text default(""), not null +# published :boolean default(FALSE), not null +# all_day :boolean default(FALSE), not null +# scheduled_at :datetime +# starts_at :datetime +# ends_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# + +class Announcement < ApplicationRecord + after_commit :queue_publish, on: :create + + scope :unpublished, -> { where(published: false) } + scope :published, -> { where(published: true) } + scope :without_muted, ->(account) { joins("LEFT OUTER JOIN announcement_mutes ON announcement_mutes.announcement_id = announcements.id AND announcement_mutes.account_id = #{account.id}").where('announcement_mutes.id IS NULL') } + scope :chronological, -> { order(Arel.sql('COALESCE(announcements.starts_at, announcements.scheduled_at, announcements.created_at) ASC')) } + + has_many :announcement_mutes, dependent: :destroy + has_many :announcement_reactions, dependent: :destroy + + validates :text, presence: true + validates :starts_at, presence: true, if: -> { ends_at.present? } + validates :ends_at, presence: true, if: -> { starts_at.present? } + + before_validation :set_all_day + before_validation :set_starts_at, on: :create + before_validation :set_ends_at, on: :create + + def time_range? + starts_at.present? && ends_at.present? + end + + def mentions + @mentions ||= Account.from_text(text) + end + + def tags + @tags ||= Tag.find_or_create_by_names(Extractor.extract_hashtags(text)) + end + + def emojis + @emojis ||= CustomEmoji.from_text(text) + end + + def reactions(account = nil) + records = begin + scope = announcement_reactions.group(:announcement_id, :name, :custom_emoji_id).order(Arel.sql('MIN(created_at) ASC')) + + if account.nil? + scope.select('name, custom_emoji_id, count(*) as count, false as me') + else + scope.select("name, custom_emoji_id, count(*) as count, exists(select 1 from announcement_reactions r where r.account_id = #{account.id} and r.announcement_id = announcement_reactions.announcement_id and r.name = announcement_reactions.name) as me") + end + end + + ActiveRecord::Associations::Preloader.new.preload(records, :custom_emoji) + records + end + + private + + def set_all_day + self.all_day = false if starts_at.blank? || ends_at.blank? + end + + def set_starts_at + self.starts_at = starts_at.change(hour: 0, min: 0, sec: 0) if all_day? && starts_at.present? + end + + def set_ends_at + self.ends_at = ends_at.change(hour: 23, min: 59, sec: 59) if all_day? && ends_at.present? + end + + def queue_publish + PublishScheduledAnnouncementWorker.perform_async(id) if scheduled_at.blank? + end +end diff --git a/app/models/announcement_filter.rb b/app/models/announcement_filter.rb new file mode 100644 index 0000000000..950852460d --- /dev/null +++ b/app/models/announcement_filter.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class AnnouncementFilter + KEYS = %i( + published + unpublished + ).freeze + + attr_reader :params + + def initialize(params) + @params = params + end + + def results + scope = Announcement.unscoped + + params.each do |key, value| + next if key.to_s == 'page' + + scope.merge!(scope_for(key, value.to_s.strip)) if value.present? + end + + scope.chronological + end + + private + + def scope_for(key, _value) + case key.to_s + when 'published' + Announcement.published + when 'unpublished' + Announcement.unpublished + else + raise "Unknown filter: #{key}" + end + end +end diff --git a/app/models/announcement_mute.rb b/app/models/announcement_mute.rb new file mode 100644 index 0000000000..46fda2f5d6 --- /dev/null +++ b/app/models/announcement_mute.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: announcement_mutes +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# announcement_id :bigint(8) +# created_at :datetime not null +# updated_at :datetime not null +# + +class AnnouncementMute < ApplicationRecord + belongs_to :account + belongs_to :announcement, inverse_of: :announcement_mutes + + validates :account_id, uniqueness: { scope: :announcement_id } +end diff --git a/app/models/announcement_reaction.rb b/app/models/announcement_reaction.rb new file mode 100644 index 0000000000..d22771034f --- /dev/null +++ b/app/models/announcement_reaction.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: announcement_reactions +# +# id :bigint(8) not null, primary key +# account_id :bigint(8) +# announcement_id :bigint(8) +# name :string default(""), not null +# custom_emoji_id :bigint(8) +# created_at :datetime not null +# updated_at :datetime not null +# + +class AnnouncementReaction < ApplicationRecord + after_commit :queue_publish + + belongs_to :account + belongs_to :announcement, inverse_of: :announcement_reactions + belongs_to :custom_emoji, optional: true + + validates :name, presence: true + validates_with ReactionValidator + + before_validation :set_custom_emoji + + private + + def set_custom_emoji + self.custom_emoji = CustomEmoji.local.find_by(disabled: false, shortcode: name) if name.present? + end + + def queue_publish + PublishAnnouncementReactionWorker.perform_async(announcement_id, name) unless announcement.destroyed? + end +end diff --git a/app/models/backup.rb b/app/models/backup.rb index 8eeb1748aa..d242fd62c1 100644 --- a/app/models/backup.rb +++ b/app/models/backup.rb @@ -7,11 +7,11 @@ # user_id :bigint(8) # dump_file_name :string # dump_content_type :string -# dump_file_size :bigint # dump_updated_at :datetime # processed :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null +# dump_file_size :bigint(8) # class Backup < ApplicationRecord diff --git a/app/models/bookmark.rb b/app/models/bookmark.rb index 01dc48ee70..916261a17b 100644 --- a/app/models/bookmark.rb +++ b/app/models/bookmark.rb @@ -3,11 +3,11 @@ # # Table name: bookmarks # -# id :integer not null, primary key +# id :bigint(8) not null, primary key +# account_id :bigint(8) not null +# status_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null -# account_id :integer not null -# status_id :integer not null # class Bookmark < ApplicationRecord diff --git a/app/models/concerns/account_interactions.rb b/app/models/concerns/account_interactions.rb index f27d39483a..14bcf7bb10 100644 --- a/app/models/concerns/account_interactions.rb +++ b/app/models/concerns/account_interactions.rb @@ -84,6 +84,7 @@ module AccountInteractions has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account has_many :conversation_mutes, dependent: :destroy has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy + has_many :announcement_mutes, dependent: :destroy end def follow!(other_account, reblogs: nil, uri: nil) diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 0dacaf654b..d177cf2815 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -67,7 +67,7 @@ class CustomEmoji < ApplicationRecord end class << self - def from_text(text, domain) + def from_text(text, domain = nil) return [] if text.blank? shortcodes = text.scan(SCAN_RE).map(&:first).uniq diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 382562fb84..8df8a4fbf3 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -20,6 +20,7 @@ class CustomFilter < ApplicationRecord notifications public thread + account ).freeze include Expireable diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index b87b1b9d33..6a0b892f62 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -142,6 +142,7 @@ class MediaAttachment < ApplicationRecord validates :account, presence: true validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local? + validates :file, presence: true, if: :local? scope :attached, -> { where.not(status_id: nil).or(where.not(scheduled_status_id: nil)) } scope :unattached, -> { where(status_id: nil, scheduled_status_id: nil) } diff --git a/app/models/relationship_filter.rb b/app/models/relationship_filter.rb index 51640f494c..e6859bf3dd 100644 --- a/app/models/relationship_filter.rb +++ b/app/models/relationship_filter.rb @@ -7,5 +7,114 @@ class RelationshipFilter by_domain activity order + location ).freeze + + attr_reader :params, :account + + def initialize(account, params) + @account = account + @params = params + + set_defaults! + end + + def results + scope = scope_for('relationship', params['relationship'].to_s.strip) + + params.each do |key, value| + next if key.to_s == 'page' + + scope.merge!(scope_for(key.to_s, value.to_s.strip)) if value.present? + end + + scope + end + + private + + def set_defaults! + params['relationship'] = 'following' if params['relationship'].blank? + params['order'] = 'recent' if params['order'].blank? + end + + def scope_for(key, value) + case key + when 'relationship' + relationship_scope(value) + when 'by_domain' + by_domain_scope(value) + when 'location' + location_scope(value) + when 'status' + status_scope(value) + when 'order' + order_scope(value) + when 'activity' + activity_scope(value) + else + raise "Unknown filter: #{key}" + end + end + + def relationship_scope(value) + case value + when 'following' + account.following.eager_load(:account_stat).reorder(nil) + when 'followed_by' + account.followers.eager_load(:account_stat).reorder(nil) + when 'mutual' + account.followers.eager_load(:account_stat).reorder(nil).merge(Account.where(id: account.following)) + when 'invited' + Account.joins(user: :invite).merge(Invite.where(user: account.user)).eager_load(:account_stat).reorder(nil) + else + raise "Unknown relationship: #{value}" + end + end + + def by_domain_scope(value) + Account.where(domain: value) + end + + def location_scope(value) + case value + when 'local' + Account.local + when 'remote' + Account.remote + else + raise "Unknown location: #{value}" + end + end + + def status_scope(value) + case value + when 'moved' + Account.where.not(moved_to_account_id: nil) + when 'primary' + Account.where(moved_to_account_id: nil) + else + raise "Unknown status: #{value}" + end + end + + def order_scope(value) + case value + when 'active' + Account.by_recent_status + when 'recent' + params[:relationship] == 'invited' ? Account.recent : Follow.recent + else + raise "Unknown order: #{value}" + end + end + + def activity_scope(value) + case value + when 'dormant' + AccountStat.where(last_status_at: nil).or(AccountStat.where(AccountStat.arel_table[:last_status_at].lt(1.month.ago))) + else + raise "Unknown activity: #{value}" + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index 794c2091c7..9d5114e742 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -248,7 +248,7 @@ class User < ApplicationRecord ip: request.remote_ip).session_id end - def exclusive_session(id) + def clear_other_sessions(id) session_activations.exclusive(id) end diff --git a/app/policies/announcement_policy.rb b/app/policies/announcement_policy.rb new file mode 100644 index 0000000000..0a4e4575ca --- /dev/null +++ b/app/policies/announcement_policy.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AnnouncementPolicy < ApplicationPolicy + def index? + staff? + end + + def create? + admin? + end + + def update? + admin? + end + + def destroy? + admin? + end +end diff --git a/app/serializers/rest/announcement_serializer.rb b/app/serializers/rest/announcement_serializer.rb new file mode 100644 index 0000000000..924d87b341 --- /dev/null +++ b/app/serializers/rest/announcement_serializer.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +class REST::AnnouncementSerializer < ActiveModel::Serializer + attributes :id, :content, :starts_at, :ends_at, :all_day + + has_many :mentions + has_many :tags, serializer: REST::StatusSerializer::TagSerializer + has_many :emojis, serializer: REST::CustomEmojiSerializer + has_many :reactions, serializer: REST::ReactionSerializer + + def id + object.id.to_s + end + + def content + Formatter.instance.linkify(object.text) + end + + def reactions + object.reactions(current_user&.account) + end + + class AccountSerializer < ActiveModel::Serializer + attributes :id, :username, :url, :acct + + def id + object.id.to_s + end + + def url + ActivityPub::TagManager.instance.url_for(object) + end + end +end diff --git a/app/serializers/rest/reaction_serializer.rb b/app/serializers/rest/reaction_serializer.rb new file mode 100644 index 0000000000..1a5dca0183 --- /dev/null +++ b/app/serializers/rest/reaction_serializer.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class REST::ReactionSerializer < ActiveModel::Serializer + include RoutingHelper + + attributes :name, :count + + attribute :me, if: :current_user? + attribute :url, if: :custom_emoji? + attribute :static_url, if: :custom_emoji? + + def count + object.respond_to?(:count) ? object.count : 0 + end + + def current_user? + !current_user.nil? + end + + def custom_emoji? + object.custom_emoji.present? + end + + def url + full_asset_url(object.custom_emoji.image.url) + end + + def static_url + full_asset_url(object.custom_emoji.image.url(:static)) + end +end diff --git a/app/validators/reaction_validator.rb b/app/validators/reaction_validator.rb new file mode 100644 index 0000000000..de0f2c94b9 --- /dev/null +++ b/app/validators/reaction_validator.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class ReactionValidator < ActiveModel::Validator + SUPPORTED_EMOJIS = Oj.load(File.read(Rails.root.join('app', 'javascript', 'mastodon', 'features', 'emoji', 'emoji_map.json'))).keys.freeze + + def validate(reaction) + return if reaction.name.blank? || reaction.custom_emoji_id.present? + + reaction.errors.add(:name, I18n.t('reactions.errors.unrecognized_emoji')) unless unicode_emoji?(reaction.name) + end + + private + + def unicode_emoji?(name) + SUPPORTED_EMOJIS.include?(name) + end +end diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index 9c26dbabce..c312fe2bdd 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -2,8 +2,6 @@ = "#{display_name(@account)} (@#{@account.local_username_and_domain})" - content_for :header_tags do - %meta{ name: 'description', content: account_description(@account) }/ - - if @account.user&.setting_noindex %meta{ name: 'robots', content: 'noindex, noarchive' }/ diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 1429f56d56..a83f771340 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -41,7 +41,7 @@ .dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size') .dashboard__counters__label= t 'admin.accounts.media_attachments' %div - = link_to admin_account_followers_path(@account.id) do + = link_to admin_account_relationships_path(@account.id, location: 'local', relationship: 'followed_by') do .dashboard__counters__num= number_with_delimiter @account.local_followers_count .dashboard__counters__label= t 'admin.accounts.followers' %div diff --git a/app/views/admin/announcements/_announcement.html.haml b/app/views/admin/announcements/_announcement.html.haml new file mode 100644 index 0000000000..75768c7ba2 --- /dev/null +++ b/app/views/admin/announcements/_announcement.html.haml @@ -0,0 +1,14 @@ +%tr + %td + = truncate(announcement.text) + %td + = time_range(announcement) if announcement.time_range? + %td + - if announcement.scheduled_at.present? + = fa_icon('clock-o') if announcement.scheduled_at > Time.now.utc + = l(announcement.scheduled_at) + - else + = l(announcement.created_at) + %td + = table_link_to 'pencil', t('generic.edit'), edit_admin_announcement_path(announcement) if can?(:update, announcement) + = table_link_to 'trash', t('generic.delete'), admin_announcement_path(announcement), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, announcement) diff --git a/app/views/admin/announcements/edit.html.haml b/app/views/admin/announcements/edit.html.haml new file mode 100644 index 0000000000..c5c605e939 --- /dev/null +++ b/app/views/admin/announcements/edit.html.haml @@ -0,0 +1,22 @@ +- content_for :page_title do + = t('.title') + += simple_form_for @announcement, url: admin_announcement_path(@announcement) do |f| + = render 'shared/error_messages', object: @announcement + + .fields-group + = f.input :starts_at, include_blank: true, wrapper: :with_block_label + = f.input :ends_at, include_blank: true, wrapper: :with_block_label + + .fields-group + = f.input :all_day, as: :boolean, wrapper: :with_label + + .fields-group + = f.input :text, wrapper: :with_block_label + + - if @announcement.scheduled_at.present? && !@announcement.published? + .fields-group + = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label + + .actions + = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/announcements/index.html.haml b/app/views/admin/announcements/index.html.haml new file mode 100644 index 0000000000..634f586fbc --- /dev/null +++ b/app/views/admin/announcements/index.html.haml @@ -0,0 +1,30 @@ +- content_for :page_title do + = t('admin.announcements.title') + +- content_for :heading_actions do + = link_to t('admin.announcements.new.title'), new_admin_announcement_path, class: 'button' + +.filters + .filter-subset + %strong= t('admin.relays.status') + %ul + %li= filter_link_to t('generic.all'), published: nil, unpublished: nil + %li= filter_link_to safe_join([t('admin.announcements.live'), "(#{number_with_delimiter(Announcement.published.count)})"], ' '), published: '1', unpublished: nil + +- if @announcements.empty? + %div.muted-hint.center-text + = t 'admin.announcements.empty' +- else + .table-wrapper + %table.table + %thead + %tr + %th= t('simple_form.labels.announcement.text') + %th= t('admin.announcements.time_range') + %th= t('admin.announcements.published') + %th + %tbody + = render partial: 'announcement', collection: @announcements + += paginate @announcements + diff --git a/app/views/admin/announcements/new.html.haml b/app/views/admin/announcements/new.html.haml new file mode 100644 index 0000000000..a5298c5f66 --- /dev/null +++ b/app/views/admin/announcements/new.html.haml @@ -0,0 +1,21 @@ +- content_for :page_title do + = t('.title') + += simple_form_for @announcement, url: admin_announcements_path do |f| + = render 'shared/error_messages', object: @announcement + + .fields-group + = f.input :starts_at, include_blank: true, wrapper: :with_block_label + = f.input :ends_at, include_blank: true, wrapper: :with_block_label + + .fields-group + = f.input :all_day, as: :boolean, wrapper: :with_label + + .fields-group + = f.input :text, wrapper: :with_block_label + + .fields-group + = f.input :scheduled_at, include_blank: true, wrapper: :with_block_label + + .actions + = f.button :button, t('.create'), type: :submit diff --git a/app/views/admin/followers/index.html.haml b/app/views/admin/followers/index.html.haml deleted file mode 100644 index 25f1f290f4..0000000000 --- a/app/views/admin/followers/index.html.haml +++ /dev/null @@ -1,28 +0,0 @@ -- content_for :page_title do - = t('admin.followers.title', acct: @account.acct) - -.filters - .filter-subset - %strong= t('admin.accounts.location.title') - %ul - %li= link_to t('admin.accounts.location.local'), admin_account_followers_path(@account.id), class: 'selected' - .back-link{ style: 'flex: 1 1 auto; text-align: right' } - = link_to admin_account_path(@account.id) do - = fa_icon 'chevron-left fw' - = t('admin.followers.back_to_account') - -%hr.spacer/ - -.table-wrapper - %table.table - %thead - %tr - %th= t('admin.accounts.username') - %th= t('admin.accounts.role') - %th= t('admin.accounts.most_recent_ip') - %th= t('admin.accounts.most_recent_activity') - %th - %tbody - = render partial: 'admin/accounts/account', collection: @followers - -= paginate @followers diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml new file mode 100644 index 0000000000..3afaff6153 --- /dev/null +++ b/app/views/admin/relationships/index.html.haml @@ -0,0 +1,39 @@ +- content_for :page_title do + = t('admin.relationships.title', acct: @account.acct) + +.filters + .filter-subset + %strong= t 'relationships.relationship' + %ul + %li= filter_link_to t('relationships.following'), relationship: nil + %li= filter_link_to t('relationships.followers'), relationship: 'followed_by' + %li= filter_link_to t('relationships.mutual'), relationship: 'mutual' + %li= filter_link_to t('relationships.invited'), relationship: 'invited' + + .filter-subset + %strong= t('admin.accounts.location.title') + %ul + %li= filter_link_to t('admin.accounts.moderation.all'), location: nil + %li= filter_link_to t('admin.accounts.location.local'), location: 'local' + %li= filter_link_to t('admin.accounts.location.remote'), location: 'remote' + + .back-link{ style: 'flex: 1 1 auto; text-align: right' } + = link_to admin_account_path(@account.id) do + = fa_icon 'chevron-left fw' + = t('admin.statuses.back_to_account') + +%hr.spacer/ + +.table-wrapper + %table.table + %thead + %tr + %th= t('admin.accounts.username') + %th= t('admin.accounts.role') + %th= t('admin.accounts.most_recent_ip') + %th= t('admin.accounts.most_recent_activity') + %th + %tbody + = render partial: 'admin/accounts/account', collection: @accounts + += paginate @accounts diff --git a/app/workers/publish_announcement_reaction_worker.rb b/app/workers/publish_announcement_reaction_worker.rb new file mode 100644 index 0000000000..6f3b6dc5b6 --- /dev/null +++ b/app/workers/publish_announcement_reaction_worker.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class PublishAnnouncementReactionWorker + include Sidekiq::Worker + include Redisable + + def perform(announcement_id, name) + announcement = Announcement.find(announcement_id) + + reaction, = announcement.announcement_reactions.where(name: name).group(:announcement_id, :name, :custom_emoji_id).select('name, custom_emoji_id, count(*) as count, false as me') + reaction ||= announcement.announcement_reactions.new(name: name) + + payload = InlineRenderer.render(reaction, nil, :reaction).tap { |h| h[:announcement_id] = announcement_id } + payload = Oj.dump(event: :'announcement.reaction', payload: payload) + + Account.joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).find_each do |account| + redis.publish("timeline:#{account.id}", payload) if redis.exists("subscribed:timeline:#{account.id}") + end + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/app/workers/publish_scheduled_announcement_worker.rb b/app/workers/publish_scheduled_announcement_worker.rb new file mode 100644 index 0000000000..4b2014e34a --- /dev/null +++ b/app/workers/publish_scheduled_announcement_worker.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class PublishScheduledAnnouncementWorker + include Sidekiq::Worker + include Redisable + + def perform(announcement_id) + announcement = Announcement.find(announcement_id) + announcement.update(published: true) + + payload = InlineRenderer.render(announcement, nil, :announcement) + payload = Oj.dump(event: :announcement, payload: payload) + + Account.joins(:user).where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago).find_each do |account| + redis.publish("timeline:#{account.id}", payload) if redis.exists("subscribed:timeline:#{account.id}") + end + end +end diff --git a/app/workers/scheduler/scheduled_statuses_scheduler.rb b/app/workers/scheduler/scheduled_statuses_scheduler.rb index 1772a246bc..4262f1d01b 100644 --- a/app/workers/scheduler/scheduled_statuses_scheduler.rb +++ b/app/workers/scheduler/scheduled_statuses_scheduler.rb @@ -6,14 +6,38 @@ class Scheduler::ScheduledStatusesScheduler sidekiq_options unique: :until_executed, retry: 0 def perform + publish_scheduled_statuses! + publish_scheduled_announcements! + unpublish_expired_announcements! + end + + private + + def publish_scheduled_statuses! due_statuses.find_each do |scheduled_status| PublishScheduledStatusWorker.perform_at(scheduled_status.scheduled_at, scheduled_status.id) end end - private - def due_statuses ScheduledStatus.where('scheduled_at <= ?', Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET) end + + def publish_scheduled_announcements! + due_announcements.find_each do |announcement| + PublishScheduledAnnouncementWorker.perform_at(announcement.scheduled_at, announcement.id) + end + end + + def due_announcements + Announcement.unpublished.where('scheduled_at IS NOT NULL AND scheduled_at <= ?', Time.now.utc + PostStatusService::MIN_SCHEDULE_OFFSET) + end + + def unpublish_expired_announcements! + expired_announcements.in_batches.update_all(published: false) + end + + def expired_announcements + Announcement.published.where('ends_at IS NOT NULL AND ends_at <= ?', Time.now.utc) + end end diff --git a/config/application.rb b/config/application.rb index c1bc18a303..bf25fa0d9e 100644 --- a/config/application.rb +++ b/config/application.rb @@ -7,7 +7,6 @@ require 'rails/all' Bundler.require(*Rails.groups) require_relative '../app/lib/exceptions' -require_relative '../app/middleware/handle_bad_encoding_middleware' require_relative '../lib/paperclip/lazy_thumbnail' require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/video_transcoder' @@ -120,7 +119,6 @@ module Mastodon config.active_job.queue_adapter = :sidekiq - config.middleware.insert_before Rack::Runtime, HandleBadEncodingMiddleware config.middleware.use Rack::Attack config.middleware.use Rack::Deflater diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb index 9645268195..3dc48ef08f 100644 --- a/config/initializers/simple_form.rb +++ b/config/initializers/simple_form.rb @@ -98,7 +98,7 @@ SimpleForm.setup do |config| b.use :html5 b.use :label b.use :hint, wrap_with: { tag: :span, class: :hint } - b.use :input + b.use :input, wrap_with: { tag: :div, class: :label_input } b.use :error, wrap_with: { tag: :span, class: :error } end diff --git a/config/initializers/twitter_regex.rb b/config/initializers/twitter_regex.rb index 87815d4580..f84f7c0cbb 100644 --- a/config/initializers/twitter_regex.rb +++ b/config/initializers/twitter_regex.rb @@ -47,32 +47,39 @@ module Twitter #{REGEXEN[:validate_url_pct_encoded]}| #{REGEXEN[:validate_url_sub_delims]} )/iox - REGEXEN[:valid_xmpp_uri] = %r{ - ( # $1 total match - (#{REGEXEN[:valid_url_preceding_chars]}) # $2 Preceding character - ( # $3 URL - ((?:xmpp):) # $4 Protocol - (//#{REGEXEN[:validate_nodeid]}+@#{REGEXEN[:valid_domain]}/)? # $5 Authority (optional) - (#{REGEXEN[:validate_nodeid]}+@)? # $6 Username in path (optional) - (#{REGEXEN[:valid_domain]}) # $7 Domain in path - (/#{REGEXEN[:validate_resid]}+)? # $8 Resource in path (optional) - (\?#{REGEXEN[:valid_url_query_chars]}*#{REGEXEN[:valid_url_query_ending_chars]})? # $9 Query String + REGEXEN[:xmpp_uri] = %r{ + (xmpp:) # Protocol + (//#{REGEXEN[:validate_nodeid]}+@#{REGEXEN[:valid_domain]}/)? # Authority (optional) + (#{REGEXEN[:validate_nodeid]}+@)? # Username in path (optional) + (#{REGEXEN[:valid_domain]}) # Domain in path + (/#{REGEXEN[:validate_resid]}+)? # Resource in path (optional) + (\?#{REGEXEN[:valid_url_query_chars]}*#{REGEXEN[:valid_url_query_ending_chars]})? # Query String + }iox + REGEXEN[:magnet_uri] = %r{ + (magnet:) # Protocol + (\?#{REGEXEN[:valid_url_query_chars]}*#{REGEXEN[:valid_url_query_ending_chars]}) # Query String + }iox + REGEXEN[:valid_extended_uri] = %r{ + ( # $1 total match + (#{REGEXEN[:valid_url_preceding_chars]}) # $2 Preceding character + ( # $3 URL + (#{REGEXEN[:xmpp_uri]}) | (#{REGEXEN[:magnet_uri]}) ) ) }iox end module Extractor - # Extracts a list of all XMPP URIs included in the Tweet text along + # Extracts a list of all XMPP and magnet URIs included in the Toot text along # with the indices. If the text is nil or contains no - # XMPP URIs an empty array will be returned. + # XMPP or magnet URIs an empty array will be returned. # # If a block is given then it will be called for each XMPP URI. - def extract_xmpp_uris_with_indices(text, options = {}) # :yields: uri, start, end + def extract_extra_uris_with_indices(text, options = {}) # :yields: uri, start, end return [] unless text && text.index(":") urls = [] - text.to_s.scan(Twitter::Regex[:valid_xmpp_uri]) do + text.to_s.scan(Twitter::Regex[:valid_extended_uri]) do valid_uri_match_data = $~ start_position = valid_uri_match_data.char_begin(3) diff --git a/config/locales/ar.yml b/config/locales/ar.yml index 1519be44bf..7309ad3db4 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -86,6 +86,7 @@ ar: roles: admin: المدير bot: روبوت + group: فريق moderator: مُشرِف unavailable: الصفحة التعريفية غير متوفرة unfollow: إلغاء المتابعة @@ -262,6 +263,7 @@ ar: shortcode_hint: على الأقل حرفين، و فقط رموز أبجدية عددية و أسطر سفلية title: الإيموجي الخاصة uncategorized: غير مصنّف + unlist: تنحية مِن القائمة unlisted: غير مدرج update_failed_msg: تعذرت عملية تحديث ذاك الإيموجي updated_msg: تم تحديث الإيموجي بنجاح! @@ -347,9 +349,6 @@ ar: create: إضافة نطاق title: إضافة نطاق بريد جديد إلى اللائحة السوداء title: القائمة السوداء للبريد الإلكتروني - followers: - back_to_account: العودة إلى الحساب - title: "%{acct} مُتابِعون" instances: by_domain: النطاق delivery_available: التسليم متوفر @@ -612,6 +611,7 @@ ar: set_new_password: إدخال كلمة مرور جديدة setup: email_below_hint_html: إذا كان عنوان البريد الإلكتروني التالي غير صحيح، فيمكنك تغييره هنا واستلام بريد إلكتروني جديد للتأكيد. + email_settings_hint_html: لقد تم إرسال رسالة بريد إلكترونية للتأكيد إلى %{email}. إن كان عنوان البريد الإلكتروني غير صحيح ، يمكنك تغييره في إعدادات حسابك. title: الضبط status: account_status: حالة الحساب @@ -659,6 +659,7 @@ ar: before: 'يرجى قراءة هذه الملاحظات بتأنّي قبل المواصلة:' data_removal: سوف تُحذَف منشوراتك والبيانات الأخرى نهائيا email_change_html: بإمكانك تغيير عنوان بريدك الإلكتروني دون أن يُحذف حسابك + email_contact_html: إن لم تتلقّ أي شيء ، يمكنك مراسلة %{email} لطلب المساعدة irreversible: لن يكون بإمكانك استرجاع أو إعادة تنشيط حسابك more_details_html: للمزيد مِن التفاصيل ، يرجى الإطلاع على سياسة الخصوصية. username_available: سيصبح اسم مستخدمك متوفرا ثانية @@ -716,6 +717,7 @@ ar: invalid_irreversible: إلّا مجالات الإشعارات و الخيط الرئيسي معنية بالتصفية اللارجعية index: delete: إزالة + empty: ليست لديك أية عوامل تصفية. title: عوامل التصفية new: title: إضافة عامل تصفية جديد @@ -815,6 +817,7 @@ ar: set_redirect: تعين إعادة التوجيه warning: before: 'يرجى قراءة هذه الملاحظات بتأنّي قبل المواصلة:' + followers: تقوم هذه العملية بنقل كافة المُتابِعين مِن الحساب الحالي إلى الحساب الجديد other_data: لن يتم نقل أية بيانات أخرى تلقائيا moderation: title: الإشراف @@ -887,6 +890,7 @@ ar: duration_too_long: بعيد جدا في المستقبَل duration_too_short: مبكّر جدا expired: لقد انتهى استطلاع الرأي + invalid_choice: خيار التصويت الذي قُمتَ يتحديده غير موجود too_many_options: لا يمكنه أن يحتوي أكثر مِن %{max} عناصر preferences: other: إعدادات أخرى diff --git a/config/locales/ast.yml b/config/locales/ast.yml index 6c757558ef..d64f6b0052 100644 --- a/config/locales/ast.yml +++ b/config/locales/ast.yml @@ -305,6 +305,7 @@ ast: errors: already_voted: Yá votesti nesta encuesta expired: La encuesta yá finó + invalid_choice: El la opción de votu escoyida nun esiste preferences: public_timelines: Llinies temporales públiques relationships: diff --git a/config/locales/ca.yml b/config/locales/ca.yml index e25172d6b2..ccbd16e2a1 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -9,18 +9,18 @@ ca: administered_by: 'Administrat per:' api: API apps: Apps mòbils - apps_platforms: Utilitza Mastodont des de iOS, Android i altres plataformes + apps_platforms: Utilitza Mastodon des de iOS, Android i altres plataformes browse_directory: Navega per el directori de perfils i filtra segons interessos - browse_local_posts: Navega un flux en directe de publicacions d’aquest servidor - browse_public_posts: Navega per una transmissió en directe de publicacions públiques a Mastodont + browse_local_posts: Navega un flux en directe de publicacions públiques d’aquest servidor + browse_public_posts: Navega per una transmissió en directe de publicacions públiques a Mastodon contact: Contacte contact_missing: No configurat contact_unavailable: N/D discover_users: Descobreix usuaris documentation: Documentació - federation_hint_html: Amb un compte de %{instance} podràs seguir persones de qualsevol servidor Mastodont i altres. + federation_hint_html: Amb un compte de %{instance} podràs seguir persones de qualsevol servidor Mastodon i altres. get_apps: Prova una aplicació mòbil - hosted_on: Mastodont allotjat a %{domain} + hosted_on: Mastodon allotjat a %{domain} instance_actor_flash: | Aquest compte és un actor virtual utilitzat per a representar al propi servidor i no cap usuari individual. S'utilitza per a propòsits de federació i no ha de ser bloquejat si no voleu bloquejar tota la instància, en aquest cas hauríeu d'utilitzar un bloqueig de domini. @@ -31,7 +31,7 @@ ca: source_code: Codi font status_count_after: one: estat - other: estats + other: tuts status_count_before: Que han escrit tagline: Segueix els teus amics i descobreix-ne de nous terms: Termes del servei @@ -40,14 +40,14 @@ ca: domain: Servidor reason: Raó rejecting_media: 'Els arxius multimèdia d''aquests servidors no seran processats o emmagatzemats i cap miniatura serà mostrada, requerint clic manual a través de l''arxiu original:' - silenced: 'Les publicacions d''aquests servidors seran amagades en les cronologíes públiques i converses, i cap notificació serà generada de les interaccions dels seus usuaris, llevat que estiguis seguint-los:' + silenced: 'Les publicacions d''aquests servidors seran amagades en les línies de temps públiques i en les converses, i cap notificació serà generada de les interaccions dels seus usuaris, llevat que estiguis seguint-los:' suspended: 'Cap dada d''aquests servidors serà processada, emmagatzemada o intercanviada, fent impossible qualsevol interacció o comunicació amb els usuaris d''aquests servidors:' - unavailable_content_html: Mastodont generalment et permet per veure contingut i interaccionar amb usuaris de qualsevol altre servidor en el fedivers. Aquestes són les excepcions que s'han fet en aquest servidor particular. + unavailable_content_html: Mastodon generalment et permet veure el contingut i interaccionar amb els usuaris de qualsevol altre servidor en el fedivers. Aquestes són les excepcions que s'han fet en aquest servidor particular. user_count_after: one: usuari other: usuaris user_count_before: Tenim - what_is_mastodon: Què és Mastodont? + what_is_mastodon: Què és Mastodon? accounts: choices_html: 'Eleccions de %{name}:' endorsements_hint: Pots recomanar persones que segueixes a l'interfície de web, que apareixeran aquí. @@ -70,7 +70,7 @@ ca: pin_errors: following: Has d'estar seguint la persona que vulguis avalar posts: - one: Barrita + one: Tut other: Tuts posts_tab_heading: Tuts posts_with_replies: Tuts i respostes @@ -144,7 +144,7 @@ ca: moderation_notes: Notes de moderació most_recent_activity: Activitat més recent most_recent_ip: IP més recent - no_account_selected: No s'han canviat els comptes perque no s'han seleccionat + no_account_selected: No s'han canviat els comptes perquè no s'han seleccionat no_limits_imposed: Sense límits imposats not_subscribed: No subscrit pending: Revisió pendent @@ -179,7 +179,7 @@ ca: targeted_reports: Informes realitzats per altres silence: Silenci silenced: Silenciat - statuses: Estats + statuses: Tuts subscribe: Subscriu suspended: Suspès time_in_queue: Esperant en la cua %{time} @@ -226,7 +226,7 @@ ca: unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}" update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}" update_status: "%{name} estat actualitzat per %{target}" - deleted_status: "(estat esborrat)" + deleted_status: "(tut esborrat)" title: Registre d'auditoria custom_emojis: assign_category: Assigna una categoria @@ -270,7 +270,7 @@ ca: feature_registrations: Registres feature_relay: Relay de la Federació feature_spam_check: Anti-spam - feature_timeline_preview: Vista previa de la cronología + feature_timeline_preview: Vista prèvia de línia de temps features: Característiques hidden_service: Federació amb serveis ocults open_reports: informes oberts @@ -280,7 +280,7 @@ ca: search: Cerca de text complet single_user_mode: Mode d'usuari únic software: Programari - space: Ús d’espai + space: Ús de l’espai title: Panell total_users: usuaris en total trends: Tendències @@ -299,7 +299,7 @@ ca: destroyed_msg: El bloqueig de domini s'ha desfet domain: Domini edit: Editar el bloqueig del domini - existing_domain_block_html: Ja has imposat uns limits més estrictes a %{name}, l'hauries de desbloquejar-lo primer. + existing_domain_block_html: Ja has imposat uns límits més estrictes a %{name}, l'hauries de desbloquejar-lo primer. new: create: Crea un bloqueig hint: El bloqueig de domini no impedirà la creació de nous comptes en la base de dades, però s'aplicaran de manera retroactiva mètodes de moderació específics sobre aquests comptes. @@ -344,9 +344,6 @@ ca: create: Afegeix un domini title: Nova adreça de correu en la llista negra title: Llista negra de correus electrònics - followers: - back_to_account: Torna al compte - title: Seguidors de %{acct} instances: by_domain: Domini delivery_available: El lliurament està disponible @@ -375,14 +372,16 @@ ca: title: Convida pending_accounts: title: Comptes pendents (%{count}) + relationships: + title: relacions del %{acct} relays: add_new: Afegiu un nou relay delete: Esborra - description_html: Un relay de federació és un servidor intermediari que intercanvia grans volums de barritades públiquess entre servidors que es subscriuen i publiquen en ell. Pot ajudar a servidors petits i mitjans a descobrir contingut del fedivers, no fent necessari que els usuaris locals manualment segueixin altres persones de servidors remots. + description_html: Un relay de federació és un servidor intermediari que intercanvia grans volums de tuts públics entre servidors que es subscriuen i publiquen en ell. Pot ajudar a servidors petits i mitjans a descobrir contingut del fedivers, no fent necessari que els usuaris locals manualment segueixin altres persones de servidors remots. disable: Inhabilita disabled: Desactivat enable: Activat - enable_hint: Una vegada habilitat el teu servidor es subscriurà a totes les barritades públiques d'aquesta ristra i començarà a enviar-hi totes les barritades públiques d'aquest servidor. + enable_hint: Una vegada habilitat, el teu servidor es subscriurà a tots els tuts públics d'aquest relay i començarà a enviar-hi tots els tuts públics d'aquest servidor. enabled: Activat inbox_url: URL del Relay pending: S'està esperant l'aprovació del relay @@ -431,7 +430,7 @@ ca: updated_at: Actualitzat settings: activity_api_enabled: - desc_html: Nombre d'estats publicats localment, usuaris actius i registres nous en períodes setmanals + desc_html: Nombre de tuts publicats localment, usuaris actius i registres nous en períodes setmanals title: Publica estadístiques agregades sobre l'activitat de l'usuari bootstrap_timeline_accounts: desc_html: Separa diversos noms d'usuari amb comes. Només funcionaran els comptes locals i desblocats. El valor predeterminat quan està buit és tots els administradors locals. @@ -486,8 +485,8 @@ ca: open: Qualsevol pot registrar-se title: Mode de registres show_known_fediverse_at_about_page: - desc_html: Quan s'activa, mostrarà tots els brams del fedivers conegut en vista prèvia. En cas contrari, només es mostraran els locals - title: Mostra el fedivers conegut en vista prèvia de la cronología + desc_html: Quan està desactivat, restringeix la línia de temps pública enllaçada des de la pàgina inicial a mostrar només contingut local + title: Inclou el contingut federat a la pàgina no autenticada de la línia de temps pública show_staff_badge: desc_html: Mostra una insígnia de personal en la pàgina d'usuari title: Mostra insígnia de personal @@ -498,21 +497,21 @@ ca: desc_html: Un bon lloc per al codi de conducta, regles, directrius i altres coses que distingeixen el teu servidor. Pots utilitzar etiquetes HTML title: Descripció ampliada del lloc site_short_description: - desc_html: Es mostra a la barra lateral i a metaetiquetes. Descriu en un únic paràgraf què és Mastodont i què fa que aquest servidor sigui especial. Si està buit, s'estableix per defecte la descripció del servidor. + desc_html: Es mostra a la barra lateral i a metaetiquetes. Descriu en un únic paràgraf què és Mastodon i què fa que aquest servidor sigui especial. title: Descripció curta del servidor site_terms: desc_html: Pots escriure la teva pròpia política de privadesa, els termes del servei o d'altres normes legals. Pots utilitzar etiquetes HTML title: Termes del servei personalitzats site_title: Nom del servidor spam_check_enabled: - desc_html: Mastodont pot auto-silenciar i informar automàticament de comptes basat en mesures com ara la detecció de comptes que envien missatges repetits no sol·licitats. Pot haver-hi falsos positius. + desc_html: Mastodon pot informar automàticament de comptes que envien repetits missatges no sol·licitats. Pot haver-hi falsos positius. title: Anti-spam thumbnail: desc_html: S'utilitza per obtenir visualitzacions prèvies a través d'OpenGraph i API. Es recomana 1200x630px title: Miniatura del servidor timeline_preview: - desc_html: Mostra la cronología pública a la pàgina inicial - title: Vista prèvia de la cronología + desc_html: Mostra l'enllaç a la línia de temps pública a la pàgina inicial i permet l'accés a l'API a la línia de temps pública sense autenticació + title: Permet l'accés no autenticat a la línia de temps pública title: Configuració del lloc trendable_by_default: desc_html: Afecta a les etiquetes que no s'havien rebutjat prèviament @@ -578,7 +577,7 @@ ca: remove: Desvincula l'àlies appearance: advanced_web_interface: Interfície web avançada - advanced_web_interface_hint: 'Si vols fer ús de tota l''amplada de la teva pantalla, l''interfície web avançada et permet configurar diverses columnes per a veure molta més informació al mateix temps: Inici, notificacions, cronología federada i qualsevol número de llistes i etiquetes.' + advanced_web_interface_hint: 'Si vols fer ús de tota l''amplada de la teva pantalla, l''interfície web avançada et permet configurar diverses columnes per a veure molta més informació al mateix temps: Inici, notificacions, línia de temps federada i qualsevol número de llistes i etiquetes.' animations_and_accessibility: Animacions i accessibilitat confirmation_dialogs: Diàlegs de confirmació discovery: Descobriment @@ -611,9 +610,9 @@ ca: delete_account: Suprimeix el compte delete_account_html: Si vols suprimir el compte pots fer-ho aquí. Se't demanarà confirmació. description: - prefix_invited_by_user: "@%{name} t'ha invitat a unir-te a aquest servidor de Mastodont!" - prefix_sign_up: Registra't avui a Mastodont! - suffix: Amb un compte seràs capaç de seguir persones, publicar i intercanviar missatges amb usuaris de qualsevol servidor de Mastodont i més! + prefix_invited_by_user: "@%{name} t'ha invitat a unir-te a aquest servidor de Mastodon!" + prefix_sign_up: Registra't avui a Mastodon! + suffix: Amb un compte seràs capaç de seguir persones, publicar i intercanviar missatges amb usuaris de qualsevol servidor de Mastodon i més! didnt_get_confirmation: No has rebut el correu de confirmació? forgot_password: Has oblidat la contrasenya? invalid_reset_password_token: L'enllaç de restabliment de la contrasenya no és vàlid o ha caducat. Torna-ho a provar. @@ -709,7 +708,7 @@ ca: content: Ho sentim, però alguna cosa ha fallat a la nostra banda. title: Aquesta pàgina no es correcta '503': La pàgina no podria ser servida a causa d'un error temporal del servidor. - noscript_html: Per a utilitzar Mastodont, activa el JavaScript. També pots provar una de les aplicacions natives de Mastodont per a la vostra plataforma. + noscript_html: Per a utilitzar Mastodon, activa el JavaScript. També pots provar una de les aplicacions natives de Mastodon per a la teva plataforma. existing_username_validator: not_found: no s'ha pogut trobar cap usuari local amb aquest nom d'usuari not_found_multiple: no s'ha pogut trobar %{usernames} @@ -717,7 +716,7 @@ ca: archive_takeout: date: Data download: Baixa l’arxiu - hint_html: Pots sol·licitar un arxiu de les teves barritades i dels fitxers multimèdia pujats. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible. Pots sol·licitar un arxiu cada 7 dies. + hint_html: Pots sol·licitar un arxiu dels teus tuts i dels fitxers multimèdia pujats. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible. Pots sol·licitar un arxiu cada 7 dies. in_progress: S'està compilant el teu arxiu... request: Sol·licita el teu arxiu size: Mida @@ -731,12 +730,13 @@ ca: add_new: Afegir nova errors: limit: Ja has mostrat la quantitat màxima d'etiquetes - hint_html: "Què son les etiquetes destacades? Es mostren de manera destacada en el teu perfil públic i permeten a les persones navegar per les teves publicacions amb aquestes etiquetes. Són una gran eina per fer un seguiment de treballs creatius o de projectes a llarg termini." + hint_html: "Què son les etiquetes destacades? Es mostren de manera destacada en el teu perfil públic i permeten a les persones navegar per les teves publicacions gràcies a aquestes etiquetes. Són una gran eina per fer un seguiment de treballs creatius o de projectes a llarg termini." filters: contexts: - home: Cronología d'inici + account: Perfils + home: Línia de temps Inici notifications: Notificacions - public: Cronologíes públiques + public: Línies de temps públiques thread: Converses edit: title: Editar filtre @@ -748,7 +748,7 @@ ca: empty: No hi tens cap filtre. title: Filtres new: - title: Afegeix un filtre + title: Afegeix un nou filtre footer: developers: Desenvolupadors more: Més… @@ -776,11 +776,11 @@ ca: invalid_token: Els tokens de Keybase són hashs de signatures i han de tenir 66 caràcters hexadecimals verification_failed: Keybase no reconeix aquest token com a signatura del usuari de Keybase %{kb_username}. Si us plau prova des de Keybase. wrong_user: No es pot crear una prova per a %{proving} mentre es connectava com a %{current}. Inicia sessió com a %{proving} i prova de nou. - explanation_html: Aquí pots connectar criptogràficament les teves altres identitats com ara el teu perfil de Keybase. Això permet que altres persones t'envïin missatges xifrats i confiar en el contingut que els hi envies. + explanation_html: Aquí pots connectar criptogràficament les teves altres identitats com ara el teu perfil de Keybase. Això permet que altres persones t'enviïn missatges xifrats i confiar en el contingut que els hi envies. i_am_html: Sóc %{username} a %{service}. identity: Identitat inactive: Inactiu - publicize_checkbox: 'I barrita això:' + publicize_checkbox: 'I envia un tut d''això:' publicize_toot: 'Està provat! Sóc %{username} a %{service}: %{url}' status: Estat de verificació view_proof: Veure la prova @@ -829,7 +829,7 @@ ca: images_and_video: No es pot adjuntar un vídeo a una publicació que ja contingui imatges too_many: No es poden adjuntar més de 4 fitxers migrations: - acct: usuari@domini del nou compte + acct: Mogut a cancel: Cancel·la redirecció cancel_explanation: Cancel·lant la redirecció reactivará el teu compte actual però no recuperarà els seguidors que han estat moguts a aquell compte. cancelled_msg: Redirecció cancel·lada amb èxit. @@ -838,7 +838,7 @@ ca: missing_also_known_as: no fa referencia a aquest compte move_to_self: no pot ser el compte actual not_found: podria no ser trobat - on_cooldown: Estàs en temps de recuperació + on_cooldown: Estàs en temps de refredament followers_count: Seguidors en el moment del moviment incoming_migrations: Movent des d'un compte diferent incoming_migrations_html: Per a moure't des d'un altre compte a aquest, primer necessites crear un àlies de compte. @@ -863,7 +863,7 @@ ca: notification_mailer: digest: action: Mostra totes les notificacions - body: Un resum del que et vas perdre desde la darrera visita el %{since} + body: Un resum del que et vas perdre des de la darrera visita el %{since} mention: "%{name} t'ha mencionat en:" new_followers_summary: one: A més, has adquirit un nou seguidor durant la teva absència! Visca! @@ -928,12 +928,13 @@ ca: preferences: other: Altre posting_defaults: Valors predeterminats de publicació - public_timelines: Cronologíes públiques + public_timelines: Línies de temps públiques relationships: activity: Activitat del compte dormant: Inactiu followers: Seguidors following: Seguint + invited: Convidat last_active: Darrer actiu most_recent: Més recent moved: Mogut @@ -946,7 +947,7 @@ ca: status: Estat del compte remote_follow: acct: Escriu el teu usuari@domini des del qual vols seguir - missing_resource: No s'ha pogut trobar la URL de redirecció necessaria per al compte + missing_resource: No s'ha pogut trobar la URL de redirecció necessària per al compte no_account_html: No tens cap compte? Pots registrar-te aquí proceed: Comença a seguir prompt: 'Seguiràs a:' @@ -954,16 +955,16 @@ ca: remote_interaction: favourite: proceed: Procedir a afavorir - prompt: 'Vols marcar com a favorit aquesta barritada:' + prompt: 'Vols marcar com a favorit aquest tut:' reblog: proceed: Procedir a impulsar - prompt: 'Vols impulsar aquesta barritada:' + prompt: 'Vols impulsar aquest tut:' reply: proceed: Procedir a respondre - prompt: 'Vols respondre a aquesta barritada:' + prompt: 'Vols respondre a aquest tut:' scheduled_statuses: - over_daily_limit: Has superat el límit de %{limit} barritades programades per a aquell dia - over_total_limit: Has superat el limit de %{limit} barritades programades + over_daily_limit: Has superat el límit de %{limit} tuts programats per a aquell dia + over_total_limit: Has superat el limit de %{limit} tuts programats too_soon: La data programada ha de ser futura sessions: activity: Última activitat @@ -988,7 +989,7 @@ ca: weibo: Weibo current_session: Sessió actual description: "%{browser} de %{platform}" - explanation: Aquests són els navegadors web que actualment han iniciat la sessió amb el teu compte de Mastodont. + explanation: Aquests són els navegadors web que actualment han iniciat la sessió amb el teu compte de Mastodon. ip: IP platforms: adobe_air: Adobe Air @@ -1012,7 +1013,7 @@ ca: aliases: Àlies de compte appearance: Aparença authorized_apps: Aplicacions autoritzades - back: Torna a Mastodont + back: Torna a Mastodon delete: Eliminació del compte development: Desenvolupament edit_profile: Edita el perfil @@ -1047,9 +1048,9 @@ ca: open_in_web: Obre en la web over_character_limit: Límit de caràcters de %{max} superat pin_errors: - limit: Ja has fixat el màxim nombre de barritades - ownership: No es pot fixar la barritada d'altra persona - private: No es pot fixar la barritada no pública + limit: Ja has fixat el màxim nombre de tuts + ownership: No es pot fixar el tut d'algú altre + private: No es pot fixar un tut no públic reblog: No es pot fixar un impuls poll: total_people: @@ -1068,7 +1069,7 @@ ca: public: Públic public_long: Tothom pot veure-ho unlisted: No llistat - unlisted_long: Tothom ho pot veure, però no es mostra en la cronología federada + unlisted_long: Tothom ho pot veure, però no es mostra en les línies de temps públiques stream_entries: pinned: Tut fixat reblogged: ha impulsat @@ -1175,23 +1176,23 @@ ca: enabled_success: Autenticació de dos factors activada correctament generate_recovery_codes: Genera codis de recuperació instructions_html: "Escaneja aquest codi QR desde Google Authenticator o una aplicació similar del teu telèfon. Desde ara, aquesta aplicació generarà tokens que tens que ingresar quan volguis iniciar sessió." - lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els codis de recuperació els pots tornar a generar aquí. Els codis de recuperació anteriors s'anul·laran. - manual_instructions: 'Si no pots escanejar el codi QR code i necessites introduir-lo manualment, aquí tens el secret en text pla:' + lost_recovery_codes: Els codis de recuperació et permeten recuperar l'accés al teu compte si perds el telèfon. Si has perdut els codis de recuperació els pots tornar a generar aquí. S'anul·laran els codis de recuperació anteriors. + manual_instructions: 'Si no pots escanejar el codi QR i necessites introduir-lo manualment, aquí tens el secret en text pla:' recovery_codes: Codis de recuperació de còpia de seguretat recovery_codes_regenerated: Codis de recuperació regenerats amb èxit - recovery_instructions_html: Si mai perds l'accéss al teu telèfon pots utilitzar un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. Cal mantenir els codis de recuperació en lloc segur. Per exemple, imprimint-los i guardar-los amb altres documents importants. + recovery_instructions_html: Si mai perds l'accés al teu telèfon pots utilitzar un dels codis de recuperació a continuació per a recuperar l'accés al teu compte. Cal mantenir els codis de recuperació en lloc segur. Per exemple, imprimint-los i guardar-los amb altres documents importants. setup: Establir wrong_code: El codi introduït no és vàlid! És correcta l'hora del servidor i del dispositiu? user_mailer: backup_ready: - explanation: Has sol·licitat una copia completa del teu compte Mastodont. Ara ja està a punt per descàrrega! + explanation: Has sol·licitat una copia completa del teu compte Mastodon. Ara ja està a punt per a descàrrega! subject: El teu arxiu està preparat per a descàrrega title: Recollida del arxiu warning: explanation: disable: Mentre el teu compte estigui congelat les dades romandran intactes però no pots dur a terme cap acció fins que no estigui desbloquejat. silence: Mentre el teu compte estigui limitat només les persones que ja et segueixen veuen les teves dades en aquest servidor i pots ser exclòs de diverses llistes públiques. No obstant això, d'altres encara poden seguir-te manualment. - suspend: El teu compte s'ha suspès i totes les teves barritades i fitxers multimèdia penjats s'han eliminat irreversiblement d'aquest servidor i dels servidors on tenies seguidores. + suspend: El teu compte s'ha suspès i tots els teus tuts i fitxers multimèdia penjats s'han eliminat de manera irreversible d'aquest servidor i dels servidors on tenies seguidors. get_in_touch: Pots respondre a aquest correu electrònic per a contactar amb el personal de %{instance}. review_server_policies: Revisa les polítiques del servidor statuses: 'Concretament, per:' @@ -1215,11 +1216,11 @@ ca: full_handle_hint: Això és el que has de dir als teus amics perquè puguin enviar-te missatges o seguir-te des d'un altre servidor. review_preferences_action: Canviar preferències review_preferences_step: Assegura't d'establir les teves preferències, com ara els correus electrònics que vols rebre o el nivell de privadesa per defecte que t'agradaria que tinguin les teves entrades. Si no tens malaltia de moviment, pots optar per habilitar la reproducció automàtica de GIF. - subject: Et donem la benvinguda a Mastodont - tip_federated_timeline: La cronología federada és el cabal principal de la xarxa Mastodont. Però només inclou les persones a les quals els teus veïns estan subscrits, de manera que no està complet. + subject: Et donem la benvinguda a Mastodon + tip_federated_timeline: La línia de temps federada és el cabal principal de la xarxa Mastodon. Però només inclou les persones a les quals els teus veïns estan subscrits, de manera que no està completa. tip_following: Per defecte segueixes als administradors del servidor. Per trobar més persones interessants, consulta les línies de temps local i federada. tip_local_timeline: La línia de temps local és la vista del flux de publicacions dels usuaris de %{instance}. Aquests usuaris són els teus veïns més propers! - tip_mobile_webapp: Si el teu navegador del mòbil t'ofereix afegir Mastodont a la teva pantalla d'inici, podràs rebre notificacions d'empènyer "push". Es comporta com una aplicació nativa en molts aspectes! + tip_mobile_webapp: Si el teu navegador del mòbil t'ofereix afegir Mastodon a la teva pantalla d'inici, podràs rebre notificacions "push". Es comporta com una aplicació nativa en molts aspectes! tips: Consells title: Benvingut a bord, %{name}! users: @@ -1230,5 +1231,5 @@ ca: seamless_external_login: Has iniciat sessió via un servei extern per tant els ajustos de contrasenya i correu electrònic no estan disponibles. signed_in_as: 'Sessió iniciada com a:' verification: - explanation_html: 'Pots verificar-te com a propietari dels enllaços a les metadades del teu perfil. Per això, el lloc web enllaçat ha de contenir un enllaç al teu perfil de Mastodont. El vincle ha de tenir l''atribut rel="me". El contingut del text de l''enllaç no importa. Aquí tens un exemple:' + explanation_html: 'Pots verificar-te com a propietari dels enllaços a les metadades del teu perfil. Per això, el lloc web enllaçat ha de contenir un enllaç al teu perfil de Mastodon. El vincle ha de tenir l''atribut rel="me". El contingut del text de l''enllaç no importa. Aquí tens un exemple:' verification: Verificació diff --git a/config/locales/co.yml b/config/locales/co.yml index 4cc1ed5fb2..b21fc8d15f 100644 --- a/config/locales/co.yml +++ b/config/locales/co.yml @@ -344,9 +344,6 @@ co: create: Creà un blucchime title: Nova iscrizzione nant’a lista nera e-mail title: Lista nera e-mail - followers: - back_to_account: Rivene à u Contu - title: Abbunati à %{acct} instances: by_domain: Duminiu delivery_available: Rimessa dispunibule diff --git a/config/locales/cs.yml b/config/locales/cs.yml index a9b101758a..547468e50b 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -354,9 +354,6 @@ cs: create: Přidat doménu title: Nová položka pro černou listinu e-mailů title: Černá listina e-mailů - followers: - back_to_account: Zpět na účet - title: Sledující uživatele %{acct} instances: by_domain: Doména delivery_available: Doručení je k dispozici diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 5e63f1702c..ee8807d20d 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -359,13 +359,11 @@ cy: delete: Dileu destroyed_msg: Llwyddwyd i ddileu parth e-bost o'r gosbrestr domain: Parth + empty: Dim parthiau ebost ar y rhestr rhwystro. new: create: Ychwanegu parth title: Cofnod newydd yng nghosbrestr e-byst title: Cosbrestr e-bost - followers: - back_to_account: Nôl i'r gyfrif - title: Dilynwyr %{acct} instances: by_domain: Parth delivery_available: Mae'r cyflenwad ar gael @@ -776,6 +774,7 @@ cy: invalid_irreversible: Mae hidlo anadferadwy ond yn gweithio yng nghyd-destun cartref neu hysbysiadau index: delete: Dileu + empty: Nid oes gennych chi hidlyddion. title: Hidlyddion new: title: Ychwanegu hidlydd newydd @@ -967,6 +966,7 @@ cy: duration_too_long: yn rhy bell yn y dyfodol duration_too_short: yn rhy fuan expired: Mae'r pleidlais wedi gorffen yn barod + invalid_choice: Nid yw'r dewis pleidlais hyn yn bodoli over_character_limit: ni all fod yn hirach na %{max} cymeriad yr un too_few_options: rhaid cael fwy nag un eitem too_many_options: ni all cynnwys fwy na %{max} o eitemau diff --git a/config/locales/da.yml b/config/locales/da.yml index e20496918c..1cdf7722e3 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -335,9 +335,6 @@ da: create: Tilføj domæne title: Ny email blokade opslag title: Email sortliste - followers: - back_to_account: Tilbage til konto - title: "%{acct}'s følgere" instances: by_domain: Domæne delivery_available: Levering er tilgængelig diff --git a/config/locales/de.yml b/config/locales/de.yml index 9361ad4f31..218267cd3a 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -198,11 +198,13 @@ de: change_email_user: "%{name} hat die E-Mail-Adresse des Nutzers %{target} geändert" confirm_user: "%{name} hat die E-Mail-Adresse von %{target} bestätigt" create_account_warning: "%{name} hat eine Warnung an %{target} gesendet" + create_announcement: "%{name} hat die neue Ankündigung %{target} erstellt" create_custom_emoji: "%{name} hat neues Emoji %{target} hochgeladen" create_domain_allow: "%{name} hat die Domain %{target} gewhitelistet" create_domain_block: "%{name} hat die Domain %{target} blockiert" create_email_domain_block: "%{name} hat die E-Mail-Domain %{target} geblacklistet" demote_user: "%{name} stufte Benutzer_in %{target} herunter" + destroy_announcement: "%{name} hat die neue Ankündigung %{target} gelöscht" destroy_custom_emoji: "%{name} zerstörte Emoji %{target}" destroy_domain_allow: "%{name} hat die Domain %{target} von der Whitelist entfernt" destroy_domain_block: "%{name} hat die Domain %{target} entblockt" @@ -224,10 +226,22 @@ de: unassigned_report: "%{name} hat die Zuweisung der Meldung %{target} entfernt" unsilence_account: "%{name} hat die Stummschaltung von %{target} aufgehoben" unsuspend_account: "%{name} hat die Verbannung von %{target} aufgehoben" + update_announcement: "%{name} aktualisierte Ankündigung %{target}" update_custom_emoji: "%{name} hat das %{target} Emoji geändert" update_status: "%{name} hat einen Beitrag von %{target} aktualisiert" deleted_status: "(gelöschter Beitrag)" title: Überprüfungsprotokoll + announcements: + edit: + title: Ankündigung bearbeiten + empty: Keine Ankündigungen gefunden. + live: Live + new: + create: Ankündigung erstellen + title: Neue Ankündigung + published: Veröffentlicht + time_range: Zeitraum + title: Ankündigungen custom_emojis: assign_category: Kategorie zuweisen by_domain: Domain @@ -344,9 +358,6 @@ de: create: Blockade erstellen title: Neue E-Mail-Domain-Blockade title: E-Mail-Domain-Blockade - followers: - back_to_account: Zurück zum Konto - title: "%{acct}'s Follower" instances: by_domain: Domain delivery_available: Zustellung funktioniert @@ -375,6 +386,8 @@ de: title: Einladungen pending_accounts: title: Ausstehende Konten (%{count}) + relationships: + title: Beziehungen von %{acct} relays: add_new: Neues Relay hinzufügen delete: Löschen @@ -658,6 +671,9 @@ de: hint_html: "Hinweis: Wir werden dich für die nächste Stunde nicht erneut nach deinem Passwort fragen." invalid_password: Ungültiges Passwort prompt: Gib dein Passwort ein um fortzufahren + date: + formats: + default: "%d. %b %Y" datetime: distance_in_words: about_x_hours: "%{count}h" @@ -734,6 +750,7 @@ de: hint_html: "Was sind empfohlene Hashtags? Sie werden in deinem öffentlichen Profil deutlich angezeigt und ermöglichen es den Menschen, deine öffentlichen Beiträge speziell unter diesen Hashtags zu durchsuchen. Sie sind ein großartiges Werkzeug, um kreative Werke oder langfristige Projekte zu verfolgen." filters: contexts: + account: Profile home: Startseite notifications: Benachrichtigungen public: Öffentliche Zeitleisten @@ -758,6 +775,8 @@ de: all: Alle changes_saved_msg: Änderungen gespeichert! copy: Kopieren + delete: Löschen + edit: Bearbeiten no_batch_actions_available: Keine Massenaktionen auf dieser Seite verfügbar order_by: Sortieren nach save_changes: Änderungen speichern @@ -921,6 +940,7 @@ de: duration_too_long: ist zu weit in der Zukunft duration_too_short: ist zu früh expired: Die Umfrage ist bereits vorbei + invalid_choice: Die gewählte Stimmenoption existiert nicht over_character_limit: kann nicht länger als jeweils %{max} Zeichen sein too_few_options: muss mindestens einen Eintrag haben too_many_options: kann nicht mehr als %{max} Einträge beinhalten @@ -928,11 +948,15 @@ de: other: Weiteres posting_defaults: Standardeinstellungen für Beiträge public_timelines: Öffentliche Zeitleisten + reactions: + errors: + unrecognized_emoji: ist kein anerkanntes Emoji relationships: activity: Kontoaktivität dormant: Inaktiv followers: Folgende following: Folgt + invited: Eingeladen last_active: Zuletzt aktiv most_recent: Neuste moved: Umgezogen diff --git a/config/locales/doorkeeper.ar.yml b/config/locales/doorkeeper.ar.yml index 204ac429bf..49c7cade97 100644 --- a/config/locales/doorkeeper.ar.yml +++ b/config/locales/doorkeeper.ar.yml @@ -38,6 +38,7 @@ ar: application: تطبيق callback_url: رابط رد النداء delete: حذف + empty: ليس لديك أية تطبيقات. name: التسمية new: تطبيق جديد scopes: المجالات diff --git a/config/locales/doorkeeper.ast.yml b/config/locales/doorkeeper.ast.yml index db6eb1e515..5c54b5353e 100644 --- a/config/locales/doorkeeper.ast.yml +++ b/config/locales/doorkeeper.ast.yml @@ -27,6 +27,7 @@ ast: help: native_redirect_uri: Usa %{native_redirect_uri} pa pruebes llocales index: + empty: Nun tienes aplicaciones. name: Nome new: Aplicación nueva scopes: Ámbitos diff --git a/config/locales/doorkeeper.ca.yml b/config/locales/doorkeeper.ca.yml index 3c69cf8e7e..3de9d4bab2 100644 --- a/config/locales/doorkeeper.ca.yml +++ b/config/locales/doorkeeper.ca.yml @@ -38,6 +38,7 @@ ca: application: Aplicació callback_url: URL de retorn delete: Suprimeix + empty: No tens cap aplicació. name: Nom new: Aplicació nova scopes: Àmbits diff --git a/config/locales/doorkeeper.co.yml b/config/locales/doorkeeper.co.yml index a64a079318..4f03c0c323 100644 --- a/config/locales/doorkeeper.co.yml +++ b/config/locales/doorkeeper.co.yml @@ -38,6 +38,7 @@ co: application: Applicazione callback_url: URL di richjama delete: Toglie + empty: Ùn avete micca d'applicazione. name: Nome new: Applicazione nova scopes: Scopi diff --git a/config/locales/doorkeeper.cs.yml b/config/locales/doorkeeper.cs.yml index 8c5c175f5d..00345db76c 100644 --- a/config/locales/doorkeeper.cs.yml +++ b/config/locales/doorkeeper.cs.yml @@ -38,6 +38,7 @@ cs: application: Aplikace callback_url: Zpáteční URL delete: Smazat + empty: Nemáte žádné aplikace. name: Název new: Nová aplikace scopes: Rozsahy diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml index 65fb2de88a..8b850b56a6 100644 --- a/config/locales/doorkeeper.de.yml +++ b/config/locales/doorkeeper.de.yml @@ -38,6 +38,7 @@ de: application: Anwendung callback_url: Callback-URL delete: Löschen + empty: Du hast keine Anwendungen. name: Name new: Neue Anwendung scopes: Befugnisse diff --git a/config/locales/doorkeeper.el.yml b/config/locales/doorkeeper.el.yml index d4bf0ae775..7423606d4c 100644 --- a/config/locales/doorkeeper.el.yml +++ b/config/locales/doorkeeper.el.yml @@ -38,6 +38,7 @@ el: application: Εφαρμογή callback_url: URL επιστροφής (Callback) delete: Διαγραφή + empty: Δεν έχετε αιτήσεις. name: Όνομα new: Νέα εφαρμογή scopes: Εύρος εφαρμογής diff --git a/config/locales/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml index cb12d0e821..89a579ae9a 100644 --- a/config/locales/doorkeeper.eo.yml +++ b/config/locales/doorkeeper.eo.yml @@ -38,6 +38,7 @@ eo: application: Aplikaĵo callback_url: Revena URL delete: Forigi + empty: Vi havas neniun aplikaĵon. name: Nomo new: Nova aplikaĵo scopes: Ampleksoj diff --git a/config/locales/doorkeeper.es-AR.yml b/config/locales/doorkeeper.es-AR.yml index 61b14ba16c..85ab7729de 100644 --- a/config/locales/doorkeeper.es-AR.yml +++ b/config/locales/doorkeeper.es-AR.yml @@ -38,6 +38,7 @@ es-AR: application: Aplicación callback_url: Dirección web de respuesta ("callback") delete: Eliminar + empty: No tenés aplicaciones. name: Nombre new: Nueva aplicación scopes: Ámbitos diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml index 75a04eccf2..61e6cb6a18 100644 --- a/config/locales/doorkeeper.es.yml +++ b/config/locales/doorkeeper.es.yml @@ -38,6 +38,7 @@ es: application: Aplicación callback_url: URL de callback delete: Eliminar + empty: No tienes aplicaciones. name: Nombre new: Nueva aplicación scopes: Ámbitos @@ -139,7 +140,7 @@ es: write:accounts: modifica tu perfil write:blocks: bloquear cuentas y dominios write:bookmarks: guardar estados como marcadores - write:favourites: estados favoritos + write:favourites: toots favoritos write:filters: crear filtros write:follows: seguir usuarios write:lists: crear listas diff --git a/config/locales/doorkeeper.et.yml b/config/locales/doorkeeper.et.yml index 8fb9446317..d3b011a67a 100644 --- a/config/locales/doorkeeper.et.yml +++ b/config/locales/doorkeeper.et.yml @@ -38,6 +38,7 @@ et: application: Rakendus callback_url: Ümbersuunamise URL delete: Kustuta + empty: Teil pole rakendusi. name: Nimi new: Uus rakendus scopes: Ulatused diff --git a/config/locales/doorkeeper.eu.yml b/config/locales/doorkeeper.eu.yml index 19cc409928..07fc139834 100644 --- a/config/locales/doorkeeper.eu.yml +++ b/config/locales/doorkeeper.eu.yml @@ -38,6 +38,7 @@ eu: application: Aplikazioa callback_url: Itzulera URLa delete: Ezabatu + empty: Ez duzu aplikaziorik. name: Izena new: Aplikazio berria scopes: Irismena diff --git a/config/locales/doorkeeper.fa.yml b/config/locales/doorkeeper.fa.yml index 03a8d7963e..c9ca1895e6 100644 --- a/config/locales/doorkeeper.fa.yml +++ b/config/locales/doorkeeper.fa.yml @@ -38,6 +38,7 @@ fa: application: برنامه callback_url: نشانی Callback delete: حذف + empty: شما هیچ برنامه‌ای ندارید. name: نام new: برنامهٔ تازه scopes: دامنه‌ها diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml index 11bbe8cf1d..e9bf70cd25 100644 --- a/config/locales/doorkeeper.fr.yml +++ b/config/locales/doorkeeper.fr.yml @@ -38,6 +38,7 @@ fr: application: Application callback_url: URL de retour d’appel delete: Effacer + empty: Vous n’avez pas d’application. name: Nom new: Nouvelle application scopes: Permissions diff --git a/config/locales/doorkeeper.gl.yml b/config/locales/doorkeeper.gl.yml index 9cb5d754c8..281f03f84c 100644 --- a/config/locales/doorkeeper.gl.yml +++ b/config/locales/doorkeeper.gl.yml @@ -6,7 +6,7 @@ gl: name: Nome do aplicativo redirect_uri: URI a redireccionar scopes: Permisos - website: Sitio web do aplicativo + website: Sitio web da aplicación errors: models: doorkeeper/application: @@ -27,31 +27,32 @@ gl: confirmations: destroy: Está segura? edit: - title: Editar aplicativo + title: Editar aplicación form: - error: Eeeeepa! Comprobe os posibles erros no formulario + error: Eeeeepa! Comproba os posibles erros no formulario help: - native_redirect_uri: Utilice %{native_redirect_uri} para probas locais - redirect_uri: Utilice unha liña por URI - scopes: Separar permisos con espazos. Deixar en blanco para utilizar os permisos por omisión. + native_redirect_uri: Utiliza %{native_redirect_uri} para probas locais + redirect_uri: Utiliza unha liña por URI + scopes: Separar permisos con espazos. Deixar en branco para utilizar os permisos por omisión. index: - application: Aplicativo + application: Aplicación callback_url: URL de chamada delete: Eliminar + empty: Non tes aplicacións. name: Nome - new: Novo aplicativo - scopes: Permisos + new: Nova aplicación + scopes: Ámbitos show: Mostrar - title: Os seus aplicativos + title: As túas aplicacións new: - title: Novo aplicativo + title: Nova aplicación show: actions: Accións application_id: Chave do cliente callback_urls: URLs de chamada - scopes: Permisos - secret: Chave secreta do cliente - title: 'Aplicativo: %{name}' + scopes: Ámbitos + secret: Chave segreda do cliente + title: 'Aplicación: %{name}' authorizations: buttons: authorize: Autorizar @@ -60,21 +61,21 @@ gl: title: Algo fallou new: able_to: Poderá - prompt: O aplicativo %{client_name} solicita acceso a súa conta + prompt: A aplicación %{client_name} solicita acceso a túa conta title: Autorización necesaria show: - title: Copie este código de autorización e pégueo no aplicativo. + title: Copia este código de autorización e pégao na aplicación. authorized_applications: buttons: revoke: Retirar autorización confirmations: - revoke: Está segura? + revoke: Estás segura? index: - application: Aplicativo + application: Aplicación created_at: Autorizado date_format: "%d-%m-%Y %H:%M:%S" - scopes: Permisos - title: Os seus aplicativos autorizados + scopes: Ámbitos + title: As túas aplicacións autorizadas errors: messages: access_denied: O propietario do recurso ou o servidor autorizado denegaron a petición. diff --git a/config/locales/doorkeeper.hu.yml b/config/locales/doorkeeper.hu.yml index 61e7dd5f12..32709299fd 100644 --- a/config/locales/doorkeeper.hu.yml +++ b/config/locales/doorkeeper.hu.yml @@ -38,6 +38,7 @@ hu: application: Alkalmazás callback_url: Callback URL delete: Eltávolítás + empty: Nincsenek alkalmazásaid. name: Név new: Új alkalmazás scopes: Hatáskör diff --git a/config/locales/doorkeeper.id.yml b/config/locales/doorkeeper.id.yml index efaeaae169..8403904818 100644 --- a/config/locales/doorkeeper.id.yml +++ b/config/locales/doorkeeper.id.yml @@ -38,6 +38,7 @@ id: application: Aplikasi callback_url: URL Callback delete: Hapus + empty: Anda tidak memiliki aplikasi. name: Nama new: Aplikasi baru scopes: Cakupan diff --git a/config/locales/doorkeeper.is.yml b/config/locales/doorkeeper.is.yml index 31c4bca9f9..0d15479c5d 100644 --- a/config/locales/doorkeeper.is.yml +++ b/config/locales/doorkeeper.is.yml @@ -38,6 +38,7 @@ is: application: Forrit callback_url: URL-slóð baksvörunar (callback) delete: Eyða + empty: Þú ert ekki með nein forrit. name: Heiti new: Nýtt forrit scopes: Gildissvið @@ -78,12 +79,22 @@ is: errors: messages: access_denied: Eigandi tilfangs eða auðkenningarþjónn höfnuðu beininni. + credential_flow_not_configured: Flæði á lykilorðsauðkennum eiganda tilfangs (Resource Owner) brást vegna þess að Doorkeeper.configure.resource_owner_from_credentials er óskilgreint. + invalid_client: Auðkenning á biðlara brást vegna þess að biðlarinn er óþekktur, að auðkenning biðlarans fylgdi ekki með, eða að notuð var óstudd auðkenningaraðferð. + invalid_grant: Uppgefin auðkenningarheimild er ógild, útrunnin, afturkölluð, samsvarar ekki endurbirtingarslóðinni í auðkenningarbeiðninni, eða var gefin út til annars biðlara. invalid_redirect_uri: Endurbeiningarslóðin sem fylgdi er ekki gild. + invalid_request: Í beiðnina vantar nauðsynlega færibreytu, hún inniheldur óleyfilegt gildi á færibreytu, eða er gölluð á einhvern annan hátt. + invalid_resource_owner: Uppgefin auðkenni eiganda tilfangs eru ekki gild, eða að eigandi tilfangs finnst ekki invalid_scope: Umbeðið gildissvið er ógilt, óþekkt eða rangt uppsett. invalid_token: expired: Auðkenningarteiknið er útrunnið revoked: Auðkenningarteiknið var aturkallað unknown: Auðkenningarteiknið er ógilt + resource_owner_authenticator_not_configured: Leit að eiganda tilfangs (Resource Owner) brást vegna þess að Doorkeeper.configure.resource_owner_authenticator er óskilgreint. + server_error: Auðkenningarþjónninn rakst á óvænt skilyrði sem kom í veg fyrir að hægt væri að uppfylla beiðnina. + temporarily_unavailable: Auðkenningarþjónninn hefur ekki tök á að meðhöndla beiðnina vegna of mikils tímabundins álags eða viðhalds á vefþvóninum. + unauthorized_client: Biðlaraforritið hefur ekki heimild til að framkvæma beiðnina með þessari aðferð. + unsupported_grant_type: Þessi gerð auðkenningaraðferðar er ekki studd af auðkenningarþjóninum. unsupported_response_type: Auðkenningarþjónninn styður ekki þessa tegund svars. flash: applications: diff --git a/config/locales/doorkeeper.it.yml b/config/locales/doorkeeper.it.yml index 122b38c04a..68e2b57f3e 100644 --- a/config/locales/doorkeeper.it.yml +++ b/config/locales/doorkeeper.it.yml @@ -38,6 +38,7 @@ it: application: Applicazione callback_url: URL di callback delete: Elimina + empty: Non hai applicazioni. name: Nome new: Nuova applicazione scopes: Visibilità diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml index 67eadbf2d2..73932bafd6 100644 --- a/config/locales/doorkeeper.ja.yml +++ b/config/locales/doorkeeper.ja.yml @@ -38,6 +38,7 @@ ja: application: アプリ callback_url: コールバックURL delete: 削除 + empty: アプリはありません name: 名前 new: 新規アプリ scopes: アクセス権 @@ -125,7 +126,7 @@ ja: read: アカウントのすべてのデータの読み取り read:accounts: アカウント情報の読み取り read:blocks: ブロックの読み取り - read:bookmarks: ブックマークを見る + read:bookmarks: ブックマークの読み取り read:favourites: お気に入りの読み取り read:filters: フィルターの読み取り read:follows: フォローの読み取り diff --git a/config/locales/doorkeeper.kk.yml b/config/locales/doorkeeper.kk.yml index 2c3346b6eb..75f8de5429 100644 --- a/config/locales/doorkeeper.kk.yml +++ b/config/locales/doorkeeper.kk.yml @@ -38,6 +38,7 @@ kk: application: Қосымша callback_url: Callbаck URL delete: Өшіру + empty: Сізде ешқандай қосымша жоқ. name: Аты new: Жаңа қосымша scopes: Scopеs diff --git a/config/locales/doorkeeper.ko.yml b/config/locales/doorkeeper.ko.yml index 3f9e128574..6f4192ebea 100644 --- a/config/locales/doorkeeper.ko.yml +++ b/config/locales/doorkeeper.ko.yml @@ -38,6 +38,7 @@ ko: application: 애플리케이션 callback_url: 콜백 URL delete: 삭제 + empty: 어플리케이션이 없습니다. name: 이름 new: 새 애플리케이션 scopes: 범위 diff --git a/config/locales/doorkeeper.pt-BR.yml b/config/locales/doorkeeper.pt-BR.yml index 215c8795d0..90d8f93580 100644 --- a/config/locales/doorkeeper.pt-BR.yml +++ b/config/locales/doorkeeper.pt-BR.yml @@ -38,6 +38,7 @@ pt-BR: application: Aplicativos callback_url: Link de retorno delete: Excluir + empty: Não tem aplicações. name: Nome new: Novo aplicativo scopes: Autorizações diff --git a/config/locales/doorkeeper.pt-PT.yml b/config/locales/doorkeeper.pt-PT.yml index e23310a187..2433f23e9c 100644 --- a/config/locales/doorkeeper.pt-PT.yml +++ b/config/locales/doorkeeper.pt-PT.yml @@ -38,6 +38,7 @@ pt-PT: application: Aplicações callback_url: URL de retorno delete: Eliminar + empty: Não tem aplicações. name: Nome new: Nova Aplicação scopes: Autorizações diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml index f04a1306d4..532e2c9aca 100644 --- a/config/locales/doorkeeper.ru.yml +++ b/config/locales/doorkeeper.ru.yml @@ -38,6 +38,7 @@ ru: application: Приложение callback_url: Callback URL delete: Удалить + empty: У вас нет созданных приложений. name: Название new: Новое приложение scopes: Разрешения diff --git a/config/locales/doorkeeper.sv.yml b/config/locales/doorkeeper.sv.yml index af98020c1a..d9367ce5ea 100644 --- a/config/locales/doorkeeper.sv.yml +++ b/config/locales/doorkeeper.sv.yml @@ -38,6 +38,7 @@ sv: application: Applikation callback_url: Återkalls URL delete: Ta bort + empty: Du har inga program. name: Namn new: Ny applikation scopes: Omfattning diff --git a/config/locales/doorkeeper.th.yml b/config/locales/doorkeeper.th.yml index 33d6944f0b..597a65038d 100644 --- a/config/locales/doorkeeper.th.yml +++ b/config/locales/doorkeeper.th.yml @@ -36,6 +36,7 @@ th: application: แอปพลิเคชัน callback_url: URL เรียกกลับ delete: ลบ + empty: คุณไม่มีแอปพลิเคชัน name: ชื่อ new: แอปพลิเคชันใหม่ scopes: ขอบเขต diff --git a/config/locales/doorkeeper.tr.yml b/config/locales/doorkeeper.tr.yml index b4362f2a27..a218e31575 100644 --- a/config/locales/doorkeeper.tr.yml +++ b/config/locales/doorkeeper.tr.yml @@ -38,6 +38,7 @@ tr: application: Uygulama callback_url: Geri Dönüş URL delete: Sil + empty: Hiç uygulamanız yok. name: İsim new: Yeni uygulama scopes: Kapsam diff --git a/config/locales/el.yml b/config/locales/el.yml index 97ae2bd6cd..53d4755238 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -339,13 +339,11 @@ el: delete: Διαγραφή destroyed_msg: Επιτυχής διαγραφή email τομέα από τη μαύρη λίστα domain: Τομέας + empty: Δεν έχουν οριστεί αποκλεισμένοι τομείς email. new: create: Πρόσθεση τομέα title: Νέα εγγραφή email στη μαύρη λίστα title: Μαύρη λίστα email - followers: - back_to_account: Επιστροφή στον λογαριασμό - title: Ακόλουθοι του/της %{acct} instances: by_domain: Τομέας delivery_available: Διαθέσιμη παράδοση @@ -920,6 +918,7 @@ el: duration_too_long: είναι πολύ μακριά στο μέλλον duration_too_short: είναι πολύ σύντομα expired: Η ψηφοφορία έχει ήδη λήξει + invalid_choice: Αυτή η επιλογή ψήφου δεν υπάρχει over_character_limit: δε μπορεί να υπερβαίνει τους %{max} χαρακτήρες έκαστη too_few_options: πρέπει να έχει περισσότερες από μια επιλογές too_many_options: δεν μπορεί να έχει περισσότερες από %{max} επιλογές diff --git a/config/locales/en.yml b/config/locales/en.yml index 8262a58687..18a96ccbc1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -198,11 +198,13 @@ en: change_email_user: "%{name} changed the e-mail address of user %{target}" confirm_user: "%{name} confirmed e-mail address of user %{target}" create_account_warning: "%{name} sent a warning to %{target}" + create_announcement: "%{name} created new announcement %{target}" create_custom_emoji: "%{name} uploaded new emoji %{target}" create_domain_allow: "%{name} whitelisted domain %{target}" create_domain_block: "%{name} blocked domain %{target}" create_email_domain_block: "%{name} blacklisted e-mail domain %{target}" demote_user: "%{name} demoted user %{target}" + destroy_announcement: "%{name} deleted announcement %{target}" destroy_custom_emoji: "%{name} destroyed emoji %{target}" destroy_domain_allow: "%{name} removed domain %{target} from whitelist" destroy_domain_block: "%{name} unblocked domain %{target}" @@ -224,10 +226,22 @@ en: unassigned_report: "%{name} unassigned report %{target}" unsilence_account: "%{name} unsilenced %{target}'s account" unsuspend_account: "%{name} unsuspended %{target}'s account" + update_announcement: "%{name} updated announcement %{target}" update_custom_emoji: "%{name} updated emoji %{target}" update_status: "%{name} updated status by %{target}" deleted_status: "(deleted status)" title: Audit log + announcements: + edit: + title: Edit announcement + empty: No announcements found. + live: Live + new: + create: Create announcement + title: New announcement + published: Published + time_range: Time range + title: Announcements custom_emojis: assign_category: Assign category by_domain: Domain @@ -345,9 +359,6 @@ en: create: Add domain title: New e-mail blacklist entry title: E-mail blacklist - followers: - back_to_account: Back To Account - title: "%{acct}'s Followers" instances: by_domain: Domain delivery_available: Delivery is available @@ -376,6 +387,8 @@ en: title: Invites pending_accounts: title: Pending accounts (%{count}) + relationships: + title: "%{acct}'s relationships" relays: add_new: Add new relay delete: Delete @@ -671,6 +684,9 @@ en: hint_html: "Tip: We won't ask you for your password again for the next hour." invalid_password: Invalid password prompt: Confirm password to continue + date: + formats: + default: "%b %d, %Y" datetime: distance_in_words: about_x_hours: "%{count}h" @@ -747,6 +763,7 @@ en: hint_html: "What are featured hashtags? They are displayed prominently on your public profile and allow people to browse your public posts specifically under those hashtags. They are a great tool for keeping track of creative works or long-term projects." filters: contexts: + account: Profiles home: Home timeline notifications: Notifications public: Public timelines @@ -771,6 +788,8 @@ en: all: All changes_saved_msg: Changes successfully saved! copy: Copy + delete: Delete + edit: Edit no_batch_actions_available: No batch actions available on this page order_by: Order by save_changes: Save changes @@ -944,11 +963,15 @@ en: other: Other posting_defaults: Posting defaults public_timelines: Public timelines + reactions: + errors: + unrecognized_emoji: is not a recognized emoji relationships: activity: Account activity dormant: Dormant followers: Followers following: Following + invited: Invited last_active: Last active most_recent: Most recent moved: Moved @@ -1013,7 +1036,7 @@ en: firefox_os: Firefox OS ios: iOS linux: Linux - mac: Mac + mac: macOS other: unknown platform windows: Windows windows_mobile: Windows Mobile diff --git a/config/locales/eo.yml b/config/locales/eo.yml index f9374272f3..fb4d5c8bec 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -11,6 +11,7 @@ eo: apps: Poŝtelefonaj aplikaĵoj apps_platforms: Uzu Mastodon ĉe iOS, Android kaj aliajn platformojn browse_directory: Esplori profilujo kaj filtri per interesoj + browse_local_posts: Vidi vivantan fluon de publikaj mesaĝoj al Mastodon browse_public_posts: Vidi vivantan fluon de publikaj mesaĝoj al Mastodon contact: Kontakti contact_missing: Ne elektita @@ -267,6 +268,7 @@ eo: features: Funkcioj hidden_service: Federacio kun kaŝitaj servoj open_reports: nesolvitaj signaloj + pending_tags: kradvortoj atendantaj revizion pending_users: uzantoj atendantaj revizion recent_users: Lastatempaj uzantoj search: Tutteksta serĉado @@ -333,9 +335,6 @@ eo: create: Aldoni domajnon title: Nova blokado de retadresa domajno title: Nigra listo de retadresaj domajnoj - followers: - back_to_account: Reen al la konto - title: Sekvantoj de %{acct} instances: by_domain: Domajno delivery_available: Liverado disponeblas diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index da1ad126f8..f298cfbf59 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -344,9 +344,6 @@ es-AR: create: Agregar dominio title: Nueva desaprobación de correo electrónico title: Desaprobación de correo electrónico - followers: - back_to_account: Volver a la cuenta - title: Seguidores de %{acct} instances: by_domain: Dominio delivery_available: La entrega está disponible @@ -375,6 +372,8 @@ es-AR: title: Invitaciones pending_accounts: title: Cuentas pendientes (%{count}) + relationships: + title: Relaciones de %{acct} relays: add_new: Agregar nuevo relé delete: Eliminar @@ -734,6 +733,7 @@ es-AR: hint_html: "¿Qué son las etiquetas destacadas? Se muestran de forma prominente en tu perfil público y permiten a los usuarios navegar por tus toots públicos específicamente bajo esas etiquetas. Son una gran herramienta para hacer un seguimiento de trabajos creativos o proyectos a largo plazo." filters: contexts: + account: Perfiles home: Línea temporal principal notifications: Notificaciones public: Líneas temporales públicas @@ -934,6 +934,7 @@ es-AR: dormant: Inactivas followers: Seguidores following: Siguiendo + invited: Invitado last_active: Última actividad most_recent: Más reciente moved: Mudada diff --git a/config/locales/es.yml b/config/locales/es.yml index 113ea36724..cc8857bfd5 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -7,7 +7,7 @@ es: active_count_after: activo active_footnote: Usuarios Activos Mensuales (UAM) administered_by: 'Administrado por:' - api: API + api: Interfaz de Programación de la Aplicación apps: Aplicaciones móviles apps_platforms: Utiliza Mastodonte desde iOS, Android y otras plataformas browse_directory: Navega por el directorio de perfiles y filtra por intereses @@ -344,9 +344,6 @@ es: create: Añadir dominio title: Nueva entrada en la lista negra de correo title: Lista negra de correo - followers: - back_to_account: Volver a la cuenta - title: Seguidores de %{acct} instances: by_domain: Dominio delivery_available: Entrega disponible @@ -375,6 +372,8 @@ es: title: Invitaciones pending_accounts: title: Cuentas pendientes (%{count}) + relationships: + title: Relaciones de %{acct} relays: add_new: Añadir un nuevo relés delete: Borrar @@ -486,7 +485,7 @@ es: open: Cualquiera puede registrarse title: Modo de registros show_known_fediverse_at_about_page: - desc_html: Cuando esté desactivado, mostrará solamente la cronología local, y no la federada + desc_html: Cuando esté activado, se mostrarán toots de todo el fediverso conocido en la vista previa. En otro caso, se mostrarán solamente toots locales. title: Mostrar fediverso conocido en la vista previa de la cronología show_staff_badge: desc_html: Mostrar un parche de staff en la página de un usuario @@ -587,7 +586,7 @@ es: guide_link: https://es.crowdin.com/project/mastodon guide_link_text: Todos pueden contribuir. sensitive_content: Contenido sensible - toot_layout: Diseño para barritar + toot_layout: Diseño de los toots application_mailer: notification_preferences: Cambiar preferencias de correo electrónico salutation: "%{name}," @@ -734,6 +733,7 @@ es: hint_html: "¿Qué son las etiquetas destacadas? Se muestran de forma prominente en tu perfil público y permiten a las personas usuarias navegar por tus publicaciones públicas específicamente bajo esas etiquetas. Son una gran herramienta para hacer un seguimiento de obras creativas o proyectos a largo plazo." filters: contexts: + account: Perfiles home: Cronología propia notifications: Notificaciones public: Cronología pública @@ -780,7 +780,7 @@ es: i_am_html: Soy %{username} en %{service}. identity: Identidad inactive: Inactivo - publicize_checkbox: 'Y barrite esto:' + publicize_checkbox: 'Y tootee esto:' publicize_toot: "¡Comprobado! Soy %{username} en %{service}: %{url}" status: Estado de la verificación view_proof: Ver prueba @@ -934,6 +934,7 @@ es: dormant: Inactivo followers: Seguidores following: Siguiendo + invited: Invitado last_active: Última actividad most_recent: Más reciente moved: Movido @@ -954,16 +955,16 @@ es: remote_interaction: favourite: proceed: Proceder a marcar como favorito - prompt: 'Quieres marcar como favorito este bramido:' + prompt: 'Quieres marcar como favorito este toot:' reblog: - proceed: Proceder a rebarritar - prompt: 'Quieres rebarritar este bramido:' + proceed: Proceder a retootear + prompt: 'Quieres retootear este toot:' reply: proceed: Proceder a responder - prompt: 'Quieres responder a este bramido:' + prompt: 'Quieres responder a este toot:' scheduled_statuses: - over_daily_limit: Ha superado el límite de %{limit} bramidos programados para ese día - over_total_limit: Ha superado el límite de %{limit} bramidos programados + over_daily_limit: Ha superado el límite de %{limit} toots programados para ese día + over_total_limit: Ha superado el límite de %{limit} toots programados too_soon: La fecha programada debe estar en el futuro sessions: activity: Última actividad @@ -1190,8 +1191,8 @@ es: warning: explanation: disable: Mientras su cuenta esté congelada, la información de su cuenta permanecerá intacta, pero no puede realizar ninguna acción hasta que se desbloquee. - silence: Mientras su cuenta está limitada, sólo las personas que ya le están siguiendo verán sus bramidos en este servidor, y puede que se le excluya de varios listados públicos. Sin embargo, otros pueden seguirle manualmente. - suspend: Su cuenta ha sido suspendida, y todos tus bramidos y tus archivos multimedia subidos han sido irreversiblemente eliminados de este servidor, y de los servidores donde tenías seguidores. + silence: Mientras su cuenta está limitada, sólo las personas que ya le están siguiendo verán sus toots en este servidor, y puede que se le excluya de varios listados públicos. Sin embargo, otros pueden seguirle manualmente. + suspend: Su cuenta ha sido suspendida, y todos tus toots y tus archivos multimedia subidos han sido irreversiblemente eliminados de este servidor, y de los servidores donde tenías seguidores. get_in_touch: Puede responder a esta dirección de correo electrónico para ponerse en contacto con el personal de %{instance}. review_server_policies: Revisar las políticas del servidor statuses: 'Específicamente, para:' diff --git a/config/locales/et.yml b/config/locales/et.yml index 16e80ae353..e0e861c336 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -347,9 +347,6 @@ et: create: Lisa domeen title: Uus e-posti keelunimekirja sisend title: E-posti keelunimekiri - followers: - back_to_account: Tagasi minu kontole - title: "%{acct}-i jälgijad" instances: by_domain: Domeen delivery_available: Üleandmine on saadaval @@ -378,6 +375,8 @@ et: title: Kutsed pending_accounts: title: Ootel olevad kasutajad (%{count}) + relationships: + title: "%{acct}-i suhted" relays: add_new: Lisa uus relee delete: Kustuta @@ -737,6 +736,7 @@ et: hint_html: "Mis on esile toodud sildid? Need sildid näidatakse nähtavalt Teie avalikul profiilil ning nad aitavad inimestel leida postitusi, millel on antud sildid. Nad on hea viis kuidas näiteks hoida järge loovtöödel või pikaajalistel projektidel." filters: contexts: + account: Profiilid home: Kodu ajajoon notifications: Teated public: Avalikud ajajooned @@ -937,6 +937,7 @@ et: dormant: Seisev followers: Jälgijad following: Jälgib + invited: Kutsutud last_active: Viimati aktiivne most_recent: Viimased moved: Kolinud diff --git a/config/locales/eu.yml b/config/locales/eu.yml index bfa91beb89..999772cfee 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -344,9 +344,6 @@ eu: create: Gehitu domeinua title: Sarrera berria e-mail zerrenda beltzean title: E-mail zerrenda beltza - followers: - back_to_account: Itzuli kontura - title: "%{acct} kontuaren jarraitzaileak" instances: by_domain: Domeinua delivery_available: Bidalketa eskuragarri dago @@ -921,6 +918,7 @@ eu: duration_too_long: etorkizunean urrunegi dago duration_too_short: goizegi da expired: Inkesta amaitu da jada + invalid_choice: Hautatutako boto aukera ez da existitzen over_character_limit: bakoitzak gehienez %{max} karaktere izan ditzake too_few_options: elementu bat baino gehiago izan behar du too_many_options: ezin ditu %{max} elementu baino gehiago izan diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 81c163f7f0..ecf3bc391a 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -346,9 +346,6 @@ fa: create: ساختن مسدودسازی title: مسدودسازی دامین ایمیل تازه title: مسدودسازی دامین‌های ایمیل - followers: - back_to_account: بازگشت به حساب - title: پیگیران %{acct} instances: by_domain: دامین delivery_available: پیام آماده است diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 9c8f56889e..09a5994941 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -198,11 +198,13 @@ fr: change_email_user: "%{name} a modifié l’adresse de courriel de l’utilisateur·rice %{target}" confirm_user: "%{name} adresse courriel confirmée de l’utilisateur·ice %{target}" create_account_warning: "%{name} a envoyé un avertissement à %{target}" + create_announcement: "%{name} a créé une nouvelle annonce %{target}" create_custom_emoji: "%{name} a importé de nouveaux émojis %{target}" create_domain_allow: "%{name} a inscrit le domaine %{target} sur liste blanche" create_domain_block: "%{name} a bloqué le domaine %{target}" create_email_domain_block: "%{name} a mis le domaine de courriel %{target} sur liste noire" demote_user: "%{name} a rétrogradé l’utilisateur·ice %{target}" + destroy_announcement: "%{name} a supprimé l’annonce %{target}" destroy_custom_emoji: "%{name} a détruit l’émoticône %{target}" destroy_domain_allow: "%{name} a supprimé le domaine %{target} de la liste blanche" destroy_domain_block: "%{name} a débloqué le domaine %{target}" @@ -224,10 +226,22 @@ fr: unassigned_report: "%{name} a désassigné le signalement %{target}" unsilence_account: "%{name} a mis fin au mode silence de %{target}" unsuspend_account: "%{name} a réactivé le compte de %{target}" + update_announcement: "%{name} a actualisé l’annonce %{target}" update_custom_emoji: "%{name} a mis à jour l’émoji %{target}" update_status: "%{name} a mis à jour le statut de %{target}" deleted_status: "(statut supprimé)" title: Journal d’audit + announcements: + edit: + title: Modifier l’annonce + empty: Aucune annonce trouvée. + live: En direct + new: + create: Créer une annonce + title: Nouvelle annonce + published: Publié + time_range: Intervalle de temps + title: Annonces custom_emojis: assign_category: Attribuer une catégorie by_domain: Domaine @@ -344,9 +358,6 @@ fr: create: Créer le blocage title: Nouveau blocage de domaine de courriel title: Blocage de domaines de courriel - followers: - back_to_account: Retour au compte - title: Abonné⋅e⋅s de %{acct} instances: by_domain: Domaine delivery_available: Livraison disponible @@ -375,6 +386,8 @@ fr: title: Invitations pending_accounts: title: Comptes en attente (%{count}) + relationships: + title: Relations de %{acct} relays: add_new: Ajouter un nouveau relais delete: Effacer @@ -658,6 +671,9 @@ fr: hint_html: "Astuce : Nous ne vous demanderons plus votre mot de passe pour la prochaine heure." invalid_password: Mot de passe invalide prompt: Confirmez votre mot de passe pour continuer + date: + formats: + default: "%d %b %Y" datetime: distance_in_words: about_x_hours: "%{count} h" @@ -734,6 +750,7 @@ fr: hint_html: "Que sont les hashtags vedettes ? Ils sont affichés avec emphase sur votre flux d'actualités publique et permettent aux gens de parcourir vos messages publics spécifiquement sous ces hashtags. Ils sont un excellent outil pour garder trace des œuvres créatives ou des projets à long terme." filters: contexts: + account: Profils home: Accueil notifications: Notifications public: Fils publics @@ -758,6 +775,8 @@ fr: all: Tous changes_saved_msg: Les modifications ont été enregistrées avec succès ! copy: Copier + delete: Supprimer + edit: Modifier no_batch_actions_available: Aucune action par lots disponible sur cette page order_by: Classer par save_changes: Enregistrer les modifications @@ -929,11 +948,15 @@ fr: other: Autre posting_defaults: Paramètres par défaut des pouets public_timelines: Fils publics + reactions: + errors: + unrecognized_emoji: n’est pas un émoji reconnu relationships: activity: Activité du compte dormant: Dormant followers: Abonné·e·s following: Abonnements + invited: Invité·e last_active: Dernière activité most_recent: Plus récent moved: Déménagé diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 7e8776a5b0..9fa44456ed 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -2,26 +2,26 @@ gl: about: about_hashtag_html: Estes son toots públicos etiquetados con #%{hashtag}. Podes interactuar con eles se tes unha conta nalgures do fediverso. - about_mastodon_html: O Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada coma o correo electrónico! + about_mastodon_html: Mastodon é unha rede social que se basea en protocolos web abertos e libres, software de código aberto. É descentralizada como o correo electrónico. about_this: Acerca de - active_count_after: activo - active_footnote: Usuarios Activos Mensuais (UAM) - administered_by: 'Administrado por:' + active_count_after: activas + active_footnote: Usuarias Activas no Mes (UAM) + administered_by: 'Administrada por:' api: API apps: Aplicacións móbiles - apps_platforms: Emprega o Mastodon dende iOS, Android e outras plataformas + apps_platforms: Emprega Mastodon dende iOS, Android e outras plataformas browse_directory: Navega polo directorio de perfís e filtra por intereses browse_local_posts: Navega polas publicacións públicas deste servidor en tempo real - browse_public_posts: Navega polas publicacións públicas do Mastodon en tempo real + browse_public_posts: Navega polas publicacións públicas de Mastodon en tempo real contact: Contacto - contact_missing: Non estabelecido + contact_missing: Non establecido contact_unavailable: Non dispoñíbel - discover_users: Descobrir usuarios + discover_users: Descubrir usuarias documentation: Documentación federation_hint_html: Cunha conta en %{instance} poderás seguir ás persoas en calquera servidor do Mastodon e alén. get_apps: Probar unha aplicación móbil - hosted_on: O Mastodon está aloxado en %{domain} - instance_actor_flash: 'Esta conta é un actor virtual empregado para representar ó servidor e non a un usuario individual. Utilízase para propósitos de federación e non debería estar bloqueada a menos que queiras bloquear a toda a instancia, en tal caso deberías empregar o bloqueo de dominio. + hosted_on: Mastodon aloxado en %{domain} + instance_actor_flash: 'Esta conta é un actor virtual utilizado para representar ao servidor e non a unha usuaria individual. Utilízase para propósitos de federación e non debería estar bloqueada a menos que queiras bloquear a toda a instancia, en tal caso deberías utilizar o bloqueo do dominio. ' learn_more: Saber máis @@ -32,7 +32,7 @@ gl: status_count_after: one: estado other: estados - status_count_before: Que escribiron + status_count_before: Que publicaron tagline: Segue ás túas amizades e coñece novas terms: Termos do servizo unavailable_content: Contido non dispoñíbel @@ -40,22 +40,22 @@ gl: domain: Servidor reason: Razón rejecting_media: 'Os ficheiros multimedia deste servidor non serán procesados e non se amosarán miniaturas, o que require un clic manual no ficheiro orixinal:' - silenced: 'As publicacións deste servidor non se amosarán en ningún lugar agás que as sigas:' - suspended: 'Non se procesarán, almacenarán nin intercambiarán datos destes servidores, o que fai imposíbel calquera interacción ou comunicación cos usuarios dende estes servidores:' - unavailable_content_html: O Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar cos seus usuarios. Estas son as excepcións que se estabeleceron neste servidor en particular. + silenced: 'As publicacións deste servidor non se amosarán en conversas e liñas temporais, nin terás notificacións das súas usuarias agás que as sigas:' + suspended: 'Non se procesarán, almacenarán nin intercambiarán datos destes servidores, o que fai imposíbel calquera interacción ou comunicación coas usuarias dende estes servidores:' + unavailable_content_html: O Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar coas súas usuarias. Estas son as excepcións que se estabeleceron neste servidor en particular. user_count_after: - one: usuario - other: usuarios + one: usuaria + other: usuarias user_count_before: Fogar de - what_is_mastodon: Que é o Mastodon? + what_is_mastodon: Qué é Mastodon? accounts: - choices_html: 'Suxestións de %{name}:' + choices_html: 'Escollas de %{name}:' endorsements_hint: Podes suxerir a persoas que segues dende a interface web, e amosaranse aquí. - featured_tags_hint: Podes destacar determinados cancelos (hashtags) que se amosarán aquí. + featured_tags_hint: Podes destacar determinadas etiquetas que se amosarán aquí. follow: Seguir followers: - one: Seguidor - other: Seguidores + one: Seguidora + other: Seguidoras following: Seguindo joined: Uniuse en %{date} last_active: última actividade @@ -74,18 +74,18 @@ gl: other: Toots posts_tab_heading: Toots posts_with_replies: Toots e respostas - reserved_username: O nome de usuario está reservado + reserved_username: O nome de usuaria está reservado roles: - admin: Administrador + admin: Administradora bot: Bot group: Grupo - moderator: Moderador + moderator: Moderadora unavailable: Perfil non dispoñíbel unfollow: Deixar de seguir admin: account_actions: action: Executar acción - title: Executar acción de moderación en %{acct} + title: Executar acción de moderación a %{acct} account_moderation_notes: create: Deixar nota created_msg: Nota de moderación creada de xeito correcto! @@ -94,7 +94,7 @@ gl: accounts: approve: Aprobar approve_all: Aprobar todos - are_you_sure: Estás seguro? + are_you_sure: Está segura? avatar: Imaxe de perfil by_domain: Dominio change_email: @@ -110,7 +110,7 @@ gl: deleted: Eliminado demote: Rebaixar disable: Desactivar - disable_two_factor_authentication: Desactivar dobre factor + disable_two_factor_authentication: Desactivar 2FA disabled: Desactivado display_name: Nome a amosar domain: Dominio @@ -119,13 +119,13 @@ gl: email_status: Estado do email enable: Activar enabled: Activado - followers: Seguidores + followers: Seguidoras follows: Seguindo header: Cabeceira inbox_url: URL da caixa de entrada - invited_by: Convidado por + invited_by: Convidada por ip: IP - joined: Unido + joined: Uniuse location: all: Todo local: Local @@ -135,7 +135,7 @@ gl: media_attachments: Multimedia adxunta memorialize: Converter en lembranza moderation: - active: Activo + active: Activa all: Todo pending: Pendente silenced: Silenciados @@ -159,7 +159,7 @@ gl: remove_avatar: Eliminar imaxe de perfil remove_header: Eliminar cabeceira resend_confirmation: - already_confirmed: Este usuario xa está confirmado + already_confirmed: Esta usuaria xa está confirmada send: Reenviar o email de confirmación success: Email de confirmación enviado de xeito correcto! reset: Restabelecer @@ -170,9 +170,9 @@ gl: admin: Administrador moderator: Moderador staff: Persoal (staff) - user: Usuario + user: Usuaria search: Procurar - search_same_ip: Outros usuarios co mesmo IP + search_same_ip: Outras usuarias co mesmo IP shared_inbox_url: URL da caixa de entrada compartida show: created_reports: Denuncias feitas @@ -188,46 +188,60 @@ gl: undo_silenced: Desfacer silencio undo_suspension: Desfacer suspensión unsubscribe: Desbotar a subscrición - username: Nome de usuario + username: Nome de usuaria warn: Aviso web: Web whitelisted: Listaxe branca action_logs: actions: assigned_to_self_report: "%{name} atribuíu a denuncia %{target} a el mesmo" - change_email_user: "%{name} mudou o enderezo de email do usuario %{target}" - confirm_user: "%{name} confirmou o enderezo de email do usuario %{target}" + change_email_user: "%{name} cambiou o enderezo de correo-e da usuaria %{target}" + confirm_user: "%{name} comfirmou o enderezo de correo da usuaria %{target}" create_account_warning: "%{name} enviou un aviso a %{target}" + create_announcement: "%{name} creou un novo anuncio %{target}" create_custom_emoji: "%{name} subiu unha nova emoticona %{target}" create_domain_allow: "%{name} engadiu á listaxe branca o dominio %{target}" create_domain_block: "%{name} bloqueou o dominio %{target}" create_email_domain_block: "%{name} engadiu á listaxe negra o dominio de email %{target}" - demote_user: "%{name} rebaixou ó usuario %{target}" + demote_user: "%{name} degradou a usuaria %{target}" + destroy_announcement: "%{name} eliminou o anuncio %{target}" destroy_custom_emoji: "%{name} eliminou a emoticona %{target}" destroy_domain_allow: "%{name} eliminou o dominio %{target} da listaxe branca" destroy_domain_block: "%{name} desbloqueou o dominio %{target}" destroy_email_domain_block: "%{name} engadiu á lista branca o dominio de email %{target}" destroy_status: "%{name} eliminou o estado de %{target}" - disable_2fa_user: "%{name} desactivou o requirimento de dobre factor para o usuario %{target}" + disable_2fa_user: "%{name} desactivou o requirimento de dobre factor para a usuaria %{target}" disable_custom_emoji: "%{name} desactivou a emoticona %{target}" - disable_user: "%{name} desactivou o acceso á conta para o usuario %{target}" + disable_user: "%{name} desactivou o acceso á conta para a usuaria %{target}" enable_custom_emoji: "%{name} activou a emoticona %{target}" - enable_user: "%{name} activou o acceso á conta para o usuario %{target}" + enable_user: "%{name} activou o acceso á conta para a usuaria %{target}" memorialize_account: "%{name} converteu a conta de %{target} nunha páxina para a lembranza" - promote_user: "%{name} promocionou o usuario %{target}" + promote_user: "%{name} promoveu a usuaria %{target}" remove_avatar_user: "%{name} eliminou a imaxe de perfil de %{target}" reopen_report: "%{name} reabriu a denuncia %{target}" - reset_password_user: "%{name} restabeleceu o contrasinal do usuario %{target}" + reset_password_user: "%{name} restableceu o contrasinal da usuaria %{target}" resolve_report: "%{name} resolveu a denuncia %{target}" silence_account: "%{name} silenciou a conta de %{target}" suspend_account: "%{name} suspendeu a conta de %{target}" unassigned_report: "%{name} deixou de atribuír a denuncia %{target}" unsilence_account: "%{name} deixou de silenciar a conta de %{target}" unsuspend_account: "%{name} desactivou a suspensión da conta de %{target}" + update_announcement: "%{name} actualizou o anuncio %{target}" update_custom_emoji: "%{name} actualizou a emoticona %{target}" update_status: "%{name} actualizou o estado de %{target}" deleted_status: "(estado eliminado)" title: Rexistro de auditoría + announcements: + edit: + title: Editar anuncio + empty: Ningún anuncio atopado. + live: Ao vivo + new: + create: Crear anuncio + title: Novo anuncio + published: Publicado + time_range: Intre de tempo + title: Anuncios custom_emojis: assign_category: Atribuír categoría by_domain: Dominio @@ -275,18 +289,18 @@ gl: hidden_service: Federación con servizos agochados open_reports: denuncias abertas pending_tags: cancelos agardando revisión - pending_users: usuarios agardando revisión - recent_users: Usuarios recentes + pending_users: usuarias agardando revisión + recent_users: Usuarias recentes search: Procura por texto completo - single_user_mode: Modo de usuario único + single_user_mode: Modo de usuaria única software: Software space: Uso de almacenamento title: Taboleiro - total_users: usuarios en total + total_users: usuarias en total trends: Tendencias week_interactions: interaccións desta semana week_users_active: activos desta semana - week_users_new: usuarios desta semana + week_users_new: usuarias esta semana whitelist_mode: Modo de listaxe branca domain_allows: add_new: Engadir dominio á listaxe branca @@ -344,9 +358,6 @@ gl: create: Engadir dominio title: Nova entrada na listaxe negra de email title: Listaxe negra de email - followers: - back_to_account: Voltar á conta - title: Seguidores de %{acct} instances: by_domain: Dominio delivery_available: Entrega dispoñíbel @@ -375,94 +386,96 @@ gl: title: Convites pending_accounts: title: Contas pendentes (%{count}) + relationships: + title: Relacións de %{acct} relays: add_new: Engadir un novo repetidor delete: Eliminar description_html: Un repetidor da federación é un servidor intermedio que intercambia grandes volumes de toots públicos entre servidores que se suscriban e publiquen nel. Pode axudar a servidores pequenos e medios a descubrir contido no fediverso, o que de outro xeito precisaría que as usuarias locais seguisen a outra xente en servidores remotos. disable: Desactivar - disabled: Desactivada + disabled: Desactivado enable: Activar - enable_hint: Unha vez activado, o seu servidor suscribirase a todos os toots públicos de este servidor, e tamén comezará a eviar a el os toots públicos do servidor. - enabled: Activada + enable_hint: Unha vez activado, o teu servidor subscribirase a todos os toots públicos deste repetidor, e tamén comezará a enviar a el os toots públicos do servidor. + enabled: Activado inbox_url: URL do repetidor - pending: Agardando polo permiso do repetidor + pending: Agardando pola aprobación do repetidor save_and_enable: Gardar e activar - setup: Configurar a conexión ao repetidor - signatures_not_enabled: Os repetidores non funcionarán correctamente se o modo seguro ou lista branca están activados + setup: Configurar unha conexión ó repetidor + signatures_not_enabled: Os repetidores non funcionarán de xeito correcto se o modo seguro ou listaxe branca están activados status: Estado title: Repetidores report_notes: - created_msg: Creouse correctamente a nota do informe! - destroyed_msg: Nota do informe eliminouse con éxito! + created_msg: A nota da denuncia creouse de xeito correcto! + destroyed_msg: A nota da denuncia borrouse de xeito correcto! reports: account: notes: one: "%{count} nota" other: "%{count} notas" reports: - one: "%{count} informe" - other: "%{count} informes" + one: "%{count} denuncia" + other: "%{count} denuncias" action_taken_by: Acción tomada por - are_you_sure: Está segura? - assign_to_self: Asignarmo + are_you_sure: Estás seguro? + assign_to_self: Asignarme assigned: Moderador asignado - by_target_domain: Dominio da conta sobre a que informa + by_target_domain: Dominio da conta denunciada comment: - none: Nada - created_at: Reportado + none: Ningún + created_at: Denunciado mark_as_resolved: Marcar como resolto mark_as_unresolved: Marcar como non resolto notes: create: Engadir nota - create_and_resolve: Resolver con nota - create_and_unresolve: Voltar a abrir con nota + create_and_resolve: Resolver cunha nota + create_and_unresolve: Reabrir cunha nota delete: Eliminar - placeholder: Describe qué medidas foron tomadas, ou calquer outra información relacionada... - reopen: Voltar a abrir o informe - report: 'Informe #%{id}' - reported_account: Conta reportada - reported_by: Reportada por + placeholder: Describir que accións foron tomadas ou calquera outra novidade sobre esta denuncia... + reopen: Reabrir denuncia + report: 'Denuncia #%{id}' + reported_account: Conta denunciada + reported_by: Denunciado por resolved: Resolto - resolved_msg: Resolveuse con éxito o informe! + resolved_msg: Resolveuse con éxito a denuncia! status: Estado - title: Informes + title: Denuncias unassign: Non asignar unresolved: Non resolto updated_at: Actualizado settings: activity_api_enabled: - desc_html: Conta de estados publicados localmente, usuarias activas, e novos rexistros por semana + desc_html: Conta de estados publicados de xeito local, usuarias activas, e novos rexistros en períodos semanais title: Publicar estatísticas agregadas sobre a actividade da usuaria bootstrap_timeline_accounts: - desc_html: Separar múltiples nomes de usuaria con vírgulas. Só funcionarán as contas locais non bloqueadas. Si baldeiro, por omisión son todos os local admin. - title: Seguimentos por omisión para novas usuarias + desc_html: Separar os múltiples nomes de usuaria con vírgulas. Só funcionarán as contas locais non bloqueadas. Se fica baleiro, serán todos os administradores locais. + title: Seguimentos por defecto para novas contas contact_information: - email: e-mail de traballo + email: Email de negocios username: Nome de usuaria de contacto custom_css: - desc_html: Modificar o aspecto con CSS cargado en cada páxina + desc_html: Modificar a aparencia con CSS cargado en cada páxina title: CSS personalizado default_noindex: desc_html: Aféctalle a todas as usuarias que non cambiaron os axustes elas mesmas title: Por omisión exclúe as usuarias do indexado por servidores de busca domain_blocks: - all: Para todas + all: Para todos disabled: Para ninguén - title: Mostrar dominios bloqueados + title: Amosar dominios bloqueados users: Para usuarias locais conectadas domain_blocks_rationale: - title: Mostrar razón + title: Amosar motivo enable_bootstrap_timeline_accounts: title: Activar seguimentos por omisión para novas usuarias hero: - desc_html: Mostrado na portada. Recoméndase 600x100px como mínimo. Se non se establece, mostrará a imaxe por omisión do servidor - title: Imáxe Heróe + desc_html: Amosado na páxina principal. Polo menos 600x100px recomendados. Se non está definido, estará por defecto a miniatura do servidor + title: Imaxe do heroe mascot: - desc_html: Mostrado en varias páxinas. Recoméndase 293x205 como mínimo. Se non se establece publícase a mascota por omisión + desc_html: Amosado en varias páxinas. Polo menos 293x205px recomendados. Se non está definido, estará a mascota por defecto title: Imaxe da mascota peers_api_enabled: desc_html: Nomes de dominio que este servidor atopou no fediverso - title: Publicar lista de servidores descubertos + title: Publicar listaxe de servidores descobertos preview_sensitive_media: desc_html: A vista previa de ligazóns de outros sitios web mostrará unha imaxe incluso si os medios están marcados como sensibles title: Mostrar medios sensibles con vista previa OpenGraph @@ -658,6 +671,9 @@ gl: hint_html: "Nota: Non che pediremos o contrasinal na seguinte hora." invalid_password: Contrasinal incorrecto prompt: Confirma o contrasinal para continuar + date: + formats: + default: "%d %b, %Y" datetime: distance_in_words: about_x_hours: "%{count}h" @@ -734,6 +750,7 @@ gl: hint_html: "¿Qué son as etiquetas destacadas? Móstranse destacadas no seu perfil público e permítenlle a outras persoas ver os seus toots públicos nos que as utilizou. Son unha ferramenta moi útil para facer seguimento de traballos creativos e proxectos a longo prazo." filters: contexts: + account: Perfís home: Liña temporal inicial notifications: Avisos public: Liñas temporais públicas @@ -758,6 +775,8 @@ gl: all: Todo changes_saved_msg: Cambios gardados correctamente!! copy: Copiar + delete: Eliminar + edit: Editar no_batch_actions_available: Non hai accións en pila dispoñibles nesta páxina order_by: Ordenar por save_changes: Gardar cambios @@ -916,11 +935,11 @@ gl: truncate: "…" polls: errors: - already_voted: Xa votou en esta sondaxe + already_voted: Xa votaches nesta enquisa duplicate_options: contén elementos duplicados duration_too_long: está moi lonxe no futuro duration_too_short: é demasiado cedo - expired: A sondaxe rematou + expired: A enquisa rematou invalid_choice: A opción de voto escollida non existe over_character_limit: non poden ter máis de %{max} caracteres cada unha too_few_options: debe ter máis de unha opción @@ -929,11 +948,15 @@ gl: other: Outro posting_defaults: Valores por omisión public_timelines: Liñas temporais públicas + reactions: + errors: + unrecognized_emoji: non é unha emoticona recoñecida relationships: activity: Actividade da conta dormant: En repouso followers: Seguidoras following: Seguindo + invited: Convidado last_active: Último activo most_recent: Máis recente moved: Movida diff --git a/config/locales/hu.yml b/config/locales/hu.yml index ae3cc479c1..9f0e2f9485 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -341,13 +341,11 @@ hu: delete: Törlés destroyed_msg: E-mail domain sikeresen eltávolítva a feketelistáról domain: Domain + empty: Nincs email domain a feketelistán. new: create: Domain hozzáadása title: Új e-mail feketelista bejegyzés title: E-mail feketelista - followers: - back_to_account: Vissza a fiókhoz - title: "%{acct} követői" instances: by_domain: Domain delivery_available: Kézbesítés elérhető diff --git a/config/locales/id.yml b/config/locales/id.yml index 2a759bc5f6..8cdb2f1fc8 100644 --- a/config/locales/id.yml +++ b/config/locales/id.yml @@ -308,6 +308,7 @@ id: private_comment: Komentar pribadi private_comment_hint: Komentar tentang pembatasan domain ini untuk penggunaan internal oleh moderator. public_comment: Komentar publik + public_comment_hint: Komentar tentang pembatasan domain ini untuk publik umum, jika mengiklankan daftar pembatasan domain diaktifkan. reject_media: Tolak berkas media reject_media_hint: Hapus file media yang tersimpan dan menolak semua unduhan nantinya. Tidak terpengaruh dengan suspen reject_reports: Tolak laporan @@ -338,9 +339,6 @@ id: create: Tambah domain title: Entri daftar hitam surel baru title: Daftar hitam surel - followers: - back_to_account: Kembali Ke Akun - title: Pengikut %{acct} instances: by_domain: Domain delivery_available: Pengiriman tersedia @@ -440,6 +438,9 @@ id: all: Kepada semua orang disabled: Tidak kepada siapa pun title: Lihat blokir domain + users: Ke pengguna lokal yang sudah login + domain_blocks_rationale: + title: Tampilkan alasan enable_bootstrap_timeline_accounts: title: Aktifkan opsi ikuti otomatis untuk pengguna baru hero: diff --git a/config/locales/is.yml b/config/locales/is.yml index a4d21deb26..70d6e4e75d 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -52,7 +52,7 @@ is: choices_html: "%{name} hefur valið:" endorsements_hint: Þú getur auglýst efni frá fólki sem þú fylgir í vefviðmótinu og mun það birtast hér. featured_tags_hint: Þú getur gefið sérstökum myllumerkjum aukið vægi og birtast þau þá hér. - follow: Fylgja + follow: Fylgjast með followers: one: fylgjandi other: fylgjendur @@ -344,9 +344,6 @@ is: create: Bæta við léni title: Ný færsla á bannlista fyrir tölvupóstföng title: Bannlisti yfir tölvupóstföng - followers: - back_to_account: Til baka í notandaaðgang - title: Fylgjast með %{acct} instances: by_domain: Lén delivery_available: Afhending er til taks @@ -495,7 +492,7 @@ is: desc_html: Kynningarmálsgrein í API. Lýstu því hvað það er sem geri þennan Mastodon-þjón sérstakan, auk annarra mikilvægra upplýsinga. Þú getur notað HTML-einindi, sér í lagi <a> og <em>. title: Lýsing á vefþjóni site_description_extended: - desc_html: góður staður fyrir siðareglur, almennt regluverk, leiðbeiningar og annað sem gerir netþjóninni þinn sérstakann. Þú getur notað HTML-einindi + desc_html: Góður staður fyrir siðareglur, almennt regluverk, leiðbeiningar og annað sem gerir netþjóninni þinn sérstakann. Þú getur notað HTML-einindi title: Sérsniðnar ítarlegar upplýsingar site_short_description: desc_html: Birt á hliðarspjaldi og í lýsigögnum. Lýstu því hvað Mastodon gerir og hvað það er sem geri þennan vefþjón sérstakan, í einni málsgrein. @@ -645,7 +642,7 @@ is: authorize_follow: already_following: Þú ert að þegar fylgjast með þessum aðgangi error: Því miður, það kom upp villa við að fletta upp fjartengda notandaaðgangnum - follow: Fylgja + follow: Fylgjast með follow_request: 'Þú sendir beiðni um að fylgjast með til:' following: 'Tókst! Þú ert núna að fylgjast með:' post_follow: @@ -731,6 +728,7 @@ is: add_new: Bæta við nýju errors: limit: Þú ert þegar búin/n að gefa hámarksfjölda myllumerkja aukið vægi + hint_html: "Hvað eru myllumerki með aukið vægi? Þau eru birt áberandi á opinbera notandasniðinu þínu og gera fólki kleift að fletta í gegnum opinberu færslurnar þínar sérstaklega undir þessum myllumerkjum. Þau eru frábær aðferð við að halda utan um skapandi vinnu eða langtíma verkefni." filters: contexts: home: Heimatímalína @@ -741,6 +739,7 @@ is: title: Breyta síu errors: invalid_context: Ekkert eða ógilt samhengi var gefið + invalid_irreversible: Óendurkræf síun virkar bara í sambandi við heimasvæði eða tilkynningar index: delete: Eyða empty: Þú ert ekki með neinar síur. @@ -770,6 +769,11 @@ is: authorize_connection_prompt: Auðkenna dulkóðaða tengingu? errors: failed: Dulrituð tenging mistókst, endilega reyndu aftur frá %{provider}. + keybase: + invalid_token: Keybase-teikn eru tætigildi undirritana og verða að vera 66 hex-stafir + verification_failed: Keybase skilur þetta teikn ekki sem undirritun Keybase-notandans %{kb_username}. Prófaðu aftur í Keybase. + wrong_user: Get ekki búið til sannvottun fyrir %{proving} á meðan skráð er inn sem %{current}. Skráðu inn sem %{proving} og prófaðu aftur. + explanation_html: Hér geturðu tengt dulritað önnur auðkenni þín, eins og t.d. Keybase-notandasnið. Þetta gerir öðru fólki kleift að senda þér dulrituð skilaboð og að treysta efni sem þú sendir þeim. i_am_html: Ég er %{username} á %{service}. identity: Auðkenni inactive: Óvirkt @@ -783,10 +787,12 @@ is: merge_long: Halda fyrirliggjandi færslum og bæta við nýjum overwrite: Skrifa yfir overwrite_long: Skipta út fyrirliggjandi færslum með þeim nýju + preface: Þú getur flutt inn gögn sem þú hefur flutt út frá öðrum vefþjóni, svo sem lista yfir fólk sem þú fylgist með eða útilokar. + success: Það tókst að senda inn gögnin þín og verður unnið með þau þegar færi gefst types: blocking: Listi yfir útilokanir domain_blocking: Listi yfir útilokanir léna - following: Listi yfir fylgjendur + following: Listi yfir þá sem fylgst er með muting: Listi yfir þagganir upload: Senda inn in_memoriam_html: Minning. @@ -844,9 +850,11 @@ is: backreference_required: Það verður fyrst að stilla nýja aðganginn til að bakvísa á þennan aðgang before: 'Áður en haldið er áfram, skaltu lesa þessa minnispunkta gaumgæfilega:' cooldown: Eftir yfirfærslu/flutning kemur kælingartímabil á meðan þú getur ekki flutt þig aftur + disabled_account: Núverandi aðgangur þinn verður ekki nothæfur að fullu eftir þetta. Hinsvegar muntu geta flutt út gögn af honum og einnig endurvirkjað hann. followers: Þessi aðgerð mun flytja alla fylgjendur af núverandi aðgangi yfir á nýja aðganginn only_redirect_html: Einnig geturðu einungis sett upp endurbeiningu á notandasniðið þitt. other_data: Engin önnur gögn munu flytjast sjálfvirkt + redirect: Notandasnið aðgangsins verður uppfært með athugasemd um endurbeininguna og verður undanþegið frá leitum moderation: title: Umsjón notification_mailer: @@ -854,6 +862,9 @@ is: action: Skoða allar tilkynningar body: Hér er stutt yfirlit yfir þau skilaboð sem þú gætir hafa misst af síðan þú leist inn síðast %{since} mention: "%{name} minntist á þig í:" + new_followers_summary: + one: Að auki, þú hefur fengið einn nýjan fylgjanda á meðan þú varst fjarverandi! Húh! + other: Að auki, þú hefur fengið %{count} nýja fylgjendur á meðan þú varst fjarverandi! Frábært! subject: one: "1 ný tilkynning síðan þú leist inn síðast \U0001F418" other: "%{count} nýjar tilkynningar síðan þú leist inn síðast \U0001F418" @@ -873,6 +884,9 @@ is: title: Ný beiðni um að fylgjast með mention: action: Svara + body: "%{name} minntist á þig í:" + subject: "%{name} minntist á þig" + title: Ný tilvísun reblog: body: "%{name} endurbirti stöðufærsluna þína:" subject: "%{name} endurbirti stöðufærsluna þína" @@ -904,6 +918,7 @@ is: duration_too_long: er of langt inn í framtíðina duration_too_short: er of snemma expired: Könnuninni er þegar lokið + invalid_choice: Þessi valkostur er ekki til over_character_limit: geta ekki verið lengri en %{max} stafir hvert too_few_options: verður að vera með fleiri en eitt atriði too_many_options: getur ekki innihaldið meira en %{max} atriði @@ -928,8 +943,9 @@ is: status: Staða aðgangs remote_follow: acct: Settu inn notandanafn@lén þaðan sem þú vilt vera virk/ur + missing_resource: Gat ekki fundið endurbeiningarslóðina fyrir notandaaðganginn þinn no_account_html: Ertu ekki með aðgang? Þú getur nýskráð þig hér - proceed: Halda áfram í að fylgja + proceed: Halda áfram í að fylgjast með prompt: 'Þú ætlar að fara að fylgjast með:' reason_html: "Hvers vegna er þetta skref nauðsynlegt? %{instance} er ekki endilega netþjónninn þar sem þú ert skráð/ur, þannig að við verðum að endurbeina þér á heimaþjóninn þinn fyrst." remote_interaction: @@ -998,7 +1014,7 @@ is: development: Þróun edit_profile: Breyta notandasniði export: Útflutningur gagna - featured_tags: Myllumerki í umræðunni + featured_tags: Myllumerki með aukið vægi identity_proofs: Sannanir á auðkenni import: Flytja inn import_and_export: Inn- og útflutningur @@ -1148,21 +1164,32 @@ is: default: "%d. %b, %Y, %H:%M" month: "%b %Y" two_factor_authentication: + code_hint: Settu inn kóðann sem auðkenningarforritið útbjó til staðfestingar + description_html: Ef þú virkjar tvíþátta auðkenningu mun innskráning krefjast þess að þú hafir símann þinn við hendina, með honum þarf að útbúa öryggisteikn sem þú þarft að setja inn. disable: Gera óvirkt enable: Virkja enabled: Tveggja-þátta auðkenning er virk enabled_success: Það tókst að virkja tveggja-þátta auðkenningu generate_recovery_codes: Útbúa endurheimtukóða + instructions_html: "Skannaðu þennar QR-kóða inn í Google Authenticator eða álíka TOTP-forrit á símanum þínum. Héðan í frá mun það forrit útbúa teikn sem þú verður að setja inn til að geta skráð þig inn." + lost_recovery_codes: Endurheimtukóðar gera þér kleift að fá aftur samband við notandaaðganginn þinn ef þú tapar símanum þínum. Ef þú aftur hefur tapað endurheimtukóðunum, geturðu endurgert þá hér. Gömlu endurheimtukóðarnir verða þá ógiltir. + manual_instructions: 'Ef þú getur ekki skannað QR-kóðann og verður að setja hann inn handvirkt, þá er hér leyniorðið á textaformi:' recovery_codes: Kóðar fyrir endurheimtingu öryggisafrits + recovery_codes_regenerated: Það tókst að endurgera endurheimtukóða + recovery_instructions_html: Ef þú tapar símanum þínum geturðu notað einn af endurheimtukóðunum hér fyrir neðan til að fá aftur samband við notandaaðganginn þinn. Geymdu endurheimtukóðana á öruggum stað. Sem dæmi gætirðu prentað þá út og geymt með öðrum mikilvægum skjölum. setup: Setja upp wrong_code: Kóðinn sem þú settir inn er ógildur! Eru klukkur netþjónsins og tækisins réttar? user_mailer: backup_ready: + explanation: Þú baðst um fullt öryggisafrit af Mastodon notandaaðgangnum þínum. Það er núna tilbúið til niðurhals! subject: Safnskráin þín er tilbúin til niðurhals title: Taka út í safnskrá warning: explanation: + disable: Á meðan aðgangurinn þinn er frystur, eru gögn aðgangsins ósnert, en þú getur ekki framkvæmt neinar aðgerðir fyrr en honum hefur verið aflæst. + silence: Á meðan aðgangurinn þinn er takmarkaður, mun aðeins fólk sem þegar fylgist með þér sjá tístin þín á þessum vefþjóni, auk þess sem lokað gæti verið á þig á ýmsum opinberum listum. Aftur á móti geta aðrir gerst fylgjendur þínir handvirkt. suspend: Aðgangurinn þinn hefur verið settur í biðstöðu, öll þín tíst og innsent myndefni hafa verið óafturkræft fjarlægð af þessum vefþjóni, sem og af þeim vefþjónum þar sem þú áttir þér fylgjendur. + get_in_touch: Þú getur svarað þessum tölvupósti til að setja þig í samband við umsjónarmenn %{instance}. review_server_policies: Yfirfara reglur vefþjónsins statuses: 'Sérstaklega fyrir:' subject: @@ -1177,17 +1204,28 @@ is: suspend: Notandaaðgangur í bið welcome: edit_profile_action: Setja upp notandasnið + edit_profile_step: Þú getur sérsniðið notandasniðið þitt með því að senda inn auðkennismynd, síðuhaus, breytt birtingarnafninu þínu og ýmislegt fleira. Ef þú vilt yfirfara nýja fylgjendur áður en þeim er leyft að fylgjast með þér geturðu læst aðgangnum þínum. explanation: Hér eru nokkrar ábendingar til að koma þér í gang final_action: Byrjaðu að skrifa + final_step: 'Byrjaðu að tjá þig! Jafnvel án fylgjenda geta aðrir séð opinberar færslur frá þér, til dæmis á staðværu tímalínunni og í myllumerkjum. Þú gætir jafnvel viljað kynna þig með myllumerkinu #introductions.' + full_handle: Fullt auðkenni þitt + full_handle_hint: Þetta er það sem þú myndir gefa upp við vini þína svo þeir geti sent þér skilaboð eða fylgst með þér af öðrum netþjóni. review_preferences_action: Breyta kjörstillingum + review_preferences_step: Gakktu úr skugga um að kjörstillingarnar séu eins og þú vilt hafa þær, eins og t.d. hvaða tölvupóst þú vilt fá, eða hvaða stig friðhelgi þú vilt að færslurnar þínar hafi sjálfgefið. Ef þú hefur ekkert á móti sjónrænu áreiti geturðu virkjað sjálvirka spilun GIF-hreyfimynda. subject: Velkomin í Mastodon + tip_federated_timeline: Sameiginlega tímalínan er færibandasýn á Mastodon netkerfið. En hún inniheldur bara fólk sem nágrannar þínir eru áskrifendur að, þannig að hún er ekki tæmandi. + tip_following: Sjálfgefið er að þú fylgist með stjórnanda eða stjórnendum vefþjónsins. Til að finna fleira áhugavert fólk ættirðu að kíkja á staðværu og sameiginlegu tímalínurnar. tip_local_timeline: Staðværa tímalínan er færibandasýn á allt fólkið á %{instance}. Þetta eru þínir næstu nágrannar! + tip_mobile_webapp: Ef farsímavafrinn býður þér að bæta Mastodon á heimaskjáinn þinn, muntu geta tekið á móti ýti-tilkynningum. Það virkar á ýmsa vegu eins og um uppsett forrit sé að ræða! tips: Ábendingar title: Velkomin/n um borð, %{name}! users: follow_limit_reached: Þú getur ekki fylgst með fleiri en %{limit} aðilum invalid_email: Tölvupóstfangið er ógilt invalid_otp_token: Ógildur tveggja-þátta kóði + otp_lost_help_html: Ef þú hefur misst aðganginn að hvoru tveggja, geturðu sett þig í samband við %{email} + seamless_external_login: Innskráning þín er í gegnum utanaðkomandi þjónustu, þannig að stillingar fyrir lykilorð og tölvupóst eru ekki aðgengilegar. signed_in_as: 'Skráð inn sem:' verification: + explanation_html: 'Þú getur vottað að þú sért eigandi og ábyrgur fyrir tenglunum í lýsigögnum notandasniðsins þíns. Til að það virki, þurfa vefsvæðin sem vísað er í að innihalda tengil til baka í Mastodon-notandasniðið. Tengillinn sem vísar til baka verður að vera með rel="me" eigindi. Textinn í tenglinum skiptir ekki máli. Hérna er dæmi:' verification: Sannprófun diff --git a/config/locales/it.yml b/config/locales/it.yml index 7bce8d3d6e..a7e811e22e 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -344,9 +344,6 @@ it: create: Aggiungi dominio title: Nuova voce della lista nera delle email title: Lista nera email - followers: - back_to_account: Torna all'account - title: Seguaci di %{acct} instances: by_domain: Dominio delivery_available: Distribuzione disponibile @@ -922,6 +919,7 @@ it: duration_too_long: è troppo lontano nel futuro duration_too_short: è troppo presto expired: Il sondaggio si è già concluso + invalid_choice: L'opzione di voto scelta non esiste over_character_limit: non possono essere più lunghi di %{max} caratteri ciascuno too_few_options: deve avere più di un elemento too_many_options: non può contenere più di %{max} elementi diff --git a/config/locales/ja.yml b/config/locales/ja.yml index e26fe3942f..ce15f4195b 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -224,6 +224,8 @@ ja: update_status: "%{name} さんが %{target} さんの投稿を更新しました" deleted_status: "(削除済)" title: 操作履歴 + announcements: + title: 告知 custom_emojis: assign_category: カテゴリーを割り当て by_domain: ドメイン @@ -339,9 +341,6 @@ ja: create: ドメインを追加 title: メールアドレス用ブラックリスト新規追加 title: メールブラックリスト - followers: - back_to_account: 戻る - title: "%{acct}さんのフォロワー" instances: by_domain: ドメイン delivery_available: 配送可能 @@ -750,6 +749,8 @@ ja: all: すべて changes_saved_msg: 正常に変更されました! copy: コピー + delete: 削除 + edit: 編集 no_batch_actions_available: このページに一括操作はありません order_by: 並び順 save_changes: 変更を保存 @@ -910,6 +911,7 @@ ja: duration_too_long: が長過ぎます duration_too_short: が短過ぎます expired: アンケートは既に終了しました + invalid_choice: 選択された項目は存在しません over_character_limit: は%{max}文字より長くすることはできません too_few_options: は複数必要です too_many_options: は%{max}個までです diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 6297937366..5cedbd3645 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -1,7 +1,7 @@ --- kab: about: - about_hashtag_html: Tigi d tiberraḥin tizuyaz, ɣur-sent #%{hashtag}. Tzemreḍ ad tesdemreḍ akked yid-sent ma tesɛiḍ amiḍan deg kra n umḍiq deg fediverse. + about_hashtag_html: Tigi d tiberraḥin tizuyaz, γur-sent #%{hashtag}. Tzemreḍ ad tesdemreḍ akked yid-sent ma tesɛiḍ amiḍan deg kra n umḍiq deg fediverse. about_mastodon_html: 'Azeṭṭa ametti n uzekka: Ulac deg-s asussen, ulac taɛessast n tsuddiwin fell-ak, yebna ɣef leqder d ttrebga, daɣen d akeslemmas! Akked Mastudun, isefka-inek ad qimen inek!' about_this: Ɣef active_count_after: yekker @@ -215,9 +215,6 @@ kab: create: Rnu taγult title: Timerna n taɣult tamaynut n imayl ɣer tebdart taberkant title: Tabdart taberkant n imayl - followers: - back_to_account: Uγal γer umiḍan - title: Imeḍfaṛen n %{acct} instances: by_domain: Taγult delivery_available: Yella usiweḍ @@ -259,6 +256,9 @@ kab: all: Ɣef medden akk disabled: Ɣef yiwen ala users: Ɣef yimseqdacen idiganen i yeqqnen + site_description: + title: Aglam n uqeddac + site_title: Isem n uqeddac title: Iγewwaṛen n usmel statuses: batch: diff --git a/config/locales/kk.yml b/config/locales/kk.yml index 4ef87a5bc5..74c486ef60 100644 --- a/config/locales/kk.yml +++ b/config/locales/kk.yml @@ -344,9 +344,6 @@ kk: create: Add dоmain title: New e-mail blаcklist entry title: E-mail қаратізімі - followers: - back_to_account: Back To Accоunt - title: "%{acct} оқырмандары" instances: by_domain: Domаin delivery_available: Жеткізу қол жетімді diff --git a/config/locales/ko.yml b/config/locales/ko.yml index 25bb714ef9..21ea5b554b 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -341,9 +341,6 @@ ko: create: 차단 규칙 생성 title: 새 이메일 도메인 차단 title: Email 도메인 차단 - followers: - back_to_account: 계정으로 돌아가기 - title: "%{acct}의 팔로워" instances: by_domain: 도메인 delivery_available: 전송 가능 @@ -924,6 +921,7 @@ ko: dormant: 휴면 followers: 팔로워 following: 팔로잉 + invited: 초대됨 last_active: 마지막 활동 most_recent: 가장 최근 moved: 이동함 diff --git a/config/locales/lt.yml b/config/locales/lt.yml index 41f0284d80..9af094c019 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -261,9 +261,6 @@ lt: create: Pridėto domeną title: Naujas el pašto juodojo sąrašo įtraukimas title: El pašto juodasis sąrašas - followers: - back_to_account: Atgal Į Paskyrą - title: "%{acct} Sekėjai" instances: by_domain: Domenas delivery_available: Pristatymas galimas diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 5975ba68e7..092a46d57a 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -343,9 +343,6 @@ nl: create: Blokkeren title: Nieuw e-maildomein blokkeren title: E-maildomeinen blokkeren - followers: - back_to_account: Terug naar account - title: Volgers van %{acct} instances: by_domain: Domein delivery_available: Bezorging is mogelijk diff --git a/config/locales/nn.yml b/config/locales/nn.yml index 4eb8c69b38..c61523efe8 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -331,9 +331,6 @@ nn: create: Legg til domene title: Ny blokkeringsoppføring av e-postdomene title: Blokkerte e-postadresser - followers: - back_to_account: Tilbake til konto - title: "%{acct} sine fylgjarar" instances: by_domain: Domene delivery_available: Levering er tilgjengelig diff --git a/config/locales/no.yml b/config/locales/no.yml index 51d0eb1bde..12772f335f 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -317,9 +317,6 @@ create: Legg til domene title: Ny blokkeringsoppføring av e-postdomene title: Blokkering av e-postdomene - followers: - back_to_account: Tilbake til kontoen - title: "%{acct} sine følgere" instances: by_domain: Domene delivery_available: Levering er tilgjengelig diff --git a/config/locales/oc.yml b/config/locales/oc.yml index 985d2a311f..ea0729f747 100644 --- a/config/locales/oc.yml +++ b/config/locales/oc.yml @@ -331,9 +331,6 @@ oc: create: Crear un blocatge title: Nòu blocatge de domeni de corrièl title: Blocatge de domeni de corrièl - followers: - back_to_account: Tornar al compte - title: Seguidors de %{acct} instances: by_domain: Domeni delivery_available: Liurason disponibla diff --git a/config/locales/pl.yml b/config/locales/pl.yml index e243162e87..774319a5a8 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -346,9 +346,6 @@ pl: create: Utwórz blokadę title: Nowa blokada domeny e-mail title: Blokowanie domen e-mail - followers: - back_to_account: Wróć do konta - title: Śledzący %{acct} instances: by_domain: Domena delivery_available: Doręczanie jest dostępne diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index 7fe2c8946e..fb741b0d04 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -344,9 +344,6 @@ pt-BR: create: Adicionar domínio title: Novo domínio de e-mail bloqueado title: Lista de bloqueio de domínios de e-mail - followers: - back_to_account: Voltar para a conta - title: Seguidores de %{acct} instances: by_domain: Domínio delivery_available: Envio disponível @@ -375,6 +372,8 @@ pt-BR: title: Convites pending_accounts: title: Contas pendentes (%{count}) + relationships: + title: Relações de %{acct} relays: add_new: Adicionar novo repetidor delete: Excluir @@ -734,6 +733,7 @@ pt-BR: hint_html: "O que são hashtags em destaque? Elas são mostradas no seu perfil público e permitem que as pessoas acessem seus toots públicos que contenham especificamente essas hashtags. São uma excelente ferramenta para acompanhar os trabalhos criativos ou os projetos de longo prazo." filters: contexts: + account: Perfis home: Página inicial notifications: Notificações public: Linhas públicas @@ -934,6 +934,7 @@ pt-BR: dormant: Inativo followers: Seguidores following: Seguindo + invited: Convidado last_active: Última atividade most_recent: Mais recente moved: Mudou-se diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index 8628c5774d..deee432877 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -344,9 +344,6 @@ pt-PT: create: Adicionar domínio title: Novo bloqueio de domínio de email title: Bloqueio de Domínio de Email - followers: - back_to_account: Voltar à conta - title: Seguidores de %{acct} instances: by_domain: Domínio delivery_available: Entrega disponível @@ -375,6 +372,8 @@ pt-PT: title: Convites pending_accounts: title: Contas pendentes (%{count}) + relationships: + title: Relações de %{acct} relays: add_new: Adicionar novo repetidor delete: Apagar @@ -734,6 +733,7 @@ pt-PT: hint_html: "O que são hashtags em destaque? Elas são exibidas de forma bem visível no seu perfil público e permitem que as pessoas consultem as suas publicações públicas especificamente sob essas hashtags. São uma ótima ferramenta para manter o controlo de trabalhos criativos ou projetos de longo prazo." filters: contexts: + account: Perfis home: Cronologia inicial notifications: Notificações public: Cronologias públicas @@ -934,6 +934,7 @@ pt-PT: dormant: Inativo followers: Seguidores following: A seguir + invited: Convidado last_active: Última atividade most_recent: Mais recente moved: Mudou-se diff --git a/config/locales/ru.yml b/config/locales/ru.yml index a62995721e..bd019ee178 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -140,7 +140,7 @@ ru: remote: Удаленные title: Размещение login_status: Статус учётной записи - media_attachments: Мультимедийные вложения + media_attachments: Файлы мультимедиа memorialize: Сделать мемориалом moderation: active: Действующие @@ -359,9 +359,6 @@ ru: create: Создать блокировку title: Новая блокировка по домену title: Блокировка e-mail доменов - followers: - back_to_account: Вернуться к учётной записи - title: Подписчики пользователя %{acct} instances: by_domain: Домен delivery_available: Доставка возможна @@ -392,6 +389,8 @@ ru: title: Приглашения pending_accounts: title: Ожидающие учетные записи (%{count}) + relationships: + title: Связи %{acct} relays: add_new: Добавить ретранслятор delete: Удалить @@ -550,11 +549,11 @@ ru: deleted: Удалено failed_to_execute: Не удалось выполнить media: - title: Файлы медиа - no_media: Без медиа + title: Файлы мультимедиа + no_media: Без файлов no_status_selected: Ничего не изменилось, так как ни один пост не был выделен title: Посты пользователя - with_media: С медиа + with_media: С файлами tags: accounts_today: Уникальных использований за сегодня accounts_week: Уникальных использований за эту неделю @@ -755,6 +754,7 @@ ru: hint_html: "Особенные хэштеги отображаются в вашем профиле и позволяют людям просматривать ваши посты, отмеченные ими. Это отличный инструмент для отслеживания долгосрочных проектов и творческих работ." filters: contexts: + account: Посты в профилях home: Домашняя лента notifications: Уведомления public: Публичные ленты @@ -963,6 +963,7 @@ ru: dormant: Заброшенная followers: Подписчики following: Подписки + invited: Приглашённые last_active: По последней активности most_recent: По недавности moved: Мигрировавшая diff --git a/config/locales/simple_form.ar.yml b/config/locales/simple_form.ar.yml index e3355e8c53..f7a38a92c4 100644 --- a/config/locales/simple_form.ar.yml +++ b/config/locales/simple_form.ar.yml @@ -42,7 +42,7 @@ ar: setting_use_pending_items: إخفاء تحديثات الخط وراء نقرة بدلًا مِن التمرير التلقائي للتدفق username: اسم المستخدم الخاص بك سوف يكون فريدا مِن نوعه على %{domain} featured_tag: - name: 'رُبَّما تريد·ين استخدام واحد مِن هذه:' + name: 'رُبَّما تريد·ين استخدام واحد مِن بين هذه:' form_challenge: current_password: إنك بصدد الدخول إلى منطقة آمنة imports: @@ -123,7 +123,7 @@ ar: setting_theme: سمة الموقع setting_trends: اعرض ما يُتداوَل اليوم setting_unfollow_modal: إظهار مربع حوار للتأكيد قبل إلغاء متابعة أي حساب - setting_use_blurhash: أظهر ألوانًا متدرّجة على الوسائط الحساسة + setting_use_blurhash: أظهر ألوانًا متدرّجة على الوسائط المَخفية setting_use_pending_items: الوضع البطيء severity: القوّة type: صيغة الاستيراد diff --git a/config/locales/simple_form.ca.yml b/config/locales/simple_form.ca.yml index 3fae058301..d43ab04fe1 100644 --- a/config/locales/simple_form.ca.yml +++ b/config/locales/simple_form.ca.yml @@ -31,7 +31,7 @@ ca: locale: El llenguatge de l’interfície d’usuari, els correus i les notificacions push locked: Requereix que aprovis manualment els seguidors password: Utilitza com a mínim 8 caràcters - phrase: Es combinarà independentment del format en el text o l'avís de contingut del bram + phrase: Es combinarà independentment del format en el text o l'avís de contingut del tut scopes: A quines API es permetrà a l'aplicació accedir. Si selecciones un àmbit d'alt nivell, no cal que seleccionis un d'individual. setting_aggregate_reblogs: No mostra els nous impulsos dels tuts que ja s'han impulsat recentment (només afecta als impulsos nous rebuts) setting_default_sensitive: Els mèdia sensibles estan ocults per defecte i es poden revelar amb un clic diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml index 84787a6c50..0863cc4a85 100644 --- a/config/locales/simple_form.de.yml +++ b/config/locales/simple_form.de.yml @@ -14,6 +14,12 @@ de: text_html: Optional. Du kannst Beitragssyntax nutzen. Du kannst Warnungsvorlagen benutzen um Zeit zu sparen type_html: Wähle aus, was du mit %{acct} machen möchtest warning_preset_id: Optional. Du kannst immer noch eigenen Text an das Ende der Vorlage hinzufügen + announcement: + all_day: Wenn aktiviert werden nur die Daten des Zeitraums angezeigt + ends_at: Optional. Die Ankündigung wird zu diesem Zeitpunkt automatisch zurückgezogen + scheduled_at: Leer lassen um die Ankündigung sofort zu veröffentlichen + starts_at: Optional. Falls deine Ankündigung an einen bestimmten Zeitraum gebunden ist + text: Du kannst die Toot-Syntax verwenden. Bitte beachte den Platz, den die Ankündigung auf dem Bildschirm des Benutzers einnehmen wird defaults: autofollow: Leute, die sich über deine Einladung registrieren, werden dir automatisch folgen avatar: PNG, GIF oder JPG. Maximal %{size}. Wird auf %{dimensions} px herunterskaliert @@ -83,6 +89,12 @@ de: silence: Stummschalten suspend: Deaktivieren und Benutzerdaten unwiderruflich löschen warning_preset_id: Benutze eine Warnungsvorlage + announcement: + all_day: Ganztägiges Ereignis + ends_at: Ereignisende + scheduled_at: Veröffentlichung planen + starts_at: Beginn des Ereignisses + text: Ankündigung defaults: autofollow: Eingeladene Nutzer sollen dir automatisch folgen avatar: Profilbild diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 8386c8cf15..2f08209060 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -14,6 +14,12 @@ en: text_html: Optional. You can use toot syntax. You can add warning presets to save time type_html: Choose what to do with %{acct} warning_preset_id: Optional. You can still add custom text to end of the preset + announcement: + all_day: When checked, only the dates of the time range will be displayed + ends_at: Optional. Announcement will be automatically unpublished at this time + scheduled_at: Leave blank to publish the announcement immediately + starts_at: Optional. In case your announcement is bound to a specific time range + text: You can use toot syntax. Please be mindful of the space the announcement will take up on the user's screen defaults: autofollow: People who sign up through the invite will automatically follow you avatar: PNG, GIF or JPG. At most %{size}. Will be downscaled to %{dimensions}px @@ -88,6 +94,12 @@ en: silence: Silence suspend: Suspend and irreversibly delete account data warning_preset_id: Use a warning preset + announcement: + all_day: All-day event + ends_at: End of event + scheduled_at: Schedule publication + starts_at: Begin of event + text: Announcement defaults: autofollow: Invite to follow your account avatar: Avatar diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index da39046808..20467dba4d 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -7,11 +7,11 @@ es: account_migration: acct: Especifique el nombre de usuario@dominio de la cuenta a la cual se desea migrar account_warning_preset: - text: Puede usar sintaxis de barritadas, como URLs, etiquetas y menciones + text: Puede usar sintaxis de toots, como URLs, hashtags y menciones admin_account_action: - include_statuses: El usuario verá qué bramidos han causado la acción de moderación o advertencia + include_statuses: El usuario verá qué toots han causado la acción de moderación o advertencia send_email_notification: El usuario recibirá una explicación de lo que sucedió con respecto a su cuenta - text_html: Opcional. Puede usar sintaxis de bramidos. Puede añadir configuraciones predefinidas de advertencia para ahorrar tiempo + text_html: Opcional. Puede usar sintaxis de toots. Puede añadir configuraciones predefinidas de advertencia para ahorrar tiempo type_html: Elige qué hacer con %{acct} warning_preset_id: Opcional. Aún puede añadir texto personalizado al final de la configuración predefinida defaults: @@ -27,20 +27,20 @@ es: fields: Puedes tener hasta 4 elementos mostrándose como una tabla en tu perfil header: PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px inbox_url: Copia la URL de la página principal del relés que quieres utilizar - irreversible: Las bramidos filtradas desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante + irreversible: Los toots filtrados desaparecerán irreversiblemente, incluso si este filtro es eliminado más adelante locale: El idioma de la interfaz de usuario, correos y notificaciones push locked: Requiere que manualmente apruebes seguidores y las publicaciones serán mostradas solamente a tus seguidores password: Utilice al menos 8 caracteres - phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de una bramido + phrase: Se aplicará sin importar las mayúsculas o los avisos de contenido de un toot scopes: Qué APIs de la aplicación tendrán acceso. Si seleccionas el alcance de nivel mas alto, no necesitas seleccionar las individuales. - setting_aggregate_reblogs: No mostrar nuevas rebramidos para las bramidos que han sido recientemente rebramidos (sólo afecta a las rebramidos recibidos recientemente) + setting_aggregate_reblogs: No mostrar nuevos retoots para los toots que han sido recientemente retooteados (sólo afecta a los retoots recibidos recientemente) setting_default_sensitive: El contenido multimedia sensible está oculto por defecto y puede ser mostrado con un click setting_display_media_default: Ocultar contenido multimedia marcado como sensible setting_display_media_hide_all: Siempre ocultar todo el contenido multimedia setting_display_media_show_all: Mostrar siempre contenido multimedia marcado como sensible setting_hide_network: A quién sigues y quién te sigue no será mostrado en tu perfil setting_noindex: Afecta a tu perfil público y páginas de estado - setting_show_application: La aplicación que utiliza usted para publicar bramidos se mostrará en la vista detallada de sus bramidos + setting_show_application: La aplicación que utiliza usted para publicar toots se mostrará en la vista detallada de sus toots setting_use_blurhash: Los gradientes se basan en los colores de las imágenes ocultas pero haciendo borrosos los detalles setting_use_pending_items: Ocultar actualizaciones cronológicas tras un clic en lugar de desplazar automáticamente la ristra username: Tu nombre de usuario será único en %{domain} @@ -60,7 +60,7 @@ es: tag: name: Sólo se puede cambiar el cajón de las letras, por ejemplo, para que sea más legible user: - chosen_languages: Cuando se marca, solo se mostrarán las barritadas en los idiomas seleccionados en las cronologías públicas + chosen_languages: Cuando se marca, solo se mostrarán los toots en los idiomas seleccionados en los timelines públicos labels: account: fields: @@ -73,7 +73,7 @@ es: account_warning_preset: text: Texto predefinido admin_account_action: - include_statuses: Incluir en el correo electrónico a los bramidos denunciados + include_statuses: Incluir en el correo electrónico a los toots denunciados send_email_notification: Notificar al usuario por correo electrónico text: Aviso personalizado type: Acción @@ -112,21 +112,21 @@ es: setting_advanced_layout: Habilitar interfaz web avanzada setting_aggregate_reblogs: Agrupar rebarritadas en las cronologías setting_auto_play_gif: Reproducir automáticamente los GIFs animados - setting_boost_modal: Mostrar ventana de confirmación antes de un Rebramido - setting_crop_images: Recortar a 16x9 las imágenes de los bramidos no expandidos + setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot + setting_crop_images: Recortar a 16x9 las imágenes de los toots no expandidos setting_default_language: Idioma de publicación setting_default_privacy: Privacidad de publicaciones setting_default_sensitive: Marcar siempre imágenes como sensibles - setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un bramido + setting_delete_modal: Mostrar diálogo de confirmación antes de borrar un toot setting_display_media: Visualización multimedia setting_display_media_default: Por defecto setting_display_media_hide_all: Ocultar todo setting_display_media_show_all: Mostrar todo - setting_expand_spoilers: Siempre expandir los bramidos marcados con advertencias de contenido + setting_expand_spoilers: Siempre expandir los toots marcados con advertencias de contenido setting_hide_network: Ocultar tu red setting_noindex: Excluirse del indexado de motores de búsqueda setting_reduce_motion: Reducir el movimiento de las animaciones - setting_show_application: Mostrar aplicación usada para publicar bramidos + setting_show_application: Mostrar aplicación usada para publicar toots setting_system_font_ui: Utilizar la tipografía por defecto del sistema setting_theme: Tema del sitio setting_trends: Mostrar las tendencias de hoy @@ -162,7 +162,7 @@ es: listable: Permitir que esta etiqueta aparezca en las búsquedas y en el directorio del perfil name: Etiqueta trendable: Permitir que esta etiqueta aparezca bajo tendencias - usable: Permitir a las barritadas usar esta etiqueta + usable: Permitir a los toots usar esta etiqueta 'no': 'No' recommended: Recomendado required: diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 7b9e466ee3..0e5332f1e5 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -14,6 +14,12 @@ fr: text_html: Optionnel. Vous pouvez utilisez la syntaxe des pouets. Vous pouvez ajouter des présélections d’attention pour économiser du temps type_html: Choisir que faire avec %{acct} warning_preset_id: Optionnel. Vous pouvez toujours ajouter un texte personnalisé à la fin de la présélection + announcement: + all_day: Si coché, seules les dates de l’intervalle de temps seront affichées + ends_at: Optionnel. L’annonce sera automatiquement dépubliée à ce moment + scheduled_at: Laisser vide pour publier l’annonce immédiatement + starts_at: Optionnel. Si votre annonce est liée à une période spécifique + text: Vous pouvez utiliser la syntaxe d’un pouet. Veuillez prendre en compte l’espace que l'annonce prendra sur l’écran de l'utilisateur defaults: autofollow: Les personnes qui s’inscrivent grâce à l’invitation vous suivront automatiquement avatar: Au format PNG, GIF ou JPG. %{size} maximum. Sera réduit à %{dimensions}px @@ -83,6 +89,12 @@ fr: silence: Masquer suspend: Suspendre et effacer les données du compte de manière irréversible warning_preset_id: Utiliser un modèle d’avertissement + announcement: + all_day: Événement de toute la journée + ends_at: Fin de l’événement + scheduled_at: Planifier la publication + starts_at: Début de l’événement + text: Annonce defaults: autofollow: Invitation à suivre votre compte avatar: Image de profil diff --git a/config/locales/simple_form.gl.yml b/config/locales/simple_form.gl.yml index 0fdf3e74e2..0af6bd690a 100644 --- a/config/locales/simple_form.gl.yml +++ b/config/locales/simple_form.gl.yml @@ -14,6 +14,12 @@ gl: text_html: Optativo. Pode utilizar formato no toot. Pode engadir avisos preestablecidos para aforrar tempo type_html: Escolla que facer con %{acct} warning_preset_id: Optativo. Poderá engadir texto persoalizado ao final do preestablecido + announcement: + all_day: Cando se marca, só serán amosadas as datas do intre de tempo + ends_at: Opcional. O anuncio non se publicará de xeito automático neste intre + scheduled_at: Déixao baleiro para publicar o anuncio de xeito inmediato + starts_at: Opcional. No caso de que o teu anuncio estea vinculado a un intre de tempo específico + text: Podes empregar a sintaxe do toot. Ten en conta o espazo que ocupará o anuncio na pantalla do usuario defaults: autofollow: As persoas que se conectaron a través de un convite seguirana automáticamente a vostede avatar: PNG, GIF ou JPG. Máximo %{size}. Será reducida a %{dimensions}px @@ -83,6 +89,12 @@ gl: silence: Acalar suspend: Suspender e eliminar irreversiblemente datos da conta warning_preset_id: Utilizar un aviso preestablecido + announcement: + all_day: Acontecemento diario + ends_at: Final do acontecemento + scheduled_at: Publicación programada + starts_at: Comezo do acontecemento + text: Anuncio defaults: autofollow: Convide a seguir a súa conta avatar: Avatar diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index c6a89fb409..b7aabd5d14 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -6,31 +6,61 @@ is: acct: Tilgreindu notandanafn@lén á notandaaðgangnum sem þú vilt flytjast frá account_migration: acct: Tilgreindu notandanafn@lén á notandaaðgangnum sem þú vilt flytjast til + account_warning_preset: + text: Þú getur notað sömu skilgreiningar og fyrir tíst, svo sem URL-slóðir, myllumerki og tilvísanir admin_account_action: + include_statuses: Notandinn mun sjá hvaða tíst hafa valdið viðbrögðum umsjónarmanns eða aðvörun kerfisins + send_email_notification: Notandinn mun fá útskýringar á því hvað gerðist með notandaaðganginn hans + text_html: Valfrjálst. Þú getur notað sömu skilgreiningar og fyrir tíst. Þú getur bætt inn forstilltum aðvörunum til að spara tíma type_html: Veldu hvað eigi að gera við %{acct} + warning_preset_id: Valkvætt. Þú getur ennþá bætt sérsniðnum texta við enda forstillinga defaults: + autofollow: Fólk sem skráir sig í gegnum boðið mun sjálfkrafa fylgjast með þér avatar: PNG, GIF eða JPG. Mest %{size}. Verður smækkað í %{dimensions}px bot: Þessi aðgangur er aðallega til að framkvæma sjálfvirkar aðgerðir og gæti verið án þess að hann sé vaktaður reglulega - current_password: Í öryggisskyni skaltu setja inn lykiloðið fyrir þennan notandaaðgang + context: Eitt eða fleiri samhengi þar sem sían ætti að gilda + current_password: Í öryggisskyni skaltu setja inn lykilorðið fyrir þennan notandaaðgang + current_username: Til að staðfesta skaltu setja inn notandanafnið fyrir þennan notandaaðgang + digest: Er aðeins sent eftir lengri tímabil án virkni og þá aðeins ef þú hefur fengið persónuleg skilaboð á meðan þú hefur ekki verið á línunni discoverable: Persónusniðamappan er önnur leið til að láta notandaaðganginn þinn ná til fleiri lesenda email: Þú munt fá sendan staðfestingarpóst + fields: Þú getur birt allt að 4 atriði sem töflu á notandasniðinu þínu header: PNG, GIF eða JPG. Mest %{size}. Verður smækkað í %{dimensions}px + inbox_url: Afritaðu slóðina af forsíðu endurvarpans sem þú vilt nota + irreversible: Síuð tíst munu hverfa óendurkræft, jafnvel þó sían sé seinna fjarlægð locale: Tungumál notandaviðmótsins, tölvupósts og ýti-tilkynninga locked: Krefst þess að þú samþykkir fylgjendur handvirkt password: Notaðu minnst 8 stafi + phrase: Verður notað til samsvörunar burtséð frá stafstöðu texta eða viðvörunar vegna efnis í tísti + scopes: Að hvaða API-kerfisviðmótum forritið fær aðgang. Ef þú velur efsta-stigs svið, þarftu ekki að gefa einstakar heimildir. + setting_aggregate_reblogs: Ekki sýna nýjar endurbirtingar á tístum sem hafa nýlega verið endurbirt (hefur bara áhrif á ný-mótteknar endurbirtingar) setting_default_sensitive: Viðkvæmt myndefni er sjálfgefið falið og er hægt að birta með smelli setting_display_media_default: Fela myndefni sem merkt er viðkvæmt setting_display_media_hide_all: Alltaf fela allt myndefni setting_display_media_show_all: Alltaf birta myndefni sem merkt er viðkvæmt - setting_noindex: Hefur áhrip á opinbera notandasniðið þitt og stöðusíður + setting_hide_network: Hverjum þú fylgist með og hverjir fylgjast með þér verður ekki birt á notandasniðinu þínu + setting_noindex: Hefur áhrif á opinbera notandasniðið þitt og stöðusíður + setting_show_application: Nafnið á forritinu sem þú notar til að tísta mun birtast í ítarlegri sýn á tístunum þínum setting_use_blurhash: Litstiglarnir byggja á litunum í földu myndunum, en gera öll smáatriði óskýr - setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er í stað þess að hún skruni streyminu sjálfvirkt + setting_use_pending_items: Fela uppfærslur tímalínu þar til smellt er, í stað þess að hún skruni streyminu sjálfvirkt + username: Notandanafnið þitt verður einstakt á %{domain} + whole_word: Þegar stikkorð eða frasi er einungis tölur og bókstafir, verður það aðeins notað ef það samsvarar heilu orði + domain_allow: + domain: Þetta lén mun geta sótt gögn af þessum vefþjóni og tekið verður á móti innsendum gögnum frá léninu til vinnslu og geymslu featured_tag: name: 'Þú gætir viljað nota eitt af þessum:' + form_challenge: + current_password: Þú ert að fara inn á öryggissvæði imports: data: CSV-skrá sem flutt hefur verið út af öðrum Mastodon-þjóni invite_request: text: Þetta mun hjálpa okkur við að yfirfara umsóknina þína + sessions: + otp: 'Settu inn tveggja-þátta kóðann sem farsímaforritið útbjó eða notaðu einn af endurheimtukóðunum þínum:' + tag: + name: Þú getur aðeins breytt stafstöði mill há-/lágstafa, til gæmis til að gera þetta læsilegra + user: + chosen_languages: Þegar merkt er við þetta, birtast einungis tíst á völdum tungumálum á opinberum tímalínum labels: account: fields: @@ -38,6 +68,10 @@ is: value: Efni account_alias: acct: Auðkenni gamla aðgangsins + account_migration: + acct: Auðkenni nýja aðgangsins + account_warning_preset: + text: Forstilltur texti admin_account_action: include_statuses: Innifela kærð tíst í tölvupóstinum send_email_notification: Láta notanda vita með tölvupósti @@ -65,6 +99,7 @@ is: expires_in: Rennur út eftir fields: Lýsigögn notandasniðs header: Síðuhaus + inbox_url: URL-slóð á innhólf endurvarpa irreversible: Fella niður í staðinn fyrir að fela locale: Tungumál viðmóts locked: Læsa aðgangi diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml index 3e2855e588..503610652f 100644 --- a/config/locales/simple_form.ja.yml +++ b/config/locales/simple_form.ja.yml @@ -83,6 +83,8 @@ ja: silence: サイレンス suspend: 停止しアカウントのデータを恒久的に削除する warning_preset_id: プリセット警告文を使用 + announcement: + text: 告知 defaults: autofollow: 招待から参加後、あなたをフォロー avatar: アイコン @@ -158,7 +160,7 @@ ja: pending_account: 新しいアカウントの承認が必要な時 reblog: トゥートがブーストされた時 report: 通報を受けた時 - trending_tag: 未審査のハッシュタグが人気の時にメールで通知する + trending_tag: 未審査のハッシュタグが人気の時 tag: listable: 検索とディレクトリへの使用を許可する name: ハッシュタグ diff --git a/config/locales/sk.yml b/config/locales/sk.yml index b127bee0f2..b02ee8eca8 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -350,9 +350,6 @@ sk: create: Pridaj doménu title: Nový email na zablokovanie title: Blokované emailové adresy - followers: - back_to_account: Späť na účet - title: Sledovatielia užívateľa %{acct} instances: by_domain: Doména delivery_available: Je v dosahu doručovania @@ -917,6 +914,7 @@ sk: duration_too_long: je príliš ďaleko do budúcnosti duration_too_short: je príliš skoro expired: Anketa už skončila + invalid_choice: Zvolená hlasovacia možnosť neexistuje over_character_limit: každá nemôže byť dlhšia ako %{max} znakov too_few_options: musí mať viac ako jednu položku too_many_options: nemôže zahŕňať viac ako %{max} položiek diff --git a/config/locales/sl.yml b/config/locales/sl.yml index c078cea1bc..afb928f117 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -320,9 +320,6 @@ sl: create: Dodaj domeno title: Nov vnos e-pošte na črni seznam title: Črni seznam e-pošt - followers: - back_to_account: Nazaj na račun - title: Sledilci od %{acct} instances: by_domain: Domena delivery_available: Na voljo je dostava diff --git a/config/locales/sq.yml b/config/locales/sq.yml index c4d95a6f75..6a7a945c4e 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -266,9 +266,6 @@ sq: create: Shtoni përkatësi title: Zë i ri email në listë bllokimesh title: Listë bllokimesh email-esh - followers: - back_to_account: Mbrapsht Te Llogaria - title: Ndjekës të %{acct} instances: delivery_available: Ka shpërndarje të mundshme known_accounts: diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 23a8266851..c686812157 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -280,9 +280,6 @@ sr: create: Додај домен title: Нова ставка е-поштe у црној листи title: Црна листа E-поште - followers: - back_to_account: Назад на налог - title: "%{acct} Пратиоци" instances: delivery_available: Достава је доступна known_accounts: diff --git a/config/locales/sv.yml b/config/locales/sv.yml index b94277825f..0094ff06bc 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -328,9 +328,6 @@ sv: create: Skapa domän title: Ny E-postdomänblocklistningsinmatning title: E-postdomänblock - followers: - back_to_account: Tillbaka till konto - title: "%{acct}'s följare" instances: by_domain: Domän moderation: diff --git a/config/locales/th.yml b/config/locales/th.yml index b7f4a5f34f..02b8fc97e4 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -306,9 +306,6 @@ th: create: เพิ่มโดเมน title: รายการบัญชีดำอีเมลใหม่ title: บัญชีดำอีเมล - followers: - back_to_account: กลับไปที่บัญชี - title: ผู้ติดตามของ %{acct} instances: by_domain: โดเมน known_accounts: @@ -386,7 +383,10 @@ th: custom_css: title: CSS ที่กำหนดเอง domain_blocks: + all: ให้กับทุกคน + disabled: ให้กับไม่มีใคร title: แสดงการปิดกั้นโดเมน + users: ให้กับผู้ใช้ในเว็บที่เข้าสู่ระบบ domain_blocks_rationale: title: แสดงคำชี้แจงเหตุผล enable_bootstrap_timeline_accounts: diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 1e80315c02..0fa4750bc1 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -6,7 +6,7 @@ tr: about_this: Bu sunucu hakkında active_count_after: etkin active_footnote: Aylık Aktif Kullanıcılar (AAK) - administered_by: 'Tarafından yönetildi:' + administered_by: 'Yöneten:' api: API apps: Mobil uygulamalar apps_platforms: İos, Android ve diğer platformlardaki Mastodon'u kullanın @@ -344,9 +344,6 @@ tr: create: Alan adı ekle title: Yeni e-posta kara liste girişi title: E-posta kara listesi - followers: - back_to_account: Hesaba Geri Dön - title: "%{acct} Takipçileri" instances: by_domain: Alan adı delivery_available: Teslimat mevcut @@ -375,6 +372,8 @@ tr: title: Davetler pending_accounts: title: Bekleyen hesaplar (%{count}) + relationships: + title: "%{acct} kişisinin ilişkileri" relays: add_new: Yeni aktarıcı ekle delete: Sil @@ -692,7 +691,7 @@ tr: directories: directory: Profil dizini explanation: Kullanıcıları ilgi alanlarına göre keşfedin - explore_mastodon: "%{title} keşfet" + explore_mastodon: "%{title} sunucusunu keşfet" domain_validator: invalid_domain: geçerli bir alan adı değil errors: @@ -721,7 +720,7 @@ tr: in_progress: Arşivinizi derliyoruz... request: Arşiv isteği size: Boyut - blocks: Blokladıklarınız + blocks: Engelledikleriniz csv: CSV domain_blocks: Alan adı blokları lists: Listeler @@ -734,6 +733,7 @@ tr: hint_html: "Öne çıkan etiketler nelerdir? Genel profilinizde belirgin bir şekilde görüntülenirler ve kişilerin genel yayınlarınıza özellikle bu etiketler altında göz atmalarına izin verir. Yaratıcı çalışmaları veya uzun vadeli projeleri takip etmek için harika bir araçtır." filters: contexts: + account: Profiller home: Ana zaman çizelgesi notifications: Bildirimler public: Genel zaman çizelgesi @@ -934,6 +934,7 @@ tr: dormant: Atıl followers: Takipçiler following: Takip edilenler + invited: Davet edildi last_active: Son aktivite most_recent: En son moved: Taşındı @@ -1215,7 +1216,7 @@ tr: full_handle_hint: Arkadaşlarınıza, size başka bir sunucudan mesaj atabilmeleri veya sizi takip edebilmeleri için söyleyeceğiniz şey budur. review_preferences_action: Tercihleri değiştirin review_preferences_step: Hangi e-postaları almak veya gönderilerinizin varsayılan olarak hangi gizlilik seviyesinde olmasını istediğiniz gibi tercihlerinizi ayarladığınızdan emin olun. Hareket hastalığınız yoksa, GIF otomatik oynatmayı etkinleştirmeyi seçebilirsiniz. - subject: Mastodon'a hoşgeldiniz + subject: Mastodon'a hoş geldiniz tip_federated_timeline: Federe zaman tüneli, Mastodon ağının genel bir görüntüsüdür. Ancak yalnızca komşularınızın abone olduğu kişileri içerir, bu yüzden tamamı değildir. tip_following: Sunucu yönetici(ler)ini varsayılan olarak takip edersiniz. Daha ilginç insanlar bulmak için yerel ve federe zaman çizelgelerini kontrol edin. tip_local_timeline: Yerel zaman çizelgesi, %{instance} üzerindeki kişilerin genel bir görüntüsüdür. Bunlar senin en yakın komşularındır! diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 32b38067be..7df8abcce4 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -352,9 +352,6 @@ uk: create: Додати домен title: Нове блокування поштового домену title: Чорний список поштових доменів - followers: - back_to_account: Повернутися до Облікового запису - title: Підписники %{acct} instances: by_domain: Домен delivery_available: Доставлення доступне diff --git a/config/locales/vi.yml b/config/locales/vi.yml index b01c1ea204..ec8e853fe8 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -336,9 +336,6 @@ vi: create: Thêm tên miền title: Mục nhập danh sách đen e-mail mới title: Danh sách đen e-mail - followers: - back_to_account: Quay lại tài khoản - title: Người theo dõi của %{acct} instances: by_domain: Miền delivery_available: Giao hàng tận nơi diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index a1deb13e20..21b0ecf782 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -338,9 +338,6 @@ zh-CN: create: 添加域名 title: 添加电子邮件域名屏蔽 title: 电子邮件域名屏蔽 - followers: - back_to_account: 返回帐户 - title: "%{acct} 的关注者" instances: by_domain: 域名 delivery_available: 无法投递 diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index 8dd4c346b4..ffd5ba5e95 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -294,9 +294,6 @@ zh-HK: create: 新增網域 title: 新增電郵網域阻隔 title: 電郵網域阻隔 - followers: - back_to_account: 返回帳戶 - title: "%{acct} 的關注者" instances: moderation: all: 全部 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 1ba2c82c8e..5b25688edb 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -294,9 +294,6 @@ zh-TW: create: 新增站點 title: 新增電子信箱黑名單項目 title: 電子信箱黑名單 - followers: - back_to_account: 返回帳戶 - title: "%{acct} 的關注者" instances: moderation: all: 全部 diff --git a/config/navigation.rb b/config/navigation.rb index ab4262182b..bd172f25fb 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -52,6 +52,7 @@ SimpleNavigation::Configuration.run do |navigation| n.item :admin, safe_join([fa_icon('cogs fw'), t('admin.title')]), admin_dashboard_url, if: proc { current_user.staff? } do |s| s.item :dashboard, safe_join([fa_icon('tachometer fw'), t('admin.dashboard.title')]), admin_dashboard_url s.item :settings, safe_join([fa_icon('cogs fw'), t('admin.settings.title')]), edit_admin_settings_url, if: -> { current_user.admin? }, highlights_on: %r{/admin/settings} + s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements} s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_url, highlights_on: %r{/admin/custom_emojis} s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_url, if: -> { current_user.admin? && !whitelist_mode? }, highlights_on: %r{/admin/relays} s.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url, link_html: { target: 'sidekiq' }, if: -> { current_user.admin? } diff --git a/config/routes.rb b/config/routes.rb index 545c07255c..322d66aec3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -175,9 +175,12 @@ Rails.application.routes.draw do get :edit end end + resources :email_domain_blocks, only: [:index, :new, :create, :destroy] resources :action_logs, only: [:index] resources :warning_presets, except: [:new] + resources :announcements, except: [:show] + resource :settings, only: [:edit, :update] resources :invites, only: [:index, :create, :destroy] do @@ -225,7 +228,7 @@ Rails.application.routes.draw do resource :reset, only: [:create] resource :action, only: [:new, :create], controller: 'account_actions' resources :statuses, only: [:index, :show, :create, :update, :destroy] - resources :followers, only: [:index] + resources :relationships, only: [:index] resource :confirmation, only: [:create] do collection do @@ -320,6 +323,16 @@ Rails.application.routes.draw do resources :scheduled_statuses, only: [:index, :show, :update, :destroy] resources :preferences, only: [:index] + resources :announcements, only: [:index] do + scope module: :announcements do + resources :reactions, only: [:update, :destroy] + end + + member do + post :dismiss + end + end + resources :conversations, only: [:index, :destroy] do member do post :read diff --git a/db/migrate/20191218153258_create_announcements.rb b/db/migrate/20191218153258_create_announcements.rb new file mode 100644 index 0000000000..58e143c920 --- /dev/null +++ b/db/migrate/20191218153258_create_announcements.rb @@ -0,0 +1,16 @@ +class CreateAnnouncements < ActiveRecord::Migration[5.2] + def change + create_table :announcements do |t| + t.text :text, null: false, default: '' + + t.boolean :published, null: false, default: false + t.boolean :all_day, null: false, default: false + + t.datetime :scheduled_at + t.datetime :starts_at + t.datetime :ends_at + + t.timestamps + end + end +end diff --git a/db/migrate/20200113125135_create_announcement_mutes.rb b/db/migrate/20200113125135_create_announcement_mutes.rb new file mode 100644 index 0000000000..c588e7fcd3 --- /dev/null +++ b/db/migrate/20200113125135_create_announcement_mutes.rb @@ -0,0 +1,12 @@ +class CreateAnnouncementMutes < ActiveRecord::Migration[5.2] + def change + create_table :announcement_mutes do |t| + t.belongs_to :account, foreign_key: { on_delete: :cascade, index: false } + t.belongs_to :announcement, foreign_key: { on_delete: :cascade } + + t.timestamps + end + + add_index :announcement_mutes, [:account_id, :announcement_id], unique: true + end +end diff --git a/db/migrate/20200114113335_create_announcement_reactions.rb b/db/migrate/20200114113335_create_announcement_reactions.rb new file mode 100644 index 0000000000..226c81a18e --- /dev/null +++ b/db/migrate/20200114113335_create_announcement_reactions.rb @@ -0,0 +1,15 @@ +class CreateAnnouncementReactions < ActiveRecord::Migration[5.2] + def change + create_table :announcement_reactions do |t| + t.belongs_to :account, foreign_key: { on_delete: :cascade, index: false } + t.belongs_to :announcement, foreign_key: { on_delete: :cascade } + + t.string :name, null: false, default: '' + t.belongs_to :custom_emoji, foreign_key: { on_delete: :cascade } + + t.timestamps + end + + add_index :announcement_reactions, [:account_id, :announcement_id, :name], unique: true, name: :index_announcement_reactions_on_account_id_and_announcement_id + end +end diff --git a/db/migrate/20200119112504_add_public_index_to_statuses.rb b/db/migrate/20200119112504_add_public_index_to_statuses.rb new file mode 100644 index 0000000000..db007848e0 --- /dev/null +++ b/db/migrate/20200119112504_add_public_index_to_statuses.rb @@ -0,0 +1,11 @@ +class AddPublicIndexToStatuses < ActiveRecord::Migration[5.2] + disable_ddl_transaction! + + def up + add_index :statuses, [:id, :account_id], name: :index_statuses_public_20200119, algorithm: :concurrently, order: { id: :desc }, where: 'deleted_at IS NULL AND visibility = 0 AND reblog_of_id IS NULL AND ((NOT reply) OR (in_reply_to_account_id = account_id))' + end + + def down + remove_index :statuses, name: :index_statuses_public_20200119 + end +end diff --git a/db/schema.rb b/db/schema.rb index b7ab74033e..2f41dee979 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_12_003415) do +ActiveRecord::Schema.define(version: 2020_01_19_112504) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -196,15 +196,49 @@ ActiveRecord::Schema.define(version: 2019_12_12_003415) do t.index ["target_type", "target_id"], name: "index_admin_action_logs_on_target_type_and_target_id" end + create_table "announcement_mutes", force: :cascade do |t| + t.bigint "account_id" + t.bigint "announcement_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "announcement_id"], name: "index_announcement_mutes_on_account_id_and_announcement_id", unique: true + t.index ["account_id"], name: "index_announcement_mutes_on_account_id" + t.index ["announcement_id"], name: "index_announcement_mutes_on_announcement_id" + end + + create_table "announcement_reactions", force: :cascade do |t| + t.bigint "account_id" + t.bigint "announcement_id" + t.string "name", default: "", null: false + t.bigint "custom_emoji_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "announcement_id", "name"], name: "index_announcement_reactions_on_account_id_and_announcement_id", unique: true + t.index ["account_id"], name: "index_announcement_reactions_on_account_id" + t.index ["announcement_id"], name: "index_announcement_reactions_on_announcement_id" + t.index ["custom_emoji_id"], name: "index_announcement_reactions_on_custom_emoji_id" + end + + create_table "announcements", force: :cascade do |t| + t.text "text", default: "", null: false + t.boolean "published", default: false, null: false + t.boolean "all_day", default: false, null: false + t.datetime "scheduled_at" + t.datetime "starts_at" + t.datetime "ends_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "backups", force: :cascade do |t| t.bigint "user_id" t.string "dump_file_name" t.string "dump_content_type" - t.bigint "dump_file_size" t.datetime "dump_updated_at" t.boolean "processed", default: false, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.bigint "dump_file_size" end create_table "blocks", force: :cascade do |t| @@ -693,6 +727,7 @@ ActiveRecord::Schema.define(version: 2019_12_12_003415) do t.datetime "deleted_at" t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" + t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))" t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id" t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id" t.index ["reblog_of_id", "account_id"], name: "index_statuses_on_reblog_of_id_and_account_id" @@ -820,6 +855,11 @@ ActiveRecord::Schema.define(version: 2019_12_12_003415) do add_foreign_key "account_warnings", "accounts", on_delete: :nullify add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade + add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade + add_foreign_key "announcement_mutes", "announcements", on_delete: :cascade + add_foreign_key "announcement_reactions", "accounts", on_delete: :cascade + add_foreign_key "announcement_reactions", "announcements", on_delete: :cascade + add_foreign_key "announcement_reactions", "custom_emojis", on_delete: :cascade add_foreign_key "backups", "users", on_delete: :nullify add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade diff --git a/lib/cli.rb b/lib/cli.rb index fbdf49fc34..19cc5d6b51 100644 --- a/lib/cli.rb +++ b/lib/cli.rb @@ -96,6 +96,8 @@ module Mastodon prompt.warn('Do NOT interrupt this process...') + Setting.registrations_mode = 'none' + Account.local.without_suspended.find_each do |account| payload = ActiveModelSerializers::SerializableResource.new( account, diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index a8e5f0b79e..e0549d0a59 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -9,15 +9,15 @@ module Mastodon end def minor - 0 - end - - def patch 1 end + def patch + 0 + end + def flags - '' + 'rc1' end def suffix diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake index fb9c89aa4c..a374e33ad2 100644 --- a/lib/tasks/auto_annotate_models.rake +++ b/lib/tasks/auto_annotate_models.rake @@ -4,6 +4,7 @@ if Rails.env.development? task :set_annotation_options do Annotate.set_defaults( 'routes' => 'false', + 'models' => 'true', 'position_in_routes' => 'before', 'position_in_class' => 'before', 'position_in_test' => 'before', diff --git a/spec/controllers/api/v1/announcements/reactions_controller_spec.rb b/spec/controllers/api/v1/announcements/reactions_controller_spec.rb new file mode 100644 index 0000000000..72620e2421 --- /dev/null +++ b/spec/controllers/api/v1/announcements/reactions_controller_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::Announcements::ReactionsController, type: :controller do + render_views + + let(:user) { Fabricate(:user) } + let(:scopes) { 'write:favourites' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + + let!(:announcement) { Fabricate(:announcement) } + + describe 'PUT #update' do + context 'without token' do + it 'returns http unauthorized' do + put :update, params: { announcement_id: announcement.id, id: '😂' } + expect(response).to have_http_status :unauthorized + end + end + + context 'with token' do + before do + allow(controller).to receive(:doorkeeper_token) { token } + put :update, params: { announcement_id: announcement.id, id: '😂' } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'creates reaction' do + expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to_not be_nil + end + end + end + + describe 'DELETE #destroy' do + before do + announcement.announcement_reactions.create!(account: user.account, name: '😂') + end + + context 'without token' do + it 'returns http unauthorized' do + delete :destroy, params: { announcement_id: announcement.id, id: '😂' } + expect(response).to have_http_status :unauthorized + end + end + + context 'with token' do + before do + allow(controller).to receive(:doorkeeper_token) { token } + delete :destroy, params: { announcement_id: announcement.id, id: '😂' } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'creates reaction' do + expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to be_nil + end + end + end +end diff --git a/spec/controllers/api/v1/announcements_controller_spec.rb b/spec/controllers/api/v1/announcements_controller_spec.rb new file mode 100644 index 0000000000..6ee46b60eb --- /dev/null +++ b/spec/controllers/api/v1/announcements_controller_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::AnnouncementsController, type: :controller do + render_views + + let(:user) { Fabricate(:user) } + let(:scopes) { 'read' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + + let!(:announcement) { Fabricate(:announcement) } + + describe 'GET #index' do + context 'without token' do + it 'returns http unprocessable entity' do + get :index + expect(response).to have_http_status :unprocessable_entity + end + end + + context 'with token' do + before do + allow(controller).to receive(:doorkeeper_token) { token } + get :index + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + end + + describe 'POST #dismiss' do + context 'without token' do + it 'returns http unauthorized' do + post :dismiss, params: { id: announcement.id } + expect(response).to have_http_status :unauthorized + end + end + + context 'with token' do + let(:scopes) { 'write:accounts' } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + post :dismiss, params: { id: announcement.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'dismisses announcement' do + expect(announcement.announcement_mutes.find_by(account: user.account)).to_not be_nil + end + end + end +end diff --git a/spec/controllers/api/v1/trends_controller_spec.rb b/spec/controllers/api/v1/trends_controller_spec.rb new file mode 100644 index 0000000000..91e0d18fe7 --- /dev/null +++ b/spec/controllers/api/v1/trends_controller_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::TrendsController, type: :controller do + render_views + + describe 'GET #index' do + before do + allow(TrendingTags).to receive(:get).and_return(Fabricate.times(10, :tag)) + get :index + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/fabricators/announcement_fabricator.rb b/spec/fabricators/announcement_fabricator.rb new file mode 100644 index 0000000000..5a3871d901 --- /dev/null +++ b/spec/fabricators/announcement_fabricator.rb @@ -0,0 +1,6 @@ +Fabricator(:announcement) do + text { Faker::Lorem.paragraph(sentence_count: 2) } + published true + starts_at nil + ends_at nil +end diff --git a/spec/fabricators/announcement_mute_fabricator.rb b/spec/fabricators/announcement_mute_fabricator.rb new file mode 100644 index 0000000000..c4eafe8f4c --- /dev/null +++ b/spec/fabricators/announcement_mute_fabricator.rb @@ -0,0 +1,4 @@ +Fabricator(:announcement_mute) do + account + announcement +end diff --git a/spec/fabricators/announcement_reaction_fabricator.rb b/spec/fabricators/announcement_reaction_fabricator.rb new file mode 100644 index 0000000000..f923c59c60 --- /dev/null +++ b/spec/fabricators/announcement_reaction_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator(:announcement_reaction) do + account + announcement + name '🌿' +end diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb index bb938e36d9..651927c2dd 100644 --- a/spec/fabricators/media_attachment_fabricator.rb +++ b/spec/fabricators/media_attachment_fabricator.rb @@ -1,16 +1,12 @@ Fabricator(:media_attachment) do account + file do |attrs| - [ - case attrs[:type] - when :gifv - attachment_fixture ['attachment.gif', 'attachment.webm'].sample - when :image - attachment_fixture 'attachment.jpg' - when nil - attachment_fixture ['attachment.gif', 'attachment.jpg', 'attachment.webm'].sample - end, - nil - ].sample + case attrs[:type] + when :gifv, :video + attachment_fixture('attachment.webm') + else + attachment_fixture('attachment.jpg') + end end end diff --git a/spec/lib/formatter_spec.rb b/spec/lib/formatter_spec.rb index 83be0a5883..633d59c2ab 100644 --- a/spec/lib/formatter_spec.rb +++ b/spec/lib/formatter_spec.rb @@ -258,6 +258,14 @@ RSpec.describe Formatter do is_expected.to include 'href="xmpp:muc@instance.com?join"' end end + + context 'given text containing a magnet: URI' do + let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' } + + it 'matches the full URI' do + is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"' + end + end end describe '#format_spoiler' do diff --git a/spec/middleware/handle_bad_encoding_middleware_spec.rb b/spec/middleware/handle_bad_encoding_middleware_spec.rb deleted file mode 100644 index 8c0d24f182..0000000000 --- a/spec/middleware/handle_bad_encoding_middleware_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails_helper' - -RSpec.describe HandleBadEncodingMiddleware do - let(:app) { double() } - let(:middleware) { HandleBadEncodingMiddleware.new(app) } - - it "request with query string is unchanged" do - expect(app).to receive(:call).with("PATH" => "/some/path", "QUERY_STRING" => "name=fred") - middleware.call("PATH" => "/some/path", "QUERY_STRING" => "name=fred") - end - - it "request with no query string is unchanged" do - expect(app).to receive(:call).with("PATH" => "/some/path") - middleware.call("PATH" => "/some/path") - end - - it "request with invalid encoding in query string drops query string" do - expect(app).to receive(:call).with("QUERY_STRING" => "", "PATH" => "/some/path") - middleware.call("QUERY_STRING" => "q=%2Fsearch%2Fall%Forder%3Ddescending%26page%3D5%26sort%3Dcreated_at", "PATH" => "/some/path") - end -end diff --git a/spec/models/announcement_mute_spec.rb b/spec/models/announcement_mute_spec.rb new file mode 100644 index 0000000000..9d0e4c9037 --- /dev/null +++ b/spec/models/announcement_mute_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe AnnouncementMute, type: :model do +end diff --git a/spec/models/announcement_reaction_spec.rb b/spec/models/announcement_reaction_spec.rb new file mode 100644 index 0000000000..f6e1515840 --- /dev/null +++ b/spec/models/announcement_reaction_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe AnnouncementReaction, type: :model do +end diff --git a/spec/models/announcement_spec.rb b/spec/models/announcement_spec.rb new file mode 100644 index 0000000000..7f7b647a9e --- /dev/null +++ b/spec/models/announcement_spec.rb @@ -0,0 +1,4 @@ +require 'rails_helper' + +RSpec.describe Announcement, type: :model do +end diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index a275621a13..456bc42167 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -31,14 +31,6 @@ RSpec.describe MediaAttachment, type: :model do context 'file is blank' do let(:file) { nil } - context 'remote_url is blank' do - let(:remote_url) { '' } - - it 'returns false' do - is_expected.to be false - end - end - context 'remote_url is present' do let(:remote_url) { 'remote_url' } @@ -153,6 +145,11 @@ RSpec.describe MediaAttachment, type: :model do end end + it 'is invalid without file' do + media = MediaAttachment.new(account: Fabricate(:account)) + expect(media.valid?).to be false + end + describe 'descriptions for remote attachments' do it 'are cut off at 1500 characters' do media = Fabricate(:media_attachment, description: 'foo' * 1000, remote_url: 'http://example.com/blah.jpg') diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb index bf06f50e9b..025a3da40c 100644 --- a/spec/services/post_status_service_spec.rb +++ b/spec/services/post_status_service_spec.rb @@ -212,14 +212,18 @@ RSpec.describe PostStatusService, type: :service do it 'does not allow attaching both videos and images' do account = Fabricate(:account) + video = Fabricate(:media_attachment, type: :video, account: account) + image = Fabricate(:media_attachment, type: :image, account: account) + + video.update(type: :video) expect do subject.call( account, text: "test status update", media_ids: [ - Fabricate(:media_attachment, type: :video, account: account), - Fabricate(:media_attachment, type: :image, account: account), + video, + image, ].map(&:id), ) end.to raise_error(