Merge remote-tracking branch 'glitch-soc/main'

This commit is contained in:
Tykayn 2022-04-01 14:33:32 +02:00 committed by tykayn
commit 034466d82c
165 changed files with 3103 additions and 2225 deletions

View File

@ -1,7 +1,7 @@
version: 2.1
orbs:
ruby: circleci/ruby@1.4.0
ruby: circleci/ruby@1.4.1
node: circleci/node@5.0.1
executors:

View File

@ -3,7 +3,7 @@ Changelog
All notable changes to this project will be documented in this file.
## Unreleased
## [3.5.0] - 2022-03-30
### Added
- **Add support for incoming edited posts** ([Gargron](https://github.com/mastodon/mastodon/pull/16697), [Gargron](https://github.com/mastodon/mastodon/pull/17727), [Gargron](https://github.com/mastodon/mastodon/pull/17728), [Gargron](https://github.com/mastodon/mastodon/pull/17320), [Gargron](https://github.com/mastodon/mastodon/pull/17404), [Gargron](https://github.com/mastodon/mastodon/pull/17390), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17335), [Gargron](https://github.com/mastodon/mastodon/pull/17696), [Gargron](https://github.com/mastodon/mastodon/pull/17745), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17740), [Gargron](https://github.com/mastodon/mastodon/pull/17697), [Gargron](https://github.com/mastodon/mastodon/pull/17648), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17531), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17499), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17498), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17380), [Gargron](https://github.com/mastodon/mastodon/pull/17373), [Gargron](https://github.com/mastodon/mastodon/pull/17334), [Gargron](https://github.com/mastodon/mastodon/pull/17333), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17699), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17748))
@ -81,6 +81,7 @@ All notable changes to this project will be documented in this file.
- Add lazy loading for emoji picker in web UI ([mashirozx](https://github.com/mastodon/mastodon/pull/16907), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17011))
- Add single option votes tooltip in polls in web UI ([Brawaru](https://github.com/mastodon/mastodon/pull/16849))
- Add confirmation modal when closing media edit modal with unsaved changes in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16518))
- Add hint about missing media attachment description in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17845))
- Add support for fetching Create and Announce activities by URI in ActivityPub ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16383))
- Add `S3_FORCE_SINGLE_REQUEST` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16866))
- Add `OMNIAUTH_ONLY` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17288), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17345))
@ -130,6 +131,11 @@ All notable changes to this project will be documented in this file.
### Fixed
- Fix IDN domains not being rendered correctly in a few left-over places ([Gargron](https://github.com/mastodon/mastodon/pull/17848))
- Fix Sanskrit translation not being used in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17820))
- Fix Kurdish languages having the wrong language codes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17812))
- Fix pghero making database schema suggestions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17807))
- Fix encoding glitch in the OpenGraph description of a profile page ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17821))
- Fix web manifest not permitting PWA usage from alternate domains ([HolgerHuo](https://github.com/mastodon/mastodon/pull/16714))
- Fix not being able to edit media attachments for scheduled posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17690))
- Fix subscribed relay activities being recorded as boosts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17571))
@ -191,6 +197,11 @@ All notable changes to this project will be documented in this file.
- Fix hashtag autocomplete overriding user-typed case ([weex](https://github.com/mastodon/mastodon/pull/16460))
- Fix WebAuthn authentication setup to not prompt for PIN ([truongnmt](https://github.com/mastodon/mastodon/pull/16545))
### Security
- Fix being able to post URLs longer than 4096 characters ([Gargron](https://github.com/mastodon/mastodon/pull/17908))
- Fix being able to bypass e-mail restrictions ([Gargron](https://github.com/mastodon/mastodon/pull/17909))
## [3.4.6] - 2022-02-03
### Fixed

View File

@ -139,7 +139,7 @@ group :development do
gem 'brakeman', '~> 5.2', require: false
gem 'bundler-audit', '~> 0.9', require: false
gem 'capistrano', '~> 3.16'
gem 'capistrano', '~> 3.17'
gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano-yarn', '~> 2.0'

View File

@ -96,7 +96,7 @@ GEM
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.16)
bcrypt (3.1.17)
better_errors (2.9.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
@ -121,7 +121,7 @@ GEM
bundler (>= 1.2.0, < 3)
thor (~> 1.0)
byebug (11.1.3)
capistrano (3.16.0)
capistrano (3.17.0)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
@ -157,7 +157,7 @@ GEM
climate_control (0.2.0)
coderay (1.1.3)
color_diff (0.1)
concurrent-ruby (1.1.9)
concurrent-ruby (1.1.10)
connection_pool (2.2.5)
cose (1.0.0)
cbor (~> 0.5.9)
@ -174,11 +174,11 @@ GEM
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise-two-factor (4.0.1)
activesupport (< 6.2)
devise-two-factor (4.0.2)
activesupport (< 7.1)
attr_encrypted (>= 1.3, < 4, != 2)
devise (~> 4.0)
railties (< 6.2)
railties (< 7.1)
rotp (~> 6.0)
devise_pam_authenticatable2 (9.2.0)
devise (>= 4.0.0)
@ -433,7 +433,7 @@ GEM
openssl-signature_algorithm (0.4.0)
orm_adapter (0.5.0)
ox (2.14.10)
parallel (1.22.0)
parallel (1.22.1)
parser (3.1.1.0)
ast (~> 2.4.1)
parslet (2.0.0)
@ -562,7 +562,7 @@ GEM
rspec-support (3.11.0)
rspec_junit_formatter (0.5.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.26.0)
rubocop (1.26.1)
parallel (~> 1.10)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
@ -735,7 +735,7 @@ DEPENDENCIES
browser
bullet (~> 7.0)
bundler-audit (~> 0.9)
capistrano (~> 3.16)
capistrano (~> 3.17)
capistrano-rails (~> 1.6)
capistrano-rbenv (~> 2.2)
capistrano-yarn (~> 2.0)

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
include FormattingHelper
settings index: { refresh_interval: '15m' }, analysis: {
filter: {
english_stop: {
@ -57,7 +59,7 @@ class StatusesIndex < Chewy::Index
field :id, type: 'long'
field :account_id, type: 'long'
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
field :text, type: 'text', value: ->(status) { status.searchable_text } do
field :stemmed, type: 'text', analyzer: 'content'
end

View File

@ -104,13 +104,27 @@ class Api::V1::Admin::AccountsController < Api::BaseController
end
def filtered_accounts
AccountFilter.new(filter_params).results
AccountFilter.new(translated_filter_params).results
end
def filter_params
params.permit(*FILTER_PARAMS)
end
def translated_filter_params
translated_params = { origin: 'local', status: 'active' }.merge(filter_params.slice(*AccountFilter::KEYS))
translated_params[:origin] = 'remote' if params[:remote].present?
%i(active pending disabled silenced suspended).each do |status|
translated_params[:status] = status.to_s if params[status].present?
end
translated_params[:permissions] = 'staff' if params[:staff].present?
translated_params
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end

View File

@ -3,6 +3,10 @@
class Api::V1::Trends::LinksController < Api::BaseController
before_action :set_links
after_action :insert_pagination_headers
DEFAULT_LINKS_LIMIT = 10
def index
render json: @links, each_serializer: REST::Trends::LinkSerializer
end
@ -20,6 +24,26 @@ class Api::V1::Trends::LinksController < Api::BaseController
end
def links_from_trends
Trends.links.query.allowed.in_locale(content_locale).limit(limit_param(10))
Trends.links.query.allowed.in_locale(content_locale).offset(offset_param).limit(limit_param(DEFAULT_LINKS_LIMIT))
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT))
end
def prev_path
api_v1_trends_links_url pagination_params(offset: offset_param - limit_param(DEFAULT_LINKS_LIMIT)) if offset_param > limit_param(DEFAULT_LINKS_LIMIT)
end
def offset_param
params[:offset].to_i
end
end

View File

@ -3,6 +3,8 @@
class Api::V1::Trends::StatusesController < Api::BaseController
before_action :set_statuses
after_action :insert_pagination_headers
def index
render json: @statuses, each_serializer: REST::StatusSerializer
end
@ -22,6 +24,26 @@ class Api::V1::Trends::StatusesController < Api::BaseController
def statuses_from_trends
scope = Trends.statuses.query.allowed.in_locale(content_locale)
scope = scope.filtered_for(current_account) if user_signed_in?
scope.limit(limit_param(DEFAULT_STATUSES_LIMIT))
scope.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT))
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT))
end
def prev_path
api_v1_trends_statuses_url pagination_params(offset: offset_param - limit_param(DEFAULT_STATUSES_LIMIT)) if offset_param > limit_param(DEFAULT_STATUSES_LIMIT)
end
def offset_param
params[:offset].to_i
end
end

View File

@ -3,6 +3,10 @@
class Api::V1::Trends::TagsController < Api::BaseController
before_action :set_tags
after_action :insert_pagination_headers
DEFAULT_TAGS_LIMIT = 10
def index
render json: @tags, each_serializer: REST::TagSerializer
end
@ -12,10 +16,30 @@ class Api::V1::Trends::TagsController < Api::BaseController
def set_tags
@tags = begin
if Setting.trends
Trends.tags.query.allowed.limit(limit_param(10))
Trends.tags.query.allowed.limit(limit_param(DEFAULT_TAGS_LIMIT))
else
[]
end
end
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path
api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT))
end
def prev_path
api_v1_trends_tags_url pagination_params(offset: offset_param - limit_param(DEFAULT_TAGS_LIMIT)) if offset_param > limit_param(DEFAULT_TAGS_LIMIT)
end
def offset_param
params[:offset].to_i
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
FILTER_PARAMS = %i(
origin
status
permissions
username
by_domain
display_name
email
ip
invited_by
).freeze
PAGINATION_PARAMS = (%i(limit) + FILTER_PARAMS).freeze
private
def filtered_accounts
AccountFilter.new(filter_params).results
end
def filter_params
params.permit(*FILTER_PARAMS)
end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end

View File

@ -15,7 +15,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
return not_found if oembed.nil?
begin
oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
rescue ArgumentError
return not_found
end

View File

@ -2,10 +2,12 @@
module AccountsHelper
def display_name(account, **options)
str = account.display_name.presence || account.username
if options[:custom_emojify]
Formatter.instance.format_display_name(account, **options)
prerender_custom_emojis(h(str), account.emojis)
else
account.display_name.presence || account.username
str
end
end

View File

@ -12,9 +12,6 @@ module Admin::Trends::StatusesHelper
return '' if text.blank?
html = Formatter.instance.send(:encode, text)
html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
html.html_safe # rubocop:disable Rails/OutputSafety
prerender_custom_emojis(h(text), status.emojis)
end
end

View File

@ -240,4 +240,8 @@ module ApplicationHelper
end
end.values
end
def prerender_custom_emojis(html, custom_emojis)
EmojiFormatter.new(html, custom_emojis, animate: prefers_autoplay?).to_s
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module FormattingHelper
def html_aware_format(text, local, options = {})
HtmlAwareFormatter.new(text, local, options).to_s
end
def linkify(text, options = {})
TextFormatter.new(text, options).to_s
end
def extract_status_plain_text(status)
PlainTextFormatter.new(status.text, status.local?).to_s
end
module_function :extract_status_plain_text
def status_content_format(status)
html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type)
end
def account_bio_format(account)
html_aware_format(account.note, account.local?)
end
def account_field_value_format(field, with_rel_me: true)
html_aware_format(field.value, field.account.local?, with_rel_me: with_rel_me, with_domains: true, multiline: false)
end
end

View File

@ -2,6 +2,7 @@
module RoutingHelper
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include Webpacker::Helper
@ -22,8 +23,6 @@ module RoutingHelper
full_asset_url(asset_pack_path(source, **options))
end
private
def use_storage?
Rails.configuration.x.use_s3 || Rails.configuration.x.use_swift
end

View File

@ -113,20 +113,6 @@ module StatusesHelper
end
end
private
def simplified_text(text)
text.dup.tap do |new_text|
URI.extract(new_text).each do |url|
new_text.gsub!(url, '')
end
new_text.gsub!(Account::MENTION_RE, '')
new_text.gsub!(Tag::HASHTAG_RE, '')
new_text.gsub!(/\s+/, '')
end
end
def embedded_view?
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
end

View File

@ -33,6 +33,7 @@ export default class Counter extends React.PureComponent {
label: PropTypes.string.isRequired,
href: PropTypes.string,
params: PropTypes.object,
target: PropTypes.string,
};
state = {
@ -54,7 +55,7 @@ export default class Counter extends React.PureComponent {
}
render () {
const { label, href } = this.props;
const { label, href, target } = this.props;
const { loading, data } = this.state;
let content;
@ -100,7 +101,7 @@ export default class Counter extends React.PureComponent {
if (href) {
return (
<a href={href} className='sparkline'>
<a href={href} className='sparkline' target={target}>
{inner}
</a>
);

View File

@ -33,6 +33,7 @@ export default class Counter extends React.PureComponent {
label: PropTypes.string.isRequired,
href: PropTypes.string,
params: PropTypes.object,
target: PropTypes.string,
};
state = {
@ -54,7 +55,7 @@ export default class Counter extends React.PureComponent {
}
render () {
const { label, href } = this.props;
const { label, href, target } = this.props;
const { loading, data } = this.state;
let content;
@ -100,7 +101,7 @@ export default class Counter extends React.PureComponent {
if (href) {
return (
<a href={href} className='sparkline'>
<a href={href} className='sparkline' target={target}>
{inner}
</a>
);

View File

@ -75,7 +75,7 @@
"column.domain_blocks": "Blokované domény",
"column.favourites": "Oblíbené",
"column.follow_requests": "Žádosti o sledování",
"column.home": "Domů",
"column.home": "Domovská časová osa",
"column.lists": "Seznamy",
"column.mutes": "Skrytí uživatelé",
"column.notifications": "Oznámení",
@ -294,7 +294,7 @@
"navigation_bar.discover": "Objevujte",
"navigation_bar.domain_blocks": "Blokované domény",
"navigation_bar.edit_profile": "Upravit profil",
"navigation_bar.explore": "Explore",
"navigation_bar.explore": "Objevování",
"navigation_bar.favourites": "Oblíbené",
"navigation_bar.filters": "Skrytá slova",
"navigation_bar.follow_requests": "Žádosti o sledování",
@ -318,7 +318,7 @@
"notification.poll": "Anketa, ve které jste hlasovali, skončila",
"notification.reblog": "Uživatel {name} boostnul váš příspěvek",
"notification.status": "Nový příspěvek od {name}",
"notification.update": "{name} edited a post",
"notification.update": "uživatel {name} upravil příspěvek",
"notifications.clear": "Smazat oznámení",
"notifications.clear_confirmation": "Opravdu chcete trvale smazat všechna vaše oznámení?",
"notifications.column_settings.admin.sign_up": "New sign-ups:",
@ -338,7 +338,7 @@
"notifications.column_settings.status": "Nové příspěvky:",
"notifications.column_settings.unread_notifications.category": "Nepřečtená oznámení",
"notifications.column_settings.unread_notifications.highlight": "Zvýraznit nepřečtená oznámení",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Úpravy:",
"notifications.filter.all": "Vše",
"notifications.filter.boosts": "Boosty",
"notifications.filter.favourites": "Oblíbení",
@ -380,7 +380,7 @@
"relative_time.days": "{number} d",
"relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
"relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
"relative_time.full.just_now": "just now",
"relative_time.full.just_now": "právě teď",
"relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
"relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
"relative_time.hours": "{number} h",
@ -391,11 +391,11 @@
"reply_indicator.cancel": "Zrušit",
"report.block": "Block",
"report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
"report.categories.other": "Other",
"report.categories.other": "Ostatní",
"report.categories.spam": "Spam",
"report.categories.violation": "Content violates one or more server rules",
"report.category.subtitle": "Choose the best match",
"report.category.title": "Tell us what's going on with this {type}",
"report.categories.violation": "Obsah porušuje jedno nebo více pravidel serveru",
"report.category.subtitle": "Vyberte nejbližší možnost",
"report.category.title": "Povězte nám, proč chcete {type} nahlásit",
"report.category.title_account": "profile",
"report.category.title_status": "post",
"report.close": "Done",
@ -404,20 +404,20 @@
"report.forward_hint": "Tento účet je z jiného serveru. Chcete na něj také poslat anonymizovanou kopii hlášení?",
"report.mute": "Mute",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
"report.next": "Next",
"report.next": "Dále",
"report.placeholder": "Dodatečné komentáře",
"report.reasons.dislike": "I don't like it",
"report.reasons.dislike_description": "It is not something you want to see",
"report.reasons.other": "It's something else",
"report.reasons.other_description": "The issue does not fit into other categories",
"report.reasons.spam": "It's spam",
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
"report.reasons.violation": "It violates server rules",
"report.reasons.violation_description": "You are aware that it breaks specific rules",
"report.rules.subtitle": "Select all that apply",
"report.rules.title": "Which rules are being violated?",
"report.statuses.subtitle": "Select all that apply",
"report.statuses.title": "Are there any posts that back up this report?",
"report.reasons.dislike": "Nelíbí se mi",
"report.reasons.dislike_description": "Není to něco, co chcete vidět",
"report.reasons.other": "Jde o něco jiného",
"report.reasons.other_description": "Problém neodpovídá ostatním kategoriím",
"report.reasons.spam": "Je to spam",
"report.reasons.spam_description": "Škodlivé odkazy, falešné interakce nebo opakované odpovědi",
"report.reasons.violation": "Porušuje pravidla serveru",
"report.reasons.violation_description": "Máte za to, že porušuje konkrétní pravidla",
"report.rules.subtitle": "Vyberte všechna relevantní",
"report.rules.title": "Která pravidla porušuje?",
"report.statuses.subtitle": "Vyberte všechny relevantní",
"report.statuses.title": "Existují příspěvky dokládající toto hlášení?",
"report.submit": "Odeslat",
"report.target": "Nahlášení uživatele {target}",
"report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
@ -490,7 +490,7 @@
"suggestions.dismiss": "Odmítnout návrh",
"suggestions.header": "Mohlo by vás zajímat…",
"tabs_bar.federated_timeline": "Federovaná",
"tabs_bar.home": "Domů",
"tabs_bar.home": "Domovská",
"tabs_bar.local_timeline": "Místní",
"tabs_bar.notifications": "Oznámení",
"tabs_bar.search": "Hledat",

View File

@ -3,16 +3,16 @@
"account.add_or_remove_from_list": "افزودن یا برداشتن از سیاهه‌ها",
"account.badges.bot": "روبات",
"account.badges.group": "گروه",
"account.block": "مسدود کردن @{name}",
"account.block": "مسدود کردن @{name}",
"account.block_domain": "مسدود کردن دامنهٔ {domain}",
"account.blocked": "مسدود",
"account.browse_more_on_origin_server": "مرور بیش‌تر روی نمایهٔ اصلی",
"account.cancel_follow_request": "لغو درخواست پی‌گیری",
"account.direct": "پیام مستقیم به @{name}",
"account.disable_notifications": "آگاهی به من هنگام فرستادن‌های @{name} پایان یابد",
"account.direct": "پیام مستقیم به @{name}",
"account.disable_notifications": "آگاه کردن من هنگام فرسته‌های @{name} را متوقّف کن",
"account.domain_blocked": "دامنه مسدود شد",
"account.edit_profile": "ویرایش نمایه",
"account.enable_notifications": "هنگام فرسته‌های @{name} مرا آگاه کن",
"account.enable_notifications": "هنگام فرسته‌های @{name} مرا آگاه کن",
"account.endorse": "معرّفی در نمایه",
"account.follow": "پی‌گیری",
"account.followers": "پی‌گیرندگان",
@ -22,34 +22,34 @@
"account.following_counter": "{count, plural, one {{counter} پی‌گرفته} other {{counter} پی‌گرفته}}",
"account.follows.empty": "این کاربر هنوز پی‌گیر کسی نیست.",
"account.follows_you": "پی می‌گیردتان",
"account.hide_reblogs": "نهفتن تقویت‌های @{name}",
"account.hide_reblogs": "نهفتن تقویت‌های @{name}",
"account.joined": "پیوسته از {date}",
"account.link_verified_on": "مالکیت این پیوند در {date} بررسی شد",
"account.locked_info": "این حساب خصوصی است. صاحبش تصمیم می‌گیرد که چه کسی پی‌گیرش باشد.",
"account.media": "رسانه",
"account.mention": "نام‌بردن از @{name}",
"account.mention": "نام‌بردن از @{name}",
"account.moved_to": "{name} منتقل شده به:",
"account.mute": "خموشاندن @{name}",
"account.mute_notifications": "خموشاندن آگاهی‌ها از @{name}",
"account.mute": "خموشاندن @{name}",
"account.mute_notifications": "خموشاندن آگاهی‌های @{name}",
"account.muted": "خموش",
"account.posts": "فرسته",
"account.posts_with_replies": "فرسته‌ها و پاسخ‌ها",
"account.report": "گزارش @{name}",
"account.report": "گزارش @{name}",
"account.requested": "منتظر پذیرش است. برای لغو درخواست پی‌گیری کلیک کنید",
"account.share": "هم‌رسانی نمایهٔ @{name}",
"account.show_reblogs": "نمایش تقویت‌های @{name}",
"account.share": "هم‌رسانی نمایهٔ @{name}",
"account.show_reblogs": "نمایش تقویت‌های @{name}",
"account.statuses_counter": "{count, plural, one {{counter} فرسته} other {{counter} فرسته}}",
"account.unblock": "رفع مسدودیت @{name}",
"account.unblock": "رفع مسدودیت @{name}",
"account.unblock_domain": "رفع مسدودیت دامنهٔ {domain}",
"account.unblock_short": "رفع مسدودیت",
"account.unendorse": "معرّفی نکردن در نمایه",
"account.unfollow": "ناپی‌گیری",
"account.unmute": "ناخموشی @{name}",
"account.unmute_notifications": "ناخموشی آگاهی‌ها از @{name}",
"account.unmute": "ناخموشی @{name}",
"account.unmute_notifications": "ناخموشی آگاهی‌های @{name}",
"account.unmute_short": "ناخموشی",
"account_note.placeholder": "برای افزودن یادداشت کلیک کنید",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
"admin.dashboard.daily_retention": "نرخ حفظ کاربر در روز پس از ثبت نام",
"admin.dashboard.monthly_retention": "نرخ حفظ کاربر در ماه پس از ثبت نام",
"admin.dashboard.retention.average": "میانگین",
"admin.dashboard.retention.cohort": "ماه ثبت‌نام",
"admin.dashboard.retention.cohort_size": "کاربران جدید",
@ -79,13 +79,13 @@
"column.lists": "سیاهه‌ها",
"column.mutes": "کاربران خموش",
"column.notifications": "آگاهی‌ها",
"column.pins": "فرسته‌های سنجاقشده",
"column.pins": "فرسته‌های سنجاق شده",
"column.public": "خط زمانی همگانی",
"column_back_button.label": "بازگشت",
"column_header.hide_settings": "نهفتن تنظیمات",
"column_header.moveLeft_settings": "جابه‌جایی ستون به چپ",
"column_header.moveRight_settings": "جابه‌جایی ستون به راست",
"column_header.pin": "سنجاقکردن",
"column_header.pin": "سنجاق کردن",
"column_header.show_settings": "نمایش تنظیمات",
"column_header.unpin": "برداشتن سنجاق",
"column_subheading.settings": "تنظیمات",
@ -94,7 +94,7 @@
"community.column_settings.remote_only": "تنها دوردست",
"compose_form.direct_message_warning": "این فرسته تنها به کاربرانی که از آن‌ها نام برده شده فرستاده خواهد شد.",
"compose_form.direct_message_warning_learn_more": "بیشتر بدانید",
"compose_form.hashtag_warning": "از آن‌جا که این فرسته فهرست‌نشده است، در نتایج جست‌وجوی برچسبها پیدا نخواهد شد. تنها فرسته‌های عمومی را می‌توان با جست‌وجوی برچسب یافت.",
"compose_form.hashtag_warning": "از آن‌جا که این فرسته فهرست نشده است، در نتایج جست‌وجوی هشتگها پیدا نخواهد شد. تنها فرسته‌های عمومی را می‌توان با جست‌وجوی هشتگ یافت.",
"compose_form.lock_disclaimer": "حسابتان {locked} نیست. هر کسی می‌تواند پی‌گیرتان شده و فرسته‌های ویژهٔ پی‌گیرانتان را ببیند.",
"compose_form.lock_disclaimer.lock": "قفل‌شده",
"compose_form.placeholder": "تازه چه خبر؟",
@ -144,7 +144,7 @@
"directory.local": "تنها از {domain}",
"directory.new_arrivals": "تازه‌واردان",
"directory.recently_active": "کاربران فعال اخیر",
"embed.instructions": "برای جاگذاری این فرسته در سایت خودتان، کد زیر را کپی کنید.",
"embed.instructions": "برای جاسازی این فرسته در سایت خودتان، کد زیر را رونوشت کنید.",
"embed.preview": "این گونه دیده خواهد شد:",
"emoji_button.activity": "فعالیت",
"emoji_button.custom": "سفارشی",
@ -164,11 +164,11 @@
"empty_column.account_timeline": "هیچ فرسته‌ای این‌جا نیست!",
"empty_column.account_unavailable": "نمایهٔ موجود نیست",
"empty_column.blocks": "هنوز کسی را مسدود نکرده‌اید.",
"empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشان‌شده‌ای ندارید. هنگامی که فرسته‌ای را نشان‌کنید، این‌جا نشان داده خواهد شد.",
"empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشانهگذاری شده‌ای ندارید. هنگامی که فرسته‌ای را نشانهگذاری کنید، این‌جا نشان داده خواهد شد.",
"empty_column.community": "خط زمانی محلّی خالی است. چیزی بنویسید تا چرخش بچرخد!",
"empty_column.direct": "هنوز هیچ پیام مستقیمی ندارید. هنگامی که چنین پیامی بگیرید یا بفرستید این‌جا نشان داده خواهد شد.",
"empty_column.domain_blocks": "هنوز هیچ دامنه‌ای مسدود نشده است.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
"empty_column.explore_statuses": "الآن چیزی پرطرفدار نیست. بعداً دوباره بررسی کنید!",
"empty_column.favourited_statuses": "شما هنوز هیچ فرسته‌ای را نپسندیده‌اید. هنگامی که فرسته‌ای را بپسندید، این‌جا نشان داده خواهد شد.",
"empty_column.favourites": "هنوز هیچ کسی این فرسته را نپسندیده است. هنگامی که کسی آن را بپسندد، این‌جا نشان داده خواهد شد.",
"empty_column.follow_recommendations": "ظاهرا هیچ پیشنهادی برای شما نمی‌توانیم تولید کنیم. می‌توانید از امکان جست‌وجو برای یافتن افرادی که ممکن است بشناسید و یا کاوش میان برچسب‌های داغ استفاده کنید.",
@ -247,7 +247,7 @@
"keyboard_shortcuts.my_profile": "گشودن نمایه‌تان",
"keyboard_shortcuts.notifications": "گشودن ستون آگاهی‌ها",
"keyboard_shortcuts.open_media": "گشودن رسانه",
"keyboard_shortcuts.pinned": "گشودن سیاههٔ فرسته‌های سنجاق شده",
"keyboard_shortcuts.pinned": "گشودن فهرست فرسته‌های سنجاق شده",
"keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
"keyboard_shortcuts.reply": "پاسخ به فرسته",
"keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری",
@ -305,7 +305,7 @@
"navigation_bar.logout": "خروج",
"navigation_bar.mutes": "کاربران خموشانده",
"navigation_bar.personal": "شخصی",
"navigation_bar.pins": "فرسته‌های سنجاقشده",
"navigation_bar.pins": "فرسته‌های سنجاق شده",
"navigation_bar.preferences": "ترجیحات",
"navigation_bar.public_timeline": "خط زمانی همگانی",
"navigation_bar.security": "امنیت",
@ -392,40 +392,40 @@
"report.block": "مسدود کردن",
"report.block_explanation": "شما فرسته‌هایشان را نخواهید دید. آن‌ها نمی‌توانند فرسته‌هایتان را ببینند یا شما را پی‌بگیرند. آنها می‌توانند بگویند که مسدود شده‌اند.",
"report.categories.other": "غیره",
"report.categories.spam": "Spam",
"report.categories.violation": "Content violates one or more server rules",
"report.category.subtitle": "Choose the best match",
"report.category.title": "Tell us what's going on with this {type}",
"report.category.title_account": "profile",
"report.category.title_status": "post",
"report.close": "Done",
"report.comment.title": "Is there anything else you think we should know?",
"report.categories.spam": "هرزنامه",
"report.categories.violation": "محتوا یک یا چند قانون کارساز را نقض می‌کند",
"report.category.subtitle": "منطبق‌ترین را انتخاب کنید",
"report.category.title": "به ما بگویید با این {type} چه مشکلی دارید",
"report.category.title_account": "نمایه",
"report.category.title_status": "فرسته",
"report.close": "انجام شد",
"report.comment.title": "آیا چیز دیگری هست که فکر می‌کنید باید بدانیم؟",
"report.forward": "فرستادن به {target}",
"report.forward_hint": "این حساب در کارساز دیگری ثبت شده. آیا می‌خواهید رونوشتی ناشناس از این گزارش به آن‌جا هم فرستاده شود؟",
"report.mute": "Mute",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
"report.next": "Next",
"report.mute": "خموش",
"report.mute_explanation": "شما فرسته‌های آن‌ها را نخواهید دید. آن‌ها همچنان می‌توانند شما را پی‌بگیرند و فرسته‌هایتان را ببینند و نمی‌دانند که خموش شده‌اند.",
"report.next": "بعدی",
"report.placeholder": "توضیحات اضافه",
"report.reasons.dislike": "I don't like it",
"report.reasons.dislike_description": "It is not something you want to see",
"report.reasons.other": "It's something else",
"report.reasons.other_description": "The issue does not fit into other categories",
"report.reasons.spam": "It's spam",
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
"report.reasons.violation": "It violates server rules",
"report.reasons.violation_description": "You are aware that it breaks specific rules",
"report.rules.subtitle": "Select all that apply",
"report.rules.title": "Which rules are being violated?",
"report.statuses.subtitle": "Select all that apply",
"report.statuses.title": "Are there any posts that back up this report?",
"report.reasons.dislike": "من آن را دوست ندارم",
"report.reasons.dislike_description": "این چیزی نیست که بخواهید ببینید",
"report.reasons.other": "بخواطر چیز دیگری است",
"report.reasons.other_description": "این موضوع در دسته‌بندی‌های دیگر نمی‌گنجد",
"report.reasons.spam": "این هرزنامه است",
"report.reasons.spam_description": "پیوندهای مخرب، تعامل جعلی یا پاسخ‌های تکراری",
"report.reasons.violation": "قوانین کارساز را نقض می‌کند",
"report.reasons.violation_description": "شما آگاه هستید که قوانین خاصی را زیر پا می‌گذارد",
"report.rules.subtitle": "همهٔ موارد انجام شده را برگزینید",
"report.rules.title": "کدام قوانین نقض شده‌اند؟",
"report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید",
"report.statuses.title": "آیا فرسته‌ای وجود دارد که از این گزارش پشتیبانی کند؟",
"report.submit": "فرستادن",
"report.target": "در حال گزارش {target}",
"report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
"report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
"report.thanks.title": "Don't want to see this?",
"report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
"report.unfollow": "Unfollow @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
"report.thanks.take_action": "در اینجا گزینه‌هایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:",
"report.thanks.take_action_actionable": "در حالی که ما این مورد را بررسی می‌کنیم، می‌توانید علیه @{name} اقدام کنید:",
"report.thanks.title": "نمی‌خواهید این را ببینید؟",
"report.thanks.title_actionable": "ممنون بابت گزارش، ما آن را بررسی خواهیم کرد.",
"report.unfollow": "ناپی‌گیری @{name}",
"report.unfollow_explanation": "شما این حساب را پی‌گرفته‌اید، برای اینکه دیگر فرسته‌هایش را در خوراک خانه‌تان نبینید؛ آن را پی‌نگیرید.",
"search.placeholder": "جست‌وجو",
"search_popout.search_format": "راهنمای جست‌وجوی پیشرفته",
"search_popout.tips.full_text": "جست‌وجوی متنی ساده فرسته‌هایی که نوشته، پسندیده، تقویت‌کرده یا در آن‌ها نام‌برده شده‌اید را به علاوهٔ نام‌های کاربری، نام‌های نمایشی و برچسب‌ها برمی‌گرداند.",
@ -434,39 +434,39 @@
"search_popout.tips.text": "جست‌وجوی متنی ساده برای نام‌ها، نام‌های کاربری، و برچسب‌ها",
"search_popout.tips.user": "کاربر",
"search_results.accounts": "افراد",
"search_results.all": "All",
"search_results.all": "همه",
"search_results.hashtags": "برچسب‌ها",
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.nothing_found": "چیزی برای این عبارت جست‌وجو یافت نشد",
"search_results.statuses": "فرسته‌ها",
"search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون فعال نشده است.",
"search_results.statuses_fts_disabled": "جست‌وجوی محتوای فرسته‌ها در این کارساز ماستودون به کار انداخته نشده است.",
"search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
"status.admin_account": "گشودن واسط مدیریت برای @{name}",
"status.admin_account": "گشودن واسط مدیریت برای @{name}",
"status.admin_status": "گشودن این فرسته در واسط مدیریت",
"status.block": "مسدود کردن @{name}",
"status.block": "مسدود کردن @{name}",
"status.bookmark": "نشانک",
"status.cancel_reblog_private": "لغو تقویت",
"status.cancel_reblog_private": "ناتقویت",
"status.cannot_reblog": "این فرسته قابل تقویت نیست",
"status.copy": "رونویسی از نشانی فرسته",
"status.copy": "رونوشت پیوند فرسته",
"status.delete": "حذف",
"status.detailed_status": "نمایش کامل گفتگو",
"status.direct": "پیام مستقیم به @{name}",
"status.edit": "Edit",
"status.edited": "Edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.embed": "جاگذاری",
"status.direct": "پیام مستقیم به @{name}",
"status.edit": "ویرایش",
"status.edited": "ویرایش شده در {date}",
"status.edited_x_times": "{count, plural, one {{count} مرتبه} other {{count} مرتبه}} ویرایش شد",
"status.embed": "جاسازی",
"status.favourite": "پسندیدن",
"status.filtered": "پالوده",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.history.created": "توسط {name} در {date} ایجاد شد",
"status.history.edited": "توسط {name} در {date} ویرایش شد",
"status.load_more": "بار کردن بیش‌تر",
"status.media_hidden": "رسانهٔ نهفته",
"status.mention": "نام‌بردن از @{name}",
"status.mention": "نام‌بردن از @{name}",
"status.more": "بیشتر",
"status.mute": "خموشاندن @{name}",
"status.mute": "خموشاندن @{name}",
"status.mute_conversation": "خموشاندن گفت‌وگو",
"status.open": "گسترش این فرسته",
"status.pin": "سنجاقکردن در نمایه",
"status.pinned": "فرستهٔ سنجاقشده",
"status.pin": "سنجاق کردن در نمایه",
"status.pinned": "فرستهٔ سنجاق شده",
"status.read_more": "بیشتر بخوانید",
"status.reblog": "تقویت",
"status.reblog_private": "تقویت برای مخاطبان نخستین",
@ -476,7 +476,7 @@
"status.remove_bookmark": "برداشتن نشانک",
"status.reply": "پاسخ",
"status.replyAll": "پاسخ به رشته",
"status.report": "گزارش @{name}",
"status.report": "گزارش @{name}",
"status.sensitive_warning": "محتوای حساس",
"status.share": "هم‌رسانی",
"status.show_less": "نمایش کمتر",

View File

@ -229,7 +229,7 @@
"keyboard_shortcuts.blocked": "Ouvrir la liste des comptes bloqués",
"keyboard_shortcuts.boost": "Partager le message",
"keyboard_shortcuts.column": "Se placer dans une colonne",
"keyboard_shortcuts.compose": "se placer dans la zone de rédaction",
"keyboard_shortcuts.compose": "Se placer dans la zone de rédaction",
"keyboard_shortcuts.description": "Description",
"keyboard_shortcuts.direct": "Ouvrir la colonne des messages directs",
"keyboard_shortcuts.down": "Descendre dans la liste",
@ -246,7 +246,7 @@
"keyboard_shortcuts.muted": "Ouvrir la liste des comptes masqués",
"keyboard_shortcuts.my_profile": "Ouvrir votre profil",
"keyboard_shortcuts.notifications": "Ouvrir la colonne de notifications",
"keyboard_shortcuts.open_media": "ouvrir le média",
"keyboard_shortcuts.open_media": "Ouvrir le média",
"keyboard_shortcuts.pinned": "Ouvrir la liste des messages épinglés",
"keyboard_shortcuts.profile": "Ouvrir le profil de lauteur·rice",
"keyboard_shortcuts.reply": "Répondre au message",

View File

@ -18,7 +18,7 @@
"account.followers": "Luchd-leantainn",
"account.followers.empty": "Chan eil neach sam bith a leantainn air a chleachdaiche seo fhathast.",
"account.followers_counter": "{count, plural, one {{counter} neach-leantainn} two {{counter} neach-leantainn} few {{counter} luchd-leantainn} other {{counter} luchd-leantainn}}",
"account.following": "Following",
"account.following": "A leantainn",
"account.following_counter": "{count, plural, one {A leantainn air {counter}} two {A leantainn air {counter}} few {A leantainn air {counter}} other {A leantainn air {counter}}}",
"account.follows.empty": "Chan eil an cleachdaiche seo a leantainn air neach sam bith fhathast.",
"account.follows_you": "Gad leantainn",
@ -41,12 +41,12 @@
"account.statuses_counter": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}",
"account.unblock": "Dì-bhac @{name}",
"account.unblock_domain": "Dì-bhac an àrainn {domain}",
"account.unblock_short": "Unblock",
"account.unblock_short": "Dì-bhac",
"account.unendorse": "Na brosnaich air a phròifil",
"account.unfollow": "Na lean tuilleadh",
"account.unmute": "Dì-mhùch @{name}",
"account.unmute_notifications": "Dì-mhùch na brathan o @{name}",
"account.unmute_short": "Unmute",
"account.unmute_short": "Dì-mhùch",
"account_note.placeholder": "Briog airson nòta a chur ris",
"admin.dashboard.daily_retention": "Reat glèidheadh nan cleachdaichean às dèidh an clàradh a-rèir latha",
"admin.dashboard.monthly_retention": "Reat glèidheadh nan cleachdaichean às dèidh an clàradh a-rèir mìos",
@ -294,7 +294,7 @@
"navigation_bar.discover": "Fidir",
"navigation_bar.domain_blocks": "Àrainnean bacte",
"navigation_bar.edit_profile": "Deasaich a phròifil",
"navigation_bar.explore": "Explore",
"navigation_bar.explore": "Rùraich",
"navigation_bar.favourites": "Na h-annsachdan",
"navigation_bar.filters": "Faclan mùchte",
"navigation_bar.follow_requests": "Iarrtasan leantainn",

View File

@ -192,7 +192,7 @@
"errors.unexpected_crash.copy_stacktrace": "スタックトレースをクリップボードにコピー",
"errors.unexpected_crash.report_issue": "問題を報告",
"explore.search_results": "検索結果",
"explore.suggested_follows": "あなたに",
"explore.suggested_follows": "おすすめ",
"explore.title": "エクスプローラー",
"explore.trending_links": "ニュース",
"explore.trending_statuses": "投稿",

View File

@ -342,7 +342,7 @@
"notifications.filter.all": "Hemû",
"notifications.filter.boosts": "Bilindkirî",
"notifications.filter.favourites": "Bijarte",
"notifications.filter.follows": "Şopîner",
"notifications.filter.follows": "Dişopîne",
"notifications.filter.mentions": "Qalkirin",
"notifications.filter.polls": "Encamên rapirsiyê",
"notifications.filter.statuses": "Ji kesên tu dişopînî re rojanekirin",
@ -501,7 +501,7 @@
"time_remaining.seconds": "{number, plural, one {# çirke} other {# çirke}} maye",
"timeline_hint.remote_resource_not_displayed": "{resource} Ji rajekerên din nayê dîtin.",
"timeline_hint.resources.followers": "Şopîner",
"timeline_hint.resources.follows": "Şopîner",
"timeline_hint.resources.follows": "Dişopîne",
"timeline_hint.resources.statuses": "Şandiyên kevn",
"trends.counter_by_accounts": "{count, plural, one {{counter} kes} other {{counter} kes}} diaxivin",
"trends.trending_now": "Rojev",

View File

@ -9,44 +9,44 @@
"account.browse_more_on_origin_server": "Meer op het originele profiel bekijken",
"account.cancel_follow_request": "Volgverzoek annuleren",
"account.direct": "@{name} een direct bericht sturen",
"account.disable_notifications": "Geef geen melding meer wanneer @{name} toot",
"account.disable_notifications": "Geef geen melding meer wanneer @{name} een bericht plaatst",
"account.domain_blocked": "Domein geblokkeerd",
"account.edit_profile": "Profiel bewerken",
"account.enable_notifications": "Geef een melding wanneer @{name} toot",
"account.enable_notifications": "Geef een melding wanneer @{name} een bericht plaatst",
"account.endorse": "Op profiel weergeven",
"account.follow": "Volgen",
"account.followers": "Volgers",
"account.followers.empty": "Niemand volgt nog deze gebruiker.",
"account.followers_counter": "{count, plural, one {{counter} volger} other {{counter} volgers}}",
"account.following": "Following",
"account.following": "Volgend",
"account.following_counter": "{count, plural, one {{counter} volgend} other {{counter} volgend}}",
"account.follows.empty": "Deze gebruiker volgt nog niemand.",
"account.follows_you": "Volgt jou",
"account.hide_reblogs": "Boosts van @{name} verbergen",
"account.joined": "Geregistreerd in {date}",
"account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
"account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie hen kan volgen.",
"account.locked_info": "De privacystatus van dit account is op besloten gezet. De eigenaar bepaalt handmatig wie diegene kan volgen.",
"account.media": "Media",
"account.mention": "@{name} vermelden",
"account.moved_to": "{name} is verhuisd naar:",
"account.mute": "@{name} negeren",
"account.mute_notifications": "Meldingen van @{name} negeren",
"account.muted": "Genegeerd",
"account.posts": "Toots",
"account.posts_with_replies": "Toots en reacties",
"account.posts": "Berichten",
"account.posts_with_replies": "Berichten en reacties",
"account.report": "@{name} rapporteren",
"account.requested": "Wacht op goedkeuring. Klik om het volgverzoek te annuleren",
"account.share": "Profiel van @{name} delen",
"account.show_reblogs": "Boosts van @{name} tonen",
"account.statuses_counter": "{count, plural, one {{counter} toot} other {{counter} toots}}",
"account.statuses_counter": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
"account.unblock": "@{name} deblokkeren",
"account.unblock_domain": "{domain} niet langer verbergen",
"account.unblock_short": "Unblock",
"account.unblock_short": "Deblokkeren",
"account.unendorse": "Niet op profiel weergeven",
"account.unfollow": "Ontvolgen",
"account.unmute": "@{name} niet langer negeren",
"account.unmute_notifications": "Meldingen van @{name} niet langer negeren",
"account.unmute_short": "Unmute",
"account.unmute_short": "Niet langer negeren",
"account_note.placeholder": "Klik om een opmerking toe te voegen",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
@ -79,7 +79,7 @@
"column.lists": "Lijsten",
"column.mutes": "Genegeerde gebruikers",
"column.notifications": "Meldingen",
"column.pins": "Vastgezette toots",
"column.pins": "Vastgezette berichten",
"column.public": "Globale tijdlijn",
"column_back_button.label": "Terug",
"column_header.hide_settings": "Instellingen verbergen",
@ -92,10 +92,10 @@
"community.column_settings.local_only": "Alleen lokaal",
"community.column_settings.media_only": "Alleen media",
"community.column_settings.remote_only": "Alleen andere servers",
"compose_form.direct_message_warning": "Deze toot wordt alleen naar vermelde gebruikers verstuurd.",
"compose_form.direct_message_warning": "Dit bericht wordt alleen naar vermelde gebruikers verstuurd.",
"compose_form.direct_message_warning_learn_more": "Meer leren",
"compose_form.hashtag_warning": "Deze toot valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare toots kunnen via hashtags gevonden worden.",
"compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de toots zien die je alleen aan jouw volgers hebt gericht.",
"compose_form.hashtag_warning": "Dit bericht valt niet onder een hashtag te bekijken, omdat deze niet op openbare tijdlijnen wordt getoond. Alleen openbare berichten kunnen via hashtags gevonden worden.",
"compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de berichten zien die je alleen aan jouw volgers hebt gericht.",
"compose_form.lock_disclaimer.lock": "besloten",
"compose_form.placeholder": "Wat wil je kwijt?",
"compose_form.poll.add_option": "Keuze toevoegen",
@ -106,7 +106,7 @@
"compose_form.poll.switch_to_single": "Poll wijzigen om een enkele keuze toe te staan",
"compose_form.publish": "Toot",
"compose_form.publish_loud": "{publish}!",
"compose_form.save_changes": "Save changes",
"compose_form.save_changes": "Wijzigingen opslaan",
"compose_form.sensitive.hide": "{count, plural, one {Media als gevoelig markeren} other {Media als gevoelig markeren}}",
"compose_form.sensitive.marked": "{count, plural, one {Media is als gevoelig gemarkeerd} other {Media is als gevoelig gemarkeerd}}",
"compose_form.sensitive.unmarked": "{count, plural, one {Media is niet als gevoelig gemarkeerd} other {Media is niet als gevoelig gemarkeerd}}",
@ -118,22 +118,22 @@
"confirmations.block.confirm": "Blokkeren",
"confirmations.block.message": "Weet je het zeker dat je {name} wilt blokkeren?",
"confirmations.delete.confirm": "Verwijderen",
"confirmations.delete.message": "Weet je het zeker dat je deze toot wilt verwijderen?",
"confirmations.delete.message": "Weet je het zeker dat je dit bericht wilt verwijderen?",
"confirmations.delete_list.confirm": "Verwijderen",
"confirmations.delete_list.message": "Weet je zeker dat je deze lijst definitief wilt verwijderen?",
"confirmations.discard_edit_media.confirm": "Weggooien",
"confirmations.discard_edit_media.message": "Je hebt niet-opgeslagen wijzigingen in de mediabeschrijving of voorvertonning, wil je deze toch weggooien?",
"confirmations.domain_block.confirm": "Verberg alles van deze server",
"confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en beter. Je zult geen toots van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd.",
"confirmations.domain_block.message": "Weet je het echt heel erg zeker dat je alles van {domain} wilt negeren? In de meeste gevallen is het blokkeren of negeren van een paar specifieke personen voldoende en beter. Je zult geen berichten van deze server op openbare tijdlijnen zien of in jouw meldingen. Jouw volgers van deze server worden verwijderd.",
"confirmations.logout.confirm": "Uitloggen",
"confirmations.logout.message": "Weet je zeker dat je wilt uitloggen?",
"confirmations.mute.confirm": "Negeren",
"confirmations.mute.explanation": "Dit verbergt toots van hen en toots waar hen in wordt vermeld, maar hen kan nog steeds jouw toots bekijken en jou volgen.",
"confirmations.mute.explanation": "Dit verbergt diens berichten en berichten waar diegene in wordt vermeld, maar diegene kan nog steeds jouw berichten bekijken en jou volgen.",
"confirmations.mute.message": "Weet je het zeker dat je {name} wilt negeren?",
"confirmations.redraft.confirm": "Verwijderen en herschrijven",
"confirmations.redraft.message": "Weet je zeker dat je deze toot wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op de originele toot zitten niet meer aan de nieuwe toot vast.",
"confirmations.redraft.message": "Weet je zeker dat je dit bericht wilt verwijderen en herschrijven? Je verliest wel de boosts en favorieten, en de reacties op het originele bericht raak je kwijt.",
"confirmations.reply.confirm": "Reageren",
"confirmations.reply.message": "Door nu te reageren overschrijf je de toot die je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
"confirmations.reply.message": "Door nu te reageren overschrijf je het bericht dat je op dit moment aan het schrijven bent. Weet je zeker dat je verder wil gaan?",
"confirmations.unfollow.confirm": "Ontvolgen",
"confirmations.unfollow.message": "Weet je het zeker dat je {name} wilt ontvolgen?",
"conversation.delete": "Gesprek verwijderen",
@ -144,7 +144,7 @@
"directory.local": "Alleen {domain}",
"directory.new_arrivals": "Nieuwe accounts",
"directory.recently_active": "Onlangs actief",
"embed.instructions": "Embed deze toot op jouw website, door de onderstaande code te kopiëren.",
"embed.instructions": "Embed dit bericht op jouw website door de onderstaande code te kopiëren.",
"embed.preview": "Zo komt het eruit te zien:",
"emoji_button.activity": "Activiteiten",
"emoji_button.custom": "Lokale emojis",
@ -161,41 +161,41 @@
"emoji_button.symbols": "Symbolen",
"emoji_button.travel": "Reizen en locaties",
"empty_column.account_suspended": "Account opgeschort",
"empty_column.account_timeline": "Hier zijn geen toots!",
"empty_column.account_timeline": "Hier zijn geen berichten!",
"empty_column.account_unavailable": "Profiel is niet beschikbaar",
"empty_column.blocks": "Jij hebt nog geen enkele gebruiker geblokkeerd.",
"empty_column.bookmarked_statuses": "Jij hebt nog geen toots aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
"empty_column.community": "De lokale tijdlijn is nog leeg. Toot iets in het openbaar om de spits af te bijten!",
"empty_column.bookmarked_statuses": "Jij hebt nog geen berichten aan je bladwijzers toegevoegd. Wanneer je er een aan jouw bladwijzers toevoegt, valt deze hier te zien.",
"empty_column.community": "De lokale tijdlijn is nog leeg. Plaats een openbaar bericht om de spits af te bijten!",
"empty_column.direct": "Je hebt nog geen directe berichten. Wanneer je er een verzend of ontvangt, zijn deze hier te zien.",
"empty_column.domain_blocks": "Er zijn nog geen geblokkeerde domeinen.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
"empty_column.favourited_statuses": "Jij hebt nog geen favoriete toots. Wanneer je er een aan jouw favorieten toevoegt, valt deze hier te zien.",
"empty_column.favourites": "Niemand heeft deze toot nog aan hun favorieten toegevoegd. Wanneer iemand dit doet, valt dat hier te zien.",
"empty_column.explore_statuses": "Momenteel zijn er geen trends. Kom later terug!",
"empty_column.favourited_statuses": "Jij hebt nog geen favoriete berichten. Wanneer je er een aan jouw favorieten toevoegt, valt deze hier te zien.",
"empty_column.favourites": "Niemand heeft dit bericht nog aan diens favorieten toegevoegd. Wanneer iemand dit doet, valt dat hier te zien.",
"empty_column.follow_recommendations": "Het lijkt er op dat er geen aanbevelingen voor jou aangemaakt kunnen worden. Je kunt proberen te zoeken naar mensen die je wellicht kent, zoeken op hashtags, de lokale en globale tijdlijnen bekijken of de gebruikersgids doorbladeren.",
"empty_column.follow_requests": "Jij hebt nog enkel volgverzoek ontvangen. Wanneer je er eentje ontvangt, valt dat hier te zien.",
"empty_column.hashtag": "Er is nog niks te vinden onder deze hashtag.",
"empty_column.home": "Deze tijdlijn is leeg! Volg meer mensen om het te vullen. {suggestions}",
"empty_column.home.suggestions": "Enkele aanbevelingen bekijken",
"empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe toots publiceren, zijn deze hier te zien.",
"empty_column.list": "Er is nog niks te zien in deze lijst. Wanneer lijstleden nieuwe berichten plaatsen, zijn deze hier te zien.",
"empty_column.lists": "Jij hebt nog geen enkele lijst. Wanneer je er eentje hebt aangemaakt, valt deze hier te zien.",
"empty_column.mutes": "Jij hebt nog geen gebruikers genegeerd.",
"empty_column.notifications": "Je hebt nog geen meldingen. Begin met iemand een gesprek.",
"empty_column.public": "Er is hier helemaal niks! Toot iets in het openbaar of volg mensen van andere servers om het te vullen",
"empty_column.public": "Er is hier helemaal niks! Plaatst een openbaar bericht of volg mensen van andere servers om het te vullen",
"error.unexpected_crash.explanation": "Als gevolg van een bug in onze broncode of als gevolg van een compatibiliteitsprobleem met jouw webbrowser, kan deze pagina niet goed worden weergegeven.",
"error.unexpected_crash.explanation_addons": "Deze pagina kon niet correct geladen worden. Deze fout wordt waarschijnlijk door een browser-add-on of een automatische vertalingshulpmiddel veroorzaakt.",
"error.unexpected_crash.next_steps": "Probeer deze pagina te vernieuwen. Wanneer dit niet helpt is het nog steeds mogelijk om Mastodon in een andere webbrowser of mobiele app te gebruiken.",
"error.unexpected_crash.next_steps_addons": "Probeer deze uit te schakelen en de pagina te verversen. Wanneer dat niet helpt, kun je Mastodon nog altijd met een andere webbrowser of mobiele app gebruiken.",
"errors.unexpected_crash.copy_stacktrace": "Stacktrace naar klembord kopiëren",
"errors.unexpected_crash.report_issue": "Technisch probleem melden",
"explore.search_results": "Search results",
"explore.suggested_follows": "For you",
"explore.title": "Explore",
"explore.trending_links": "News",
"explore.trending_statuses": "Posts",
"explore.search_results": "Zoekresultaten",
"explore.suggested_follows": "Voor jou",
"explore.title": "Verkennen",
"explore.trending_links": "Nieuws",
"explore.trending_statuses": "Berichten",
"explore.trending_tags": "Hashtags",
"follow_recommendations.done": "Klaar",
"follow_recommendations.heading": "Volg mensen waarvan je graag toots wil zien! Hier zijn enkele aanbevelingen.",
"follow_recommendations.lead": "Toots van mensen die je volgt zullen in chronologische volgorde onder start verschijnen. Wees niet bang om hierin fouten te maken, want je kunt mensen op elk moment net zo eenvoudig ontvolgen!",
"follow_recommendations.heading": "Volg mensen waarvan je graag berichten wil zien! Hier zijn enkele aanbevelingen.",
"follow_recommendations.lead": "Berichten van mensen die je volgt zullen in chronologische volgorde onder start verschijnen. Wees niet bang om hierin fouten te maken, want je kunt mensen op elk moment net zo eenvoudig ontvolgen!",
"follow_request.authorize": "Goedkeuren",
"follow_request.reject": "Afkeuren",
"follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.",
@ -227,13 +227,13 @@
"intervals.full.minutes": "{number, plural, one {# minuut} other {# minuten}}",
"keyboard_shortcuts.back": "Ga terug",
"keyboard_shortcuts.blocked": "Geblokkeerde gebruikers tonen",
"keyboard_shortcuts.boost": "Toot boosten",
"keyboard_shortcuts.boost": "Bericht boosten",
"keyboard_shortcuts.column": "Op één van de kolommen focussen",
"keyboard_shortcuts.compose": "Tekstveld voor toots focussen",
"keyboard_shortcuts.compose": "Tekstveld om een bericht te schrijven focussen",
"keyboard_shortcuts.description": "Omschrijving",
"keyboard_shortcuts.direct": "Jouw directe berichten tonen",
"keyboard_shortcuts.down": "Naar beneden in de lijst bewegen",
"keyboard_shortcuts.enter": "Toot volledig tonen",
"keyboard_shortcuts.enter": "Volledig bericht tonen",
"keyboard_shortcuts.favourite": "Aan jouw favorieten toevoegen",
"keyboard_shortcuts.favourites": "Favorieten tonen",
"keyboard_shortcuts.federated": "Globale tijdlijn tonen",
@ -247,7 +247,7 @@
"keyboard_shortcuts.my_profile": "Jouw profiel tonen",
"keyboard_shortcuts.notifications": "Meldingen tonen",
"keyboard_shortcuts.open_media": "Media openen",
"keyboard_shortcuts.pinned": "Jouw vastgezette toots tonen",
"keyboard_shortcuts.pinned": "Jouw vastgemaakte berichten tonen",
"keyboard_shortcuts.profile": "Gebruikersprofiel auteur openen",
"keyboard_shortcuts.reply": "Reageren",
"keyboard_shortcuts.requests": "Jouw volgverzoeken tonen",
@ -256,7 +256,7 @@
"keyboard_shortcuts.start": "\"Aan de slag\" tonen",
"keyboard_shortcuts.toggle_hidden": "Inhoudswaarschuwing tonen/verbergen",
"keyboard_shortcuts.toggle_sensitivity": "Media tonen/verbergen",
"keyboard_shortcuts.toot": "Nieuwe toot schrijven",
"keyboard_shortcuts.toot": "Nieuw bericht schrijven",
"keyboard_shortcuts.unfocus": "Tekst- en zoekveld ontfocussen",
"keyboard_shortcuts.up": "Naar boven in de lijst bewegen",
"lightbox.close": "Sluiten",
@ -289,12 +289,12 @@
"navigation_bar.blocks": "Geblokkeerde gebruikers",
"navigation_bar.bookmarks": "Bladwijzers",
"navigation_bar.community_timeline": "Lokale tijdlijn",
"navigation_bar.compose": "Nieuw toot schrijven",
"navigation_bar.compose": "Nieuw bericht schrijven",
"navigation_bar.direct": "Directe berichten",
"navigation_bar.discover": "Ontdekken",
"navigation_bar.domain_blocks": "Geblokkeerde domeinen",
"navigation_bar.edit_profile": "Profiel bewerken",
"navigation_bar.explore": "Explore",
"navigation_bar.explore": "Verkennen",
"navigation_bar.favourites": "Favorieten",
"navigation_bar.filters": "Filters",
"navigation_bar.follow_requests": "Volgverzoeken",
@ -305,23 +305,23 @@
"navigation_bar.logout": "Uitloggen",
"navigation_bar.mutes": "Genegeerde gebruikers",
"navigation_bar.personal": "Persoonlijk",
"navigation_bar.pins": "Vastgezette toots",
"navigation_bar.pins": "Vastgemaakte berichten",
"navigation_bar.preferences": "Instellingen",
"navigation_bar.public_timeline": "Globale tijdlijn",
"navigation_bar.security": "Beveiliging",
"notification.admin.sign_up": "{name} signed up",
"notification.favourite": "{name} voegde jouw toot als favoriet toe",
"notification.admin.sign_up": "{name} heeft zich aangemeld",
"notification.favourite": "{name} voegde jouw bericht als favoriet toe",
"notification.follow": "{name} volgt jou nu",
"notification.follow_request": "{name} wil jou graag volgen",
"notification.mention": "{name} vermeldde jou",
"notification.own_poll": "Jouw poll is beëindigd",
"notification.poll": "Een poll waaraan jij hebt meegedaan is beëindigd",
"notification.reblog": "{name} boostte jouw toot",
"notification.status": "{name} heeft zojuist een toot geplaatst",
"notification.update": "{name} edited a post",
"notification.reblog": "{name} boostte jouw bericht",
"notification.status": "{name} heeft zojuist een bericht geplaatst",
"notification.update": "{name} heeft een bericht bewerkt",
"notifications.clear": "Meldingen verwijderen",
"notifications.clear_confirmation": "Weet je het zeker dat je al jouw meldingen wilt verwijderen?",
"notifications.column_settings.admin.sign_up": "New sign-ups:",
"notifications.column_settings.admin.sign_up": "Nieuwe aanmeldingen:",
"notifications.column_settings.alert": "Desktopmeldingen",
"notifications.column_settings.favourite": "Favorieten:",
"notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
@ -335,10 +335,10 @@
"notifications.column_settings.reblog": "Boosts:",
"notifications.column_settings.show": "In kolom tonen",
"notifications.column_settings.sound": "Geluid afspelen",
"notifications.column_settings.status": "Nieuwe toots:",
"notifications.column_settings.status": "Nieuwe berichten:",
"notifications.column_settings.unread_notifications.category": "Ongelezen meldingen",
"notifications.column_settings.unread_notifications.highlight": "Ongelezen meldingen markeren",
"notifications.column_settings.update": "Edits:",
"notifications.column_settings.update": "Bewerkingen:",
"notifications.filter.all": "Alles",
"notifications.filter.boosts": "Boosts",
"notifications.filter.favourites": "Favorieten",
@ -365,7 +365,7 @@
"poll.votes": "{votes, plural, one {# stem} other {# stemmen}}",
"poll_button.add_poll": "Poll toevoegen",
"poll_button.remove_poll": "Poll verwijderen",
"privacy.change": "Zichtbaarheid van toot aanpassen",
"privacy.change": "Zichtbaarheid van bericht aanpassen",
"privacy.direct.long": "Alleen aan vermelde gebruikers tonen",
"privacy.direct.short": "Direct",
"privacy.private.long": "Alleen aan volgers tonen",
@ -378,100 +378,100 @@
"regeneration_indicator.label": "Aan het laden…",
"regeneration_indicator.sublabel": "Jouw tijdlijn wordt aangemaakt!",
"relative_time.days": "{number}d",
"relative_time.full.days": "{number, plural, one {# day} other {# days}} ago",
"relative_time.full.hours": "{number, plural, one {# hour} other {# hours}} ago",
"relative_time.full.just_now": "just now",
"relative_time.full.minutes": "{number, plural, one {# minute} other {# minutes}} ago",
"relative_time.full.seconds": "{number, plural, one {# second} other {# seconds}} ago",
"relative_time.full.days": "{number, plural, one {# dag} other {# dagen}} geleden",
"relative_time.full.hours": "{number, plural, one {# uur} other {# uur}} geleden",
"relative_time.full.just_now": "zojuist",
"relative_time.full.minutes": "{number, plural, one {# minuut} other {# minuten}} geleden",
"relative_time.full.seconds": "{number, plural, one {# seconde} other {# seconden}} geleden",
"relative_time.hours": "{number}u",
"relative_time.just_now": "nu",
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"relative_time.today": "vandaag",
"reply_indicator.cancel": "Annuleren",
"report.block": "Block",
"report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
"report.categories.other": "Other",
"report.block": "Blokkeren",
"report.block_explanation": "Je kunt diens berichten niet zien. Je kunt door diegene niet gevolgd worden en jouw berichten zijn onzichtbaar. Diegene kan zien dat die door jou is geblokkeerd.",
"report.categories.other": "Overig",
"report.categories.spam": "Spam",
"report.categories.violation": "Content violates one or more server rules",
"report.category.subtitle": "Choose the best match",
"report.category.title": "Tell us what's going on with this {type}",
"report.category.title_account": "profile",
"report.category.title_status": "post",
"report.close": "Done",
"report.comment.title": "Is there anything else you think we should know?",
"report.categories.violation": "De inhoud overtreedt een of meerdere serverregels",
"report.category.subtitle": "Kies wat het meeste overeenkomt",
"report.category.title": "Vertel ons wat er met dit {type} aan de hand is",
"report.category.title_account": "profiel",
"report.category.title_status": "bericht",
"report.close": "Klaar",
"report.comment.title": "Zijn er nog andere dingen waarvan je denkt dat wij dat moeten weten?",
"report.forward": "Naar {target} doorsturen",
"report.forward_hint": "Het account bevindt zich op een andere server. Wil je daar eveneens een geanonimiseerde kopie van deze rapportage naar toe sturen?",
"report.mute": "Mute",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
"report.next": "Next",
"report.mute": "Negeren",
"report.mute_explanation": "Je kunt diens berichten niet zien. Je kunt nog wel gevolgd worden en jouw berichten zijn nog zichtbaar, maar diegene kan niet zien dat die wordt genegeerd.",
"report.next": "Volgende",
"report.placeholder": "Extra opmerkingen",
"report.reasons.dislike": "I don't like it",
"report.reasons.dislike_description": "It is not something you want to see",
"report.reasons.other": "It's something else",
"report.reasons.other_description": "The issue does not fit into other categories",
"report.reasons.spam": "It's spam",
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
"report.reasons.violation": "It violates server rules",
"report.reasons.violation_description": "You are aware that it breaks specific rules",
"report.rules.subtitle": "Select all that apply",
"report.rules.title": "Which rules are being violated?",
"report.statuses.subtitle": "Select all that apply",
"report.statuses.title": "Are there any posts that back up this report?",
"report.reasons.dislike": "Ik vind het niet leuk",
"report.reasons.dislike_description": "Het is iets wat je niet wilt zien",
"report.reasons.other": "Het is iets anders",
"report.reasons.other_description": "Het probleem past niet in een andere categorie",
"report.reasons.spam": "Het is spam",
"report.reasons.spam_description": "Schadelijke links, reclame, misleiding of herhalende antwoorden",
"report.reasons.violation": "Het schendt de serverregels",
"report.reasons.violation_description": "Je weet dat het specifieke regels schendt",
"report.rules.subtitle": "Selecteer wat van toepassing is",
"report.rules.title": "Welke regels worden geschonden?",
"report.statuses.subtitle": "Selecteer wat van toepassing is",
"report.statuses.title": "Zijn er berichten die deze rapportage ondersteunen?",
"report.submit": "Verzenden",
"report.target": "{target} rapporteren",
"report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
"report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
"report.thanks.title": "Don't want to see this?",
"report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
"report.unfollow": "Unfollow @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
"report.thanks.take_action": "Hier zijn jouw opties waarmee je kunt bepalen wat je in Mastodon wilt zien:",
"report.thanks.take_action_actionable": "Terwijl wij jouw rapportage beroordelen, kun je deze acties ondernemen tegen @{name}:",
"report.thanks.title": "Wil je dit niet zien?",
"report.thanks.title_actionable": "Dank je voor het rapporteren. Wij gaan er naar kijken.",
"report.unfollow": "@{name} ontvolgen",
"report.unfollow_explanation": "Je volgt dit account. Om diens berichten niet meer op jouw starttijdlijn te zien, kun je diegene ontvolgen.",
"search.placeholder": "Zoeken",
"search_popout.search_format": "Geavanceerd zoeken",
"search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken in jouw toots, gebooste toots, favorieten en in toots waarin je bent vermeldt, en tevens naar gebruikersnamen, weergavenamen en hashtags.",
"search_popout.tips.full_text": "Gebruik gewone tekst om te zoeken in jouw berichten, gebooste berichten, favorieten en in berichten waarin je bent vermeldt, en tevens naar gebruikersnamen, weergavenamen en hashtags.",
"search_popout.tips.hashtag": "hashtag",
"search_popout.tips.status": "toot",
"search_popout.tips.status": "bericht",
"search_popout.tips.text": "Gebruik gewone tekst om te zoeken op weergavenamen, gebruikersnamen en hashtags",
"search_popout.tips.user": "gebruiker",
"search_results.accounts": "Gebruikers",
"search_results.all": "All",
"search_results.all": "Alles",
"search_results.hashtags": "Hashtags",
"search_results.nothing_found": "Could not find anything for these search terms",
"search_results.statuses": "Toots",
"search_results.statuses_fts_disabled": "Het zoeken in toots is op deze Mastodon-server niet ingeschakeld.",
"search_results.nothing_found": "Deze zoektermen leveren geen resultaat op",
"search_results.statuses": "Berichten",
"search_results.statuses_fts_disabled": "Het zoeken in berichten is op deze Mastodon-server niet ingeschakeld.",
"search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
"status.admin_account": "Moderatie-omgeving van @{name} openen",
"status.admin_status": "Deze toot in de moderatie-omgeving openen",
"status.admin_status": "Dit bericht in de moderatie-omgeving openen",
"status.block": "@{name} blokkeren",
"status.bookmark": "Bladwijzer toevoegen",
"status.cancel_reblog_private": "Niet langer boosten",
"status.cannot_reblog": "Deze toot kan niet geboost worden",
"status.copy": "Link naar toot kopiëren",
"status.cannot_reblog": "Dit bericht kan niet geboost worden",
"status.copy": "Link naar bericht kopiëren",
"status.delete": "Verwijderen",
"status.detailed_status": "Uitgebreide gespreksweergave",
"status.direct": "@{name} een direct bericht sturen",
"status.edit": "Edit",
"status.edited": "Edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.edit": "Bewerken",
"status.edited": "Bewerkt op {date}",
"status.edited_x_times": "{count, plural, one {{count} keer} other {{count} keer}} bewerkt",
"status.embed": "Insluiten",
"status.favourite": "Favoriet",
"status.filtered": "Gefilterd",
"status.history.created": "{name} created {date}",
"status.history.edited": "{name} edited {date}",
"status.history.created": "{name} plaatste dit {date}",
"status.history.edited": "{name} bewerkte dit {date}",
"status.load_more": "Meer laden",
"status.media_hidden": "Media verborgen",
"status.mention": "@{name} vermelden",
"status.more": "Meer",
"status.mute": "@{name} negeren",
"status.mute_conversation": "Negeer gesprek",
"status.open": "Volledige toot tonen",
"status.open": "Volledig bericht tonen",
"status.pin": "Aan profielpagina vastmaken",
"status.pinned": "Vastgemaakte toot",
"status.pinned": "Vastgemaakt bericht",
"status.read_more": "Meer lezen",
"status.reblog": "Boosten",
"status.reblog_private": "Boost naar oorspronkelijke ontvangers",
"status.reblogged_by": "{name} boostte",
"status.reblogs.empty": "Niemand heeft deze toot nog geboost. Wanneer iemand dit doet, valt dat hier te zien.",
"status.reblogs.empty": "Niemand heeft dit bericht nog geboost. Wanneer iemand dit doet, valt dat hier te zien.",
"status.redraft": "Verwijderen en herschrijven",
"status.remove_bookmark": "Bladwijzer verwijderen",
"status.reply": "Reageren",
@ -502,7 +502,7 @@
"timeline_hint.remote_resource_not_displayed": "{resource} van andere servers worden niet getoond.",
"timeline_hint.resources.followers": "Volgers",
"timeline_hint.resources.follows": "Volgend",
"timeline_hint.resources.statuses": "Oudere toots",
"timeline_hint.resources.statuses": "Oudere berichten",
"trends.counter_by_accounts": "{count, plural, one {{counter} persoon} other {{counter} personen}} zijn aan het praten",
"trends.trending_now": "Huidige trends",
"ui.beforeunload": "Je concept gaat verloren wanneer je Mastodon verlaat.",

View File

@ -18,7 +18,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Nada aqui.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.following": "Following",
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {segue {counter}} other {segue {counter}}}",
"account.follows.empty": "Nada aqui.",
"account.follows_you": "te segue",
@ -41,14 +41,14 @@
"account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
"account.unblock": "Desbloquear @{name}",
"account.unblock_domain": "Desbloquear domínio {domain}",
"account.unblock_short": "Unblock",
"account.unblock_short": "Desbloquear",
"account.unendorse": "Remover",
"account.unfollow": "Deixar de seguir",
"account.unmute": "Dessilenciar @{name}",
"account.unmute_notifications": "Mostrar notificações de @{name}",
"account.unmute_short": "Unmute",
"account.unmute_short": "Reativar",
"account_note.placeholder": "Nota pessoal sobre este perfil aqui",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
"admin.dashboard.daily_retention": "Taxa de retenção de usuários por dia, após a inscrição",
"admin.dashboard.monthly_retention": "Taxa de retenção de usuários por mês, após a inscrição",
"admin.dashboard.retention.average": "Média",
"admin.dashboard.retention.cohort": "Mês de inscrição",
@ -168,7 +168,7 @@
"empty_column.community": "A linha local está vazia. Publique algo para começar!",
"empty_column.direct": "Nada aqui. Quando você enviar ou receber toots diretos, eles aparecerão aqui.",
"empty_column.domain_blocks": "Nada aqui.",
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
"empty_column.explore_statuses": "Nada está em alta no momento. Volte mais tarde!",
"empty_column.favourited_statuses": "Nada aqui. Quando você favoritar um toot, ele aparecerá aqui.",
"empty_column.favourites": "Nada aqui. Quando alguém favoritar, o autor aparecerá aqui.",
"empty_column.follow_recommendations": "Parece que não há sugestões para você. Tente usar a pesquisa para encontrar pessoas que você possa conhecer ou explorar hashtags.",
@ -294,7 +294,7 @@
"navigation_bar.discover": "Descobrir",
"navigation_bar.domain_blocks": "Domínios bloqueados",
"navigation_bar.edit_profile": "Editar perfil",
"navigation_bar.explore": "Explore",
"navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.filters": "Palavras filtradas",
"navigation_bar.follow_requests": "Seguidores pendentes",
@ -390,42 +390,42 @@
"relative_time.today": "hoje",
"reply_indicator.cancel": "Cancelar",
"report.block": "Bloquear",
"report.block_explanation": "You will not see their posts. They will not be able to see your posts or follow you. They will be able to tell that they are blocked.",
"report.block_explanation": "Você não verá suas postagens. Eles não poderão ver suas postagens ou segui-lo. Eles serão capazes de perceber que estão bloqueados.",
"report.categories.other": "Outro",
"report.categories.spam": "Spam",
"report.categories.violation": "O conteúdo viola uma ou mais regras do servidor",
"report.category.subtitle": "Choose the best match",
"report.category.title": "Tell us what's going on with this {type}",
"report.category.subtitle": "Escolha a alternativa de melhor correspondência",
"report.category.title": "Conte-nos o que está acontecendo com esse {type}",
"report.category.title_account": "perfil",
"report.category.title_status": "publicação",
"report.close": "Concluído",
"report.comment.title": "Is there anything else you think we should know?",
"report.comment.title": "Há algo mais que você acredita que devemos saber?",
"report.forward": "Encaminhar para {target}",
"report.forward_hint": "A conta está em outra instância. Enviar uma cópia anônima da denúncia para lá?",
"report.mute": "Silenciar",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
"report.mute_explanation": "Você não verá suas postagens. Eles ainda podem seguir você e ver suas postagens e não saberão que estão silenciados.",
"report.next": "Próximo",
"report.placeholder": "Comentários adicionais aqui",
"report.reasons.dislike": "Eu não gosto disso",
"report.reasons.dislike_description": "It is not something you want to see",
"report.reasons.other": "It's something else",
"report.reasons.dislike_description": "Não é algo que você quer ver",
"report.reasons.other": "É outra coisa",
"report.reasons.other_description": "O problema não se encaixa em outras categorias",
"report.reasons.spam": "É spam",
"report.reasons.spam_description": "Malicious links, fake engagement, or repetitive replies",
"report.reasons.violation": "It violates server rules",
"report.reasons.spam_description": "Links maliciosos, envolvimento falso ou respostas repetitivas",
"report.reasons.violation": "Viola as regras do servidor",
"report.reasons.violation_description": "Você está ciente de que isso quebra regras específicas",
"report.rules.subtitle": "Selecione tudo que se aplica",
"report.rules.title": "Which rules are being violated?",
"report.rules.title": "Que regras estão sendo violadas?",
"report.statuses.subtitle": "Selecione tudo que se aplica",
"report.statuses.title": "Are there any posts that back up this report?",
"report.statuses.title": "Existem postagens que respaldam esse relatório?",
"report.submit": "Enviar",
"report.target": "Denunciando {target}",
"report.thanks.take_action": "Here are your options for controlling what you see on Mastodon:",
"report.thanks.take_action": "Aqui estão suas opções para controlar o que você vê no Mastodon:",
"report.thanks.take_action_actionable": "Enquanto revisamos isso, você pode tomar medidas contra @{name}:",
"report.thanks.title": "Não quer ver isto?",
"report.thanks.title_actionable": "Obrigado por reportar. Vamos analisar.",
"report.unfollow": "Unfollow @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
"report.unfollow": "Deixar de seguir @{name}",
"report.unfollow_explanation": "Você está seguindo esta conta. Para não mais ver os posts dele em sua página inicial, deixe de segui-lo.",
"search.placeholder": "Pesquisar",
"search_popout.search_format": "Formato de pesquisa avançada",
"search_popout.tips.full_text": "Texto simples retorna toots que você escreveu, favoritou, deu boost, ou em que foi mencionado, assim como nomes de usuário e de exibição, e hashtags correspondentes.",

View File

@ -18,7 +18,7 @@
"account.followers": "Ndjekës",
"account.followers.empty": "Këtë përdorues ende se ndjek kush.",
"account.followers_counter": "{count, plural, one {{counter} Ndjekës} other {{counter} Ndjekës}}",
"account.following": "Following",
"account.following": "Ndjekje",
"account.following_counter": "{count, plural, one {{counter} i Ndjekur} other {{counter} të Ndjekur}}",
"account.follows.empty": "Ky përdorues ende sndjek kënd.",
"account.follows_you": "Ju ndjek",
@ -41,12 +41,12 @@
"account.statuses_counter": "{count, plural, one {{counter} Mesazh} other {{counter} Mesazhe}}",
"account.unblock": "Zhbllokoje @{name}",
"account.unblock_domain": "Zhblloko përkatësinë {domain}",
"account.unblock_short": "Unblock",
"account.unblock_short": "Zhbllokoje",
"account.unendorse": "Mos e përfshi në profil",
"account.unfollow": "Resht së ndjekuri",
"account.unmute": "Ktheji zërin @{name}",
"account.unmute_notifications": "Hiqua ndalimin e shfaqjes njoftimeve nga @{name}",
"account.unmute_short": "Unmute",
"account.unmute_short": "Çheshtoje",
"account_note.placeholder": "Klikoni për të shtuar shënim",
"admin.dashboard.daily_retention": "Shkallë mbajtjeje përdoruesi, në ditë, pas regjistrimit",
"admin.dashboard.monthly_retention": "Shkallë mbajtjeje përdoruesi, në muaj, pas regjistrimit",
@ -294,7 +294,7 @@
"navigation_bar.discover": "Zbuloni",
"navigation_bar.domain_blocks": "Përkatësi të bllokuara",
"navigation_bar.edit_profile": "Përpunoni profilin",
"navigation_bar.explore": "Explore",
"navigation_bar.explore": "Eksploroni",
"navigation_bar.favourites": "Të parapëlqyer",
"navigation_bar.filters": "Fjalë të heshtuara",
"navigation_bar.follow_requests": "Kërkesa për ndjekje",

View File

@ -128,7 +128,7 @@
"confirmations.logout.confirm": "ออกจากระบบ",
"confirmations.logout.message": "คุณแน่ใจหรือไม่ว่าต้องการออกจากระบบ?",
"confirmations.mute.confirm": "ซ่อน",
"confirmations.mute.explanation": "นี่จะซ่อนโพสต์จากเขาและโพสต์ที่กล่าวถึงเขา แต่จะยังอนุญาตให้เขาเห็นโพสต์ของคุณและติดตามคุณ",
"confirmations.mute.explanation": "นี่จะซ่อนโพสต์จากเขาและโพสต์ที่กล่าวถึงเขา แต่จะยังคงอนุญาตให้เขาเห็นโพสต์ของคุณและติดตามคุณ",
"confirmations.mute.message": "คุณแน่ใจหรือไม่ว่าต้องการซ่อน {name}?",
"confirmations.redraft.confirm": "ลบแล้วร่างใหม่",
"confirmations.redraft.message": "คุณแน่ใจหรือไม่ว่าต้องการลบโพสต์นี้แล้วร่างโพสต์ใหม่? รายการโปรดและการดันจะหายไป และการตอบกลับโพสต์ดั้งเดิมจะไม่มีความเกี่ยวพัน",
@ -403,7 +403,7 @@
"report.forward": "ส่งต่อไปยัง {target}",
"report.forward_hint": "บัญชีมาจากเซิร์ฟเวอร์อื่น ส่งสำเนาของรายงานที่ไม่ระบุตัวตนไปที่นั่นด้วย?",
"report.mute": "ซ่อน",
"report.mute_explanation": "คุณจะไม่เห็นโพสต์ของเขา เขายังสามารถติดตามคุณและเห็นโพสต์ของคุณและจะไม่ทราบว่ามีการซ่อนเขา",
"report.mute_explanation": "คุณจะไม่เห็นโพสต์ของเขา เขายังคงสามารถติดตามคุณและเห็นโพสต์ของคุณและจะไม่ทราบว่ามีการซ่อนเขา",
"report.next": "ถัดไป",
"report.placeholder": "ความคิดเห็นเพิ่มเติม",
"report.reasons.dislike": "ฉันไม่ชอบโพสต์",

View File

@ -18,7 +18,7 @@
"account.followers": "Підписники",
"account.followers.empty": "Ніхто ще не підписався на цього користувача.",
"account.followers_counter": "{count, plural, one {{counter} Підписник} few {{counter} Підписники} many {{counter} Підписників} other {{counter} Підписники}}",
"account.following": "Following",
"account.following": "Стежите",
"account.following_counter": "{count, plural, one {{counter} Підписка} few {{counter} Підписки} many {{counter} Підписок} other {{counter} Підписки}}",
"account.follows.empty": "Цей користувач ще ні на кого не підписався.",
"account.follows_you": "Підписаний(-а) на вас",
@ -402,7 +402,7 @@
"report.comment.title": "Is there anything else you think we should know?",
"report.forward": "Надіслати до {target}",
"report.forward_hint": "Це акаунт з іншого серверу. Відправити анонімізовану копію скарги і туди?",
"report.mute": "Mute",
"report.mute": "Заглушити",
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
"report.next": "Далі",
"report.placeholder": "Додаткові коментарі",
@ -415,8 +415,8 @@
"report.reasons.violation": "It violates server rules",
"report.reasons.violation_description": "You are aware that it breaks specific rules",
"report.rules.subtitle": "Select all that apply",
"report.rules.title": "Which rules are being violated?",
"report.statuses.subtitle": "Select all that apply",
"report.rules.title": "Які правила порушено?",
"report.statuses.subtitle": "Виберіть усі варіанти, що підходять",
"report.statuses.title": "Are there any posts that back up this report?",
"report.submit": "Відправити",
"report.target": "Скаржимося на {target}",
@ -424,7 +424,7 @@
"report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
"report.thanks.title": "Don't want to see this?",
"report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
"report.unfollow": "Unfollow @{name}",
"report.unfollow": "Відписатися від @{name}",
"report.unfollow_explanation": "You are following this account. To not see their posts in your home feed anymore, unfollow them.",
"search.placeholder": "Пошук",
"search_popout.search_format": "Розширений формат пошуку",

View File

@ -1009,6 +1009,43 @@
.audio-player {
margin-top: 8px;
}
&.light {
.status__relative-time,
.status__visibility-icon {
color: $light-text-color;
}
.status__display-name {
color: $inverted-text-color;
}
.display-name {
color: $light-text-color;
strong {
color: $inverted-text-color;
}
}
.status__content {
color: $inverted-text-color;
a {
color: $highlight-text-color;
}
a.status__content__spoiler-link {
color: $primary-text-color;
background: $ui-primary-color;
&:hover,
&:focus {
background: lighten($ui-primary-color, 8%);
}
}
}
}
}
.status__relative-time,
@ -1154,43 +1191,6 @@
.audio-player {
margin-top: 8px;
}
&.light {
.status__relative-time,
.status__visibility-icon {
color: $light-text-color;
}
.status__display-name {
color: $inverted-text-color;
}
.display-name {
color: $light-text-color;
strong {
color: $inverted-text-color;
}
}
.status__content {
color: $inverted-text-color;
a {
color: $highlight-text-color;
}
a.status__content__spoiler-link {
color: $primary-text-color;
background: $ui-primary-color;
&:hover,
&:focus {
background: lighten($ui-primary-color, 8%);
}
}
}
}
}
.detailed-status__meta {

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class ActivityPub::Activity::Create < ActivityPub::Activity
include FormattingHelper
def perform
dereference_object!
@ -367,7 +369,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
def converted_text
Formatter.instance.linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
end
def unsupported_media_type?(mime_type)

View File

@ -27,7 +27,9 @@ class ActivityPub::Parser::MediaAttachmentParser
end
def description
@json['summary'].presence || @json['name'].presence
str = @json['summary'].presence || @json['name'].presence
str = str.strip[0...MediaAttachment::MAX_DESCRIPTION_LENGTH] if str.present?
str
end
def focus

View File

@ -5,6 +5,7 @@ class Admin::SystemCheck
Admin::SystemCheck::DatabaseSchemaCheck,
Admin::SystemCheck::SidekiqProcessCheck,
Admin::SystemCheck::RulesCheck,
Admin::SystemCheck::ElasticsearchCheck,
].freeze
def self.perform

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
def pass?
return true unless Chewy.enabled?
running_version.present? && compatible_version?
end
def message
if running_version.present?
Admin::SystemCheck::Message.new(:elasticsearch_version_check, I18n.t('admin.system_checks.elasticsearch_version_check.version_comparison', running_version: running_version, required_version: required_version))
else
Admin::SystemCheck::Message.new(:elasticsearch_running_check)
end
end
private
def running_version
@running_version ||= begin
Chewy.client.info['version']['number']
rescue Faraday::ConnectionFailed
nil
end
end
def required_version
'7.x'
end
def compatible_version?
Gem::Version.new(running_version) >= Gem::Version.new(required_version)
end
def missing_queues
@missing_queues ||= Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] }
end
end

View File

@ -0,0 +1,131 @@
# frozen_string_literal: true
class AdvancedTextFormatter < TextFormatter
class HTMLRenderer < Redcarpet::Render::HTML
def initialize(options, &block)
super(options)
@format_link = block
end
def block_code(code, _language)
<<~HTML.squish
<pre><code>#{ERB::Util.h(code).gsub("\n", '<br/>')}</code></pre>
HTML
end
def autolink(link, link_type)
return link if link_type == :email
@format_link.call(link)
end
end
# @param [String] text
# @param [Hash] options
# @option options [Boolean] :multiline
# @option options [Boolean] :with_domains
# @option options [Boolean] :with_rel_me
# @option options [Array<Account>] :preloaded_accounts
# @option options [String] :content_type
def initialize(text, options = {})
content_type = options.delete(:content_type)
super(text, options)
@text = format_markdown(text) if content_type == 'text/markdown'
end
# Differs from TextFormatter by not messing with newline after parsing
def to_s
return ''.html_safe if text.blank?
html = rewrite do |entity|
if entity[:url]
link_to_url(entity)
elsif entity[:hashtag]
link_to_hashtag(entity)
elsif entity[:screen_name]
link_to_mention(entity)
end
end
html.html_safe # rubocop:disable Rails/OutputSafety
end
# Differs from `TextFormatter` by skipping HTML tags and entities
def entities
@entities ||= begin
gaps = []
total_offset = 0
escaped = text.gsub(/<[^>]*>|&#[0-9]+;/) do |match|
total_offset += match.length - 1
end_offset = Regexp.last_match.end(0)
gaps << [end_offset - total_offset, total_offset]
' '
end
Extractor.extract_entities_with_indices(escaped, extract_url_without_protocol: false).map do |entity|
start_pos, end_pos = entity[:indices]
offset_idx = gaps.rindex { |gap| gap.first <= start_pos }
offset = offset_idx.nil? ? 0 : gaps[offset_idx].last
entity.merge(indices: [start_pos + offset, end_pos + offset])
end
end
end
private
# Differs from `TextFormatter` in that it keeps HTML; but it sanitizes at the end to remain safe
def rewrite
entities.sort_by! do |entity|
entity[:indices].first
end
result = ''.dup
last_index = entities.reduce(0) do |index, entity|
indices = entity[:indices]
result << text[index...indices.first]
result << yield(entity)
indices.last
end
result << text[last_index..-1]
Sanitize.fragment(result, Sanitize::Config::MASTODON_OUTGOING)
end
def format_markdown(html)
html = markdown_formatter.render(html)
html.delete("\r").delete("\n")
end
def markdown_formatter
extensions = {
autolink: true,
no_intra_emphasis: true,
fenced_code_blocks: true,
disable_indented_code_blocks: true,
strikethrough: true,
lax_spacing: true,
space_after_headers: true,
superscript: true,
underline: true,
highlight: true,
footnotes: false,
}
renderer = HTMLRenderer.new({
filter_html: false,
escape_html: false,
no_images: true,
no_styles: true,
safe_links_only: true,
hard_wrap: true,
link_attributes: { target: '_blank', rel: 'nofollow noopener' },
}) do |url|
link_to_url({ url: url })
end
Redcarpet::Markdown.new(renderer, extensions)
end
end

View File

@ -0,0 +1,98 @@
# frozen_string_literal: true
class EmojiFormatter
include RoutingHelper
DISALLOWED_BOUNDING_REGEX = /[[:alnum:]:]/.freeze
attr_reader :html, :custom_emojis, :options
# @param [ActiveSupport::SafeBuffer] html
# @param [Array<CustomEmoji>] custom_emojis
# @param [Hash] options
# @option options [Boolean] :animate
def initialize(html, custom_emojis, options = {})
raise ArgumentError unless html.html_safe?
@html = html
@custom_emojis = custom_emojis
@options = options
end
def to_s
return html if custom_emojis.empty? || html.blank?
i = -1
tag_open_index = nil
inside_shortname = false
shortname_start_index = -1
invisible_depth = 0
last_index = 0
result = ''.dup
while i + 1 < html.size
i += 1
if invisible_depth.zero? && inside_shortname && html[i] == ':'
inside_shortname = false
shortcode = html[shortname_start_index + 1..i - 1]
char_after = html[i + 1]
next unless (char_after.nil? || !DISALLOWED_BOUNDING_REGEX.match?(char_after)) && (emoji = emoji_map[shortcode])
result << html[last_index..shortname_start_index - 1] if shortname_start_index.positive?
result << image_for_emoji(shortcode, emoji)
last_index = i + 1
elsif tag_open_index && html[i] == '>'
tag = html[tag_open_index..i]
tag_open_index = nil
if invisible_depth.positive?
invisible_depth += count_tag_nesting(tag)
elsif tag == '<span class="invisible">'
invisible_depth = 1
end
elsif html[i] == '<'
tag_open_index = i
inside_shortname = false
elsif !tag_open_index && html[i] == ':' && (i.zero? || !DISALLOWED_BOUNDING_REGEX.match?(html[i - 1]))
inside_shortname = true
shortname_start_index = i
end
end
result << html[last_index..-1]
result.html_safe # rubocop:disable Rails/OutputSafety
end
private
def emoji_map
@emoji_map ||= custom_emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
end
def count_tag_nesting(tag)
if tag[1] == '/'
-1
elsif tag[-2] == '/'
0
else
1
end
end
def image_for_emoji(shortcode, emoji)
original_url, static_url = emoji
if animate?
image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
else
image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
end
end
def animate?
@options[:animate]
end
end

View File

@ -1,22 +1,44 @@
# frozen_string_literal: true
module Extractor
MAX_DOMAIN_LENGTH = 253
extend Twitter::TwitterText::Extractor
module_function
# :yields: username, list_slug, start, end
def extract_entities_with_indices(text, options = {}, &block)
entities = begin
extract_urls_with_indices(text, options) +
extract_hashtags_with_indices(text, check_url_overlap: false) +
extract_mentions_or_lists_with_indices(text) +
extract_extra_uris_with_indices(text)
end
return [] if entities.empty?
entities = remove_overlapping_entities(entities)
entities.each(&block) if block_given?
entities
end
def extract_mentions_or_lists_with_indices(text)
return [] unless Twitter::TwitterText::Regex[:at_signs].match?(text)
return [] unless text && Twitter::TwitterText::Regex[:at_signs].match?(text)
possible_entries = []
text.to_s.scan(Account::MENTION_RE) do |screen_name, _|
text.scan(Account::MENTION_RE) do |screen_name, _|
match_data = $LAST_MATCH_INFO
after = $'
after = $'
unless Twitter::TwitterText::Regex[:end_mention_match].match?(after)
_, domain = screen_name.split('@')
next if domain.present? && domain.length > MAX_DOMAIN_LENGTH
start_position = match_data.char_begin(1) - 1
end_position = match_data.char_end(1)
end_position = match_data.char_end(1)
possible_entries << {
screen_name: screen_name,
indices: [start_position, end_position],
@ -29,36 +51,70 @@ module Extractor
yield mention[:screen_name], mention[:indices].first, mention[:indices].last
end
end
possible_entries
end
def extract_hashtags_with_indices(text, **)
return [] unless /#/.match?(text)
def extract_hashtags_with_indices(text, _options = {})
return [] unless text&.index('#')
possible_entries = []
tags = []
text.scan(Tag::HASHTAG_RE) do |hash_text, _|
match_data = $LAST_MATCH_INFO
match_data = $LAST_MATCH_INFO
start_position = match_data.char_begin(1) - 1
end_position = match_data.char_end(1)
after = $'
end_position = match_data.char_end(1)
after = $'
if %r{\A://}.match?(after)
hash_text.match(/(.+)(https?\Z)/) do |matched|
hash_text = matched[1]
hash_text = matched[1]
end_position -= matched[2].codepoint_length
end
end
tags << {
possible_entries << {
hashtag: hash_text,
indices: [start_position, end_position],
}
end
tags.each { |tag| yield tag[:hashtag], tag[:indices].first, tag[:indices].last } if block_given?
tags
if block_given?
possible_entries.each do |tag|
yield tag[:hashtag], tag[:indices].first, tag[:indices].last
end
end
possible_entries
end
def extract_cashtags_with_indices(_text)
[] # always returns empty array
[]
end
def extract_extra_uris_with_indices(text)
return [] unless text&.index(':')
possible_entries = []
text.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
valid_uri_match_data = $LAST_MATCH_INFO
start_position = valid_uri_match_data.char_begin(3)
end_position = valid_uri_match_data.char_end(3)
possible_entries << {
url: valid_uri_match_data[3],
indices: [start_position, end_position],
}
end
if block_given?
possible_entries.each do |url|
yield url[:url], url[:indices].first, url[:indices].last
end
end
possible_entries
end
end

View File

@ -500,16 +500,8 @@ class FeedManager
return false if active_filters.empty?
combined_regex = Regexp.union(active_filters)
status = status.reblog if status.reblog?
combined_text = [
Formatter.instance.plaintext(status),
status.spoiler_text,
status.preloadable_poll ? status.preloadable_poll.options.join("\n\n") : nil,
status.ordered_media_attachments.map(&:description).join("\n\n"),
].compact.join("\n\n")
combined_regex.match?(combined_text)
combined_regex.match?(status.proper.searchable_text)
end
# Adds a status to an account's feed, returning true if a status was

View File

@ -1,382 +0,0 @@
# frozen_string_literal: true
require 'singleton'
class HTMLRenderer < Redcarpet::Render::HTML
def block_code(code, language)
"<pre><code>#{encode(code).gsub("\n", "<br/>")}</code></pre>"
end
def autolink(link, link_type)
return link if link_type == :email
Formatter.instance.link_url(link)
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
encode(link)
end
private
def html_entities
@html_entities ||= HTMLEntities.new
end
def encode(html)
html_entities.encode(html)
end
end
class Formatter
include Singleton
include RoutingHelper
include ActionView::Helpers::TextHelper
def format(status, **options)
if status.respond_to?(:reblog?) && status.reblog?
prepend_reblog = status.reblog.account.acct
status = status.proper
else
prepend_reblog = false
end
raw_content = status.text
if options[:inline_poll_options] && status.preloadable_poll
raw_content = raw_content + "\n\n" + status.preloadable_poll.options.map { |title| "[ ] #{title}" }.join("\n")
end
return '' if raw_content.blank?
unless status.local?
html = reformat(raw_content)
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
return html.html_safe # rubocop:disable Rails/OutputSafety
end
linkable_accounts = status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []
linkable_accounts << status.account
html = raw_content
html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
html = format_markdown(html) if status.content_type == 'text/markdown'
html = encode_and_link_urls(html, linkable_accounts, keep_html: %w(text/markdown text/html).include?(status.content_type))
html = reformat(html, true) if %w(text/markdown text/html).include?(status.content_type)
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
unless %w(text/markdown text/html).include?(status.content_type)
html = simple_format(html, {}, sanitize: false)
html = html.delete("\n")
end
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_markdown(html)
html = markdown_formatter.render(html)
html.delete("\r").delete("\n")
end
def reformat(html, outgoing = false)
sanitize(html, Sanitize::Config::MASTODON_STRICT.merge(outgoing: outgoing))
rescue ArgumentError
''
end
def plaintext(status)
return status.text if status.local?
text = status.text.gsub(/(<br \/>|<br>|<\/p>)+/) { |match| "#{match}\n" }
strip_tags(text)
end
def simplified_format(account, **options)
return '' if account.note.blank?
html = account.local? ? linkify(account.note) : reformat(account.note)
html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
html.html_safe # rubocop:disable Rails/OutputSafety
end
def sanitize(html, config)
Sanitize.fragment(html, config)
end
def format_spoiler(status, **options)
html = encode(status.spoiler_text)
html = encode_custom_emojis(html, status.emojis, options[:autoplay])
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_poll_option(status, option, **options)
html = encode(option.title)
html = encode_custom_emojis(html, status.emojis, options[:autoplay])
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_display_name(account, **options)
html = encode(account.display_name.presence || account.username)
html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
html.html_safe # rubocop:disable Rails/OutputSafety
end
def format_field(account, str, **options)
html = account.local? ? encode_and_link_urls(str, me: true, with_domain: true) : reformat(str)
html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
html.html_safe # rubocop:disable Rails/OutputSafety
end
def linkify(text)
html = encode_and_link_urls(text)
html = simple_format(html, {}, sanitize: false)
html = html.delete("\n")
html.html_safe # rubocop:disable Rails/OutputSafety
end
def link_url(url)
"<a href=\"#{encode(url)}\" target=\"blank\" rel=\"nofollow noopener noreferrer\">#{link_html(url)}</a>"
end
private
def markdown_formatter
extensions = {
autolink: true,
no_intra_emphasis: true,
fenced_code_blocks: true,
disable_indented_code_blocks: true,
strikethrough: true,
lax_spacing: true,
space_after_headers: true,
superscript: true,
underline: true,
highlight: true,
footnotes: false,
}
renderer = HTMLRenderer.new({
filter_html: false,
escape_html: false,
no_images: true,
no_styles: true,
safe_links_only: true,
hard_wrap: true,
link_attributes: { target: '_blank', rel: 'nofollow noopener' },
})
Redcarpet::Markdown.new(renderer, extensions)
end
def html_entities
@html_entities ||= HTMLEntities.new
end
def encode(html)
html_entities.encode(html)
end
def encode_and_link_urls(html, accounts = nil, options = {})
if accounts.is_a?(Hash)
options = accounts
accounts = nil
end
entities = options[:keep_html] ? html_friendly_extractor(html) : utf8_friendly_extractor(html, extract_url_without_protocol: false)
rewrite(html.dup, entities, options[:keep_html]) do |entity|
if entity[:url]
link_to_url(entity, options)
elsif entity[:hashtag]
link_to_hashtag(entity)
elsif entity[:screen_name]
link_to_mention(entity, accounts, options)
end
end
end
def count_tag_nesting(tag)
if tag[1] == '/' then -1
elsif tag[-2] == '/' then 0
else 1
end
end
# rubocop:disable Metrics/BlockNesting
def encode_custom_emojis(html, emojis, animate = false)
return html if emojis.empty?
emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
i = -1
tag_open_index = nil
inside_shortname = false
shortname_start_index = -1
invisible_depth = 0
while i + 1 < html.size
i += 1
if invisible_depth.zero? && inside_shortname && html[i] == ':'
shortcode = html[shortname_start_index + 1..i - 1]
emoji = emoji_map[shortcode]
if emoji
original_url, static_url = emoji
replacement = begin
if animate
image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
else
image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
end
end
before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
html = before_html + replacement + html[i + 1..-1]
i += replacement.size - (shortcode.size + 2) - 1
else
i -= 1
end
inside_shortname = false
elsif tag_open_index && html[i] == '>'
tag = html[tag_open_index..i]
tag_open_index = nil
if invisible_depth.positive?
invisible_depth += count_tag_nesting(tag)
elsif tag == '<span class="invisible">'
invisible_depth = 1
end
elsif html[i] == '<'
tag_open_index = i
inside_shortname = false
elsif !tag_open_index && html[i] == ':'
inside_shortname = true
shortname_start_index = i
end
end
html
end
# rubocop:enable Metrics/BlockNesting
def rewrite(text, entities, keep_html = false)
text = text.to_s
# Sort by start index
entities = entities.sort_by do |entity|
indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
indices.first
end
result = []
last_index = entities.reduce(0) do |index, entity|
indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
result << (keep_html ? text[index...indices.first] : encode(text[index...indices.first]))
result << yield(entity)
indices.last
end
result << (keep_html ? text[last_index..-1] : encode(text[last_index..-1]))
result.flatten.join
end
def utf8_friendly_extractor(text, options = {})
# Note: I couldn't obtain list_slug with @user/list-name format
# for mention so this requires additional check
special = Extractor.extract_urls_with_indices(text, options)
standard = Extractor.extract_entities_with_indices(text, options)
extra = Extractor.extract_extra_uris_with_indices(text, options)
Extractor.remove_overlapping_entities(special + standard + extra)
end
def html_friendly_extractor(html, options = {})
gaps = []
total_offset = 0
escaped = html.gsub(/<[^>]*>|&#[0-9]+;/) do |match|
total_offset += match.length - 1
end_offset = Regexp.last_match.end(0)
gaps << [end_offset - total_offset, total_offset]
"\u200b"
end
entities = Extractor.extract_hashtags_with_indices(escaped, :check_url_overlap => false) +
Extractor.extract_mentions_or_lists_with_indices(escaped)
Extractor.remove_overlapping_entities(entities).map do |extract|
pos = extract[:indices].first
offset_idx = gaps.rindex { |gap| gap.first <= pos }
offset = offset_idx.nil? ? 0 : gaps[offset_idx].last
next extract.merge(
:indices => [extract[:indices].first + offset, extract[:indices].last + offset]
)
end
end
def link_to_url(entity, options = {})
url = Addressable::URI.parse(entity[:url])
html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
encode(entity[:url])
end
def link_to_mention(entity, linkable_accounts, options = {})
acct = entity[:screen_name]
return link_to_account(acct, options) unless linkable_accounts
same_username_hits = 0
account = nil
username, domain = acct.split('@')
domain = nil if TagManager.instance.local_domain?(domain)
linkable_accounts.each do |item|
same_username = item.username.casecmp(username).zero?
same_domain = item.domain.nil? ? domain.nil? : item.domain.casecmp(domain)&.zero?
if same_username && !same_domain
same_username_hits += 1
elsif same_username && same_domain
account = item
end
end
account ? mention_html(account, with_domain: same_username_hits.positive? || options[:with_domain]) : "@#{encode(acct)}"
end
def link_to_account(acct, options = {})
username, domain = acct.split('@')
domain = nil if TagManager.instance.local_domain?(domain)
account = EntityCache.instance.mention(username, domain)
account ? mention_html(account, with_domain: options[:with_domain]) : "@#{encode(acct)}"
end
def link_to_hashtag(entity)
hashtag_html(entity[:hashtag])
end
def link_html(url)
url = Addressable::URI.parse(url).to_s
prefix = url.match(/\A(https?:\/\/(www\.)?|xmpp:)/).to_s
text = url[prefix.length, 30]
suffix = url[prefix.length + 30..-1]
cutoff = url[prefix.length..-1].length > 30
"<span class=\"invisible\">#{encode(prefix)}</span><span class=\"#{cutoff ? 'ellipsis' : ''}\">#{encode(text)}</span><span class=\"invisible\">#{encode(suffix)}</span>"
end
def hashtag_html(tag)
"<a href=\"#{encode(tag_url(tag))}\" class=\"mention hashtag\" rel=\"tag\">#<span>#{encode(tag)}</span></a>"
end
def mention_html(account, with_domain: false)
"<span class=\"h-card\"><a href=\"#{encode(ActivityPub::TagManager.instance.url_for(account))}\" class=\"u-url mention\">@<span>#{encode(with_domain ? account.pretty_acct : account.username)}</span></a></span>"
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
class HtmlAwareFormatter
attr_reader :text, :local, :options
alias local? local
# @param [String] text
# @param [Boolean] local
# @param [Hash] options
def initialize(text, local, options = {})
@text = text
@local = local
@options = options
end
def to_s
return ''.html_safe if text.blank?
if local?
linkify
else
reformat.html_safe # rubocop:disable Rails/OutputSafety
end
rescue ArgumentError
''.html_safe
end
private
def reformat
Sanitize.fragment(text, Sanitize::Config::MASTODON_STRICT)
end
def linkify
if %w(text/markdown text/html).include?(@options[:content_type])
AdvancedTextFormatter.new(text, options).to_s
else
TextFormatter.new(text, options).to_s
end
end
end

View File

@ -208,7 +208,7 @@ class LinkDetailsExtractor
end
def valid_url_or_nil(str, same_origin_only: false)
return if str.blank?
return if str.blank? || str == 'null'
url = @original_url + Addressable::URI.parse(str)

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
class PlainTextFormatter
include ActionView::Helpers::TextHelper
NEWLINE_TAGS_RE = /(<br \/>|<br>|<\/p>)+/.freeze
attr_reader :text, :local
alias local? local
def initialize(text, local)
@text = text
@local = local
end
def to_s
if local?
text
else
strip_tags(insert_newlines).chomp
end
end
private
def insert_newlines
text.gsub(NEWLINE_TAGS_RE) { |match| "#{match}\n" }
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class RSS::Serializer
include FormattingHelper
private
def render_statuses(builder, statuses)
@ -9,7 +11,7 @@ class RSS::Serializer
item.title(status_title(status))
.link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at)
.description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
.description(status_description(status))
status.ordered_media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
@ -19,9 +21,8 @@ class RSS::Serializer
end
def status_title(status)
return "#{status.account.acct} deleted status" if status.destroyed?
preview = status.proper.spoiler_text.presence || status.proper.text
if preview.length > 30 || preview[0, 30].include?("\n")
preview = preview[0, 30]
preview = preview[0, preview.index("\n").presence || 30] + '…'
@ -35,4 +36,20 @@ class RSS::Serializer
"#{status.account.acct}: #{preview}"
end
end
def status_description(status)
if status.proper.spoiler_text?
status.proper.spoiler_text
else
html = status_content_format(status.proper).to_str
after_html = ''
if status.proper.preloadable_poll
poll_options_html = status.proper.preloadable_poll.options.map { |o| "[ ] #{o}" }.join('<br />')
after_html = "<p>#{poll_options_html}</p>"
end
"#{html}#{after_html}"
end
end
end

158
app/lib/text_formatter.rb Normal file
View File

@ -0,0 +1,158 @@
# frozen_string_literal: true
class TextFormatter
include ActionView::Helpers::TextHelper
include ERB::Util
include RoutingHelper
URL_PREFIX_REGEX = /\A(https?:\/\/(www\.)?|xmpp:)/.freeze
DEFAULT_REL = %w(nofollow noopener noreferrer).freeze
DEFAULT_OPTIONS = {
multiline: true,
}.freeze
attr_reader :text, :options
# @param [String] text
# @param [Hash] options
# @option options [Boolean] :multiline
# @option options [Boolean] :with_domains
# @option options [Boolean] :with_rel_me
# @option options [Array<Account>] :preloaded_accounts
def initialize(text, options = {})
@text = text
@options = DEFAULT_OPTIONS.merge(options)
end
def entities
@entities ||= Extractor.extract_entities_with_indices(text, extract_url_without_protocol: false)
end
def to_s
return ''.html_safe if text.blank?
html = rewrite do |entity|
if entity[:url]
link_to_url(entity)
elsif entity[:hashtag]
link_to_hashtag(entity)
elsif entity[:screen_name]
link_to_mention(entity)
end
end
html = simple_format(html, {}, sanitize: false).delete("\n") if multiline?
html.html_safe # rubocop:disable Rails/OutputSafety
end
private
def rewrite
entities.sort_by! do |entity|
entity[:indices].first
end
result = ''.dup
last_index = entities.reduce(0) do |index, entity|
indices = entity[:indices]
result << h(text[index...indices.first])
result << yield(entity)
indices.last
end
result << h(text[last_index..-1])
result
end
def link_to_url(entity)
url = Addressable::URI.parse(entity[:url]).to_s
rel = with_rel_me? ? (DEFAULT_REL + %w(me)) : DEFAULT_REL
prefix = url.match(URL_PREFIX_REGEX).to_s
display_url = url[prefix.length, 30]
suffix = url[prefix.length + 30..-1]
cutoff = url[prefix.length..-1].length > 30
<<~HTML.squish
<a href="#{h(url)}" target="_blank" rel="#{rel.join(' ')}"><span class="invisible">#{h(prefix)}</span><span class="#{cutoff ? 'ellipsis' : ''}">#{h(display_url)}</span><span class="invisible">#{h(suffix)}</span></a>
HTML
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
h(entity[:url])
end
def link_to_hashtag(entity)
hashtag = entity[:hashtag]
url = tag_url(hashtag)
<<~HTML.squish
<a href="#{h(url)}" class="mention hashtag" rel="tag">#<span>#{h(hashtag)}</span></a>
HTML
end
def link_to_mention(entity)
username, domain = entity[:screen_name].split('@')
domain = nil if local_domain?(domain)
account = nil
if preloaded_accounts?
same_username_hits = 0
preloaded_accounts.each do |other_account|
same_username = other_account.username.casecmp(username).zero?
same_domain = other_account.domain.nil? ? domain.nil? : other_account.domain.casecmp(domain)&.zero?
if same_username && !same_domain
same_username_hits += 1
elsif same_username && same_domain
account = other_account
end
end
else
account = entity_cache.mention(username, domain)
end
return "@#{h(entity[:screen_name])}" if account.nil?
url = ActivityPub::TagManager.instance.url_for(account)
display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
<<~HTML.squish
<span class="h-card"><a href="#{h(url)}" class="u-url mention">@<span>#{h(display_username)}</span></a></span>
HTML
end
def entity_cache
@entity_cache ||= EntityCache.instance
end
def tag_manager
@tag_manager ||= TagManager.instance
end
delegate :local_domain?, to: :tag_manager
def multiline?
options[:multiline]
end
def with_domains?
options[:with_domains]
end
def with_rel_me?
options[:with_rel_me]
end
def preloaded_accounts
options[:preloaded_accounts]
end
def preloaded_accounts?
preloaded_accounts.present?
end
end

View File

@ -5,6 +5,7 @@ class ApplicationMailer < ActionMailer::Base
helper :application
helper :instance
helper :formatting
protected

View File

@ -132,13 +132,13 @@ class Account < ApplicationRecord
:approved?,
:pending?,
:disabled?,
:unconfirmed?,
:unconfirmed_or_pending?,
:role,
:admin?,
:moderator?,
:staff?,
:locale,
:hides_network?,
:shows_application?,
to: :user,
prefix: true,

View File

@ -80,6 +80,10 @@ class AccountFilter
accounts_with_users.merge(User.pending)
when 'suspended'
Account.suspended
when 'disabled'
accounts_with_users.merge(User.disabled)
when 'silenced'
Account.silenced
else
raise "Unknown status: #{value}"
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module StatusSnapshotConcern
extend ActiveSupport::Concern
included do
has_many :edits, class_name: 'StatusEdit', inverse_of: :status, dependent: :destroy
end
def edited?
edited_at.present?
end
def build_snapshot(account_id: nil, at_time: nil, rate_limit: true)
# We don't use `edits#new` here to avoid it having saved when the
# status is saved, since we want to control that manually
StatusEdit.new(
status_id: id,
text: text,
spoiler_text: spoiler_text,
sensitive: sensitive,
ordered_media_attachment_ids: ordered_media_attachment_ids&.dup || media_attachments.pluck(:id),
media_descriptions: ordered_media_attachments.map(&:description),
poll_options: preloadable_poll&.options&.dup,
account_id: account_id || self.account_id,
content_type: content_type,
created_at: at_time || edited_at,
rate_limit: rate_limit
)
end
def snapshot!(**options)
build_snapshot(**options).save!
end
end

View File

@ -185,7 +185,7 @@ class MediaAttachment < ApplicationRecord
remotable_attachment :thumbnail, IMAGE_LIMIT, suppress_errors: true, download_on_assign: false
validates :account, presence: true
validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }, if: :local?
validates :description, length: { maximum: MAX_DESCRIPTION_LENGTH }
validates :file, presence: true, if: :local?
validates :thumbnail, absence: true, if: -> { local? && !audio_or_video? }
@ -258,7 +258,6 @@ class MediaAttachment < ApplicationRecord
after_commit :enqueue_processing, on: :create
after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local?
before_create :set_unknown_type
before_create :set_processing
@ -306,10 +305,6 @@ class MediaAttachment < ApplicationRecord
self.type = :unknown if file.blank? && !type_changed?
end
def prepare_description
self.description = description.strip[0...MAX_DESCRIPTION_LENGTH] unless description.nil?
end
def set_type_and_extension
self.type = begin
if VIDEO_MIME_TYPES.include?(file_content_type)

View File

@ -37,6 +37,7 @@ class Status < ApplicationRecord
include Paginable
include Cacheable
include StatusThreadingConcern
include StatusSnapshotConcern
include RateLimitable
rate_limit by: :account, family: :statuses
@ -61,8 +62,6 @@ class Status < ApplicationRecord
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :replies, optional: true
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblogs, optional: true
has_many :edits, class_name: 'StatusEdit', inverse_of: :status, dependent: :destroy
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :bookmarks, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
@ -161,6 +160,15 @@ class Status < ApplicationRecord
ids.uniq
end
def searchable_text
[
spoiler_text,
FormattingHelper.extract_status_plain_text(self),
preloadable_poll ? preloadable_poll.options.join("\n\n") : nil,
ordered_media_attachments.map(&:description).join("\n\n"),
].compact.join("\n\n")
end
def reply?
!in_reply_to_id.nil? || attributes['reply']
end
@ -217,25 +225,6 @@ class Status < ApplicationRecord
public_visibility? || unlisted_visibility?
end
def snapshot!(account_id: nil, at_time: nil, rate_limit: true)
edits.create!(
text: text,
spoiler_text: spoiler_text,
sensitive: sensitive,
ordered_media_attachment_ids: ordered_media_attachment_ids || media_attachments.pluck(:id),
media_descriptions: ordered_media_attachments.map(&:description),
poll_options: preloadable_poll&.options,
account_id: account_id || self.account_id,
content_type: content_type,
created_at: at_time || edited_at,
rate_limit: rate_limit
)
end
def edited?
edited_at.present?
end
alias sign? distributable?
def with_media?

View File

@ -37,7 +37,7 @@ class Trends::Query
end
def offset!(value)
@offset = value
@offset = value.to_i
self
end
@ -46,7 +46,7 @@ class Trends::Query
end
def limit!(value)
@limit = value
@limit = value.to_i
self
end

View File

@ -91,11 +91,11 @@ class User < ApplicationRecord
validates :invite_request, presence: true, on: :create, if: :invite_text_required?
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
validates_with BlacklistedEmailValidator, on: :create
validates_with BlacklistedEmailValidator, if: -> { !confirmed? }
validates_with EmailMxValidator, if: :validate_email_dns?
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
# Those are honeypot/antispam fields
# Honeypot/anti-spam fields
attr_accessor :registration_form_time, :website, :confirm_password
validates_with RegistrationFormTimeValidator, on: :create
@ -208,8 +208,12 @@ class User < ApplicationRecord
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
end
def unconfirmed?
!confirmed?
end
def unconfirmed_or_pending?
!(confirmed? && approved?)
unconfirmed? || pending?
end
def inactive_message

View File

@ -2,6 +2,7 @@
class ActivityPub::ActorSerializer < ActivityPub::Serializer
include RoutingHelper
include FormattingHelper
context :security
@ -102,7 +103,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def summary
object.suspended? ? '' : Formatter.instance.simplified_format(object)
object.suspended? ? '' : account_bio_format(object)
end
def icon
@ -185,6 +186,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
class Account::FieldSerializer < ActivityPub::Serializer
include FormattingHelper
attributes :type, :name, :value
def type
@ -192,7 +195,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def value
Formatter.instance.format_field(object.account, object.value)
account_field_value_format(object)
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class ActivityPub::NoteSerializer < ActivityPub::Serializer
include FormattingHelper
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :direct_message
attributes :id, :type, :summary,
@ -50,11 +52,11 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
end
def content
Formatter.instance.format(object)
status_content_format(object)
end
def content_map
{ object.language => Formatter.instance.format(object) }
{ object.language => content }
end
def replies

View File

@ -2,6 +2,7 @@
class REST::AccountSerializer < ActiveModel::Serializer
include RoutingHelper
include FormattingHelper
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
:note, :url, :avatar, :avatar_static, :header, :header_static,
@ -14,10 +15,12 @@ class REST::AccountSerializer < ActiveModel::Serializer
attribute :suspended, if: :suspended?
class FieldSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :name, :value, :verified_at
def value
Formatter.instance.format_field(object.account, object.value)
account_field_value_format(object)
end
end
@ -32,7 +35,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def note
object.suspended? ? '' : Formatter.instance.simplified_format(object)
object.suspended? ? '' : account_bio_format(object)
end
def url

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::AnnouncementSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :id, :content, :starts_at, :ends_at, :all_day,
:published_at, :updated_at
@ -25,7 +27,7 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer
end
def content
Formatter.instance.linkify(object.text)
linkify(object.text)
end
def reactions

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::StatusEditSerializer < ActiveModel::Serializer
include FormattingHelper
has_one :account, serializer: REST::AccountSerializer
attributes :content, :spoiler_text, :sensitive, :created_at
@ -11,7 +13,7 @@ class REST::StatusEditSerializer < ActiveModel::Serializer
attribute :poll, if: -> { object.poll_options.present? }
def content
Formatter.instance.format(object)
status_content_format(object)
end
def poll

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::StatusSerializer < ActiveModel::Serializer
include FormattingHelper
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count,
@ -73,7 +75,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def content
Formatter.instance.format(object)
status_content_format(object)
end
def url

View File

@ -4,6 +4,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
include JsonLdHelper
def call(status, json)
raise ArgumentError, 'Status has unsaved changes' if status.changed?
@json = json
@status_parser = ActivityPub::Parser::StatusParser.new(@json)
@uri = @status_parser.uri
@ -17,16 +19,19 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
last_edit_date = status.edited_at.presence || status.created_at
# Since we rely on tracking of previous changes, ensure clean slate
status.clear_changes_information
# Only allow processing one create/update per status at a time
RedisLock.acquire(lock_options) do |lock|
if lock.acquired?
Status.transaction do
create_previous_edit!
record_previous_edit!
update_media_attachments!
update_poll!
update_immediate_attributes!
update_metadata!
create_edit!
create_edits!
end
queue_poll_notifications!
@ -216,19 +221,14 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
{ redis: Redis.current, key: "create:#{@uri}", autorelease: 15.minutes.seconds }
end
def create_previous_edit!
# We only need to create a previous edit when no previous edits exist, e.g.
# when the status has never been edited. For other cases, we always create
# an edit, so the step can be skipped
return if @status.edits.any?
@status.snapshot!(at_time: @status.created_at, rate_limit: false)
def record_previous_edit!
@previous_edit = @status.build_snapshot(at_time: @status.created_at, rate_limit: false) if @status.edits.empty?
end
def create_edit!
def create_edits!
return unless significant_changes?
@previous_edit&.save!
@status.snapshot!(account_id: @account.id, rate_limit: false)
end

View File

@ -134,7 +134,7 @@ class FetchLinkCardService < BaseService
when 'video'
@card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0
@card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED)
@card.html = Sanitize.fragment(embed[:html], Sanitize::Config::MASTODON_OEMBED)
@card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
when 'rich'
# Most providers rely on <script> tags, which is a no-no

View File

@ -48,47 +48,23 @@ class NotifyService < BaseService
return false if @notification.target_status.in_reply_to_id.nil?
# Using an SQL CTE to avoid unneeded back-and-forth with SQL server in case of long threads
!Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id]).zero?
WITH RECURSIVE ancestors(id, in_reply_to_id, replying_to_sender, path) AS (
SELECT
s.id,
s.in_reply_to_id,
(CASE
WHEN s.account_id = :recipient_id THEN
EXISTS (
SELECT *
FROM mentions m
WHERE m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
)
ELSE
FALSE
END),
ARRAY[s.id]
!Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id, depth_limit: 100]).zero?
WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS (
SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0
FROM statuses s
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE s.id = :id
UNION ALL
SELECT
s.id,
s.in_reply_to_id,
(CASE
WHEN s.account_id = :recipient_id THEN
EXISTS (
SELECT *
FROM mentions m
WHERE m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
)
ELSE
FALSE
END),
st.path || s.id
SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1
FROM ancestors st
JOIN statuses s ON s.id = st.in_reply_to_id
WHERE st.replying_to_sender IS FALSE AND NOT s.id = ANY(path)
LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit
)
SELECT COUNT(*)
FROM ancestors st
JOIN statuses s ON s.id = st.id
WHERE st.replying_to_sender IS TRUE AND s.visibility = 3
WHERE st.mention_id IS NOT NULL AND s.visibility = 3
SQL
end

View File

@ -4,6 +4,8 @@ class UpdateStatusService < BaseService
include Redisable
include LanguagesHelper
class NoChangesSubmittedError < StandardError; end
# @param [Status] status
# @param [Integer] account_id
# @param [Hash] options
@ -18,6 +20,8 @@ class UpdateStatusService < BaseService
@status = status
@options = options
@account_id = account_id
@media_attachments_changed = false
@poll_changed = false
Status.transaction do
create_previous_edit!
@ -33,18 +37,24 @@ class UpdateStatusService < BaseService
broadcast_updates!
@status
rescue NoChangesSubmittedError
# For calls that result in no changes, swallow the error
# but get back to the original state
@status.reload
end
private
def update_media_attachments!
previous_media_attachments = @status.media_attachments.to_a
previous_media_attachments = @status.ordered_media_attachments.to_a
next_media_attachments = validate_media!
added_media_attachments = next_media_attachments - previous_media_attachments
MediaAttachment.where(id: added_media_attachments.map(&:id)).update_all(status_id: @status.id)
@status.ordered_media_attachment_ids = (@options[:media_ids] || []).map(&:to_i) & next_media_attachments.map(&:id)
@media_attachments_changed = previous_media_attachments.map(&:id) != @status.ordered_media_attachment_ids
@status.media_attachments.reload
end
@ -70,20 +80,23 @@ class UpdateStatusService < BaseService
# If for some reasons the options were changed, it invalidates all previous
# votes, so we need to remove them
poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple
@poll_changed = true if @options[:poll][:options] != poll.options || ActiveModel::Type::Boolean.new.cast(@options[:poll][:multiple]) != poll.multiple
poll.options = @options[:poll][:options]
poll.hide_totals = @options[:poll][:hide_totals] || false
poll.multiple = @options[:poll][:multiple] || false
poll.expires_in = @options[:poll][:expires_in]
poll.reset_votes! if poll_changed
poll.reset_votes! if @poll_changed
poll.save!
@status.poll_id = poll.id
elsif previous_poll.present?
previous_poll.destroy
@poll_changed = true
@status.poll_id = nil
end
@poll_changed = true if @previous_expires_at != @status.preloadable_poll&.expires_at
end
def update_immediate_attributes!
@ -92,8 +105,11 @@ class UpdateStatusService < BaseService
@status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text)
@status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale)
@status.content_type = @options[:content_type] || @status.content_type
@status.edited_at = Time.now.utc
# We raise here to rollback the entire transaction
raise NoChangesSubmittedError unless significant_changes?
@status.edited_at = Time.now.utc
@status.save!
end
@ -139,4 +155,8 @@ class UpdateStatusService < BaseService
def create_edit!
@status.snapshot!(account_id: @account_id)
end
def significant_changes?
@status.changed? || @poll_changed || @media_attachments_changed
end
end

View File

@ -3,35 +3,57 @@
class StatusLengthValidator < ActiveModel::Validator
MAX_CHARS = (ENV['MAX_TOOT_CHARS'] || 7777).to_i
URL_PLACEHOLDER_CHARS = 23
URL_PLACEHOLDER = "\1#{'x' * URL_PLACEHOLDER_CHARS}"
URL_PLACEHOLDER = 'x' * 23
def validate(status)
return unless status.local? && !status.reblog?
@status = status
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?
status.errors.add(:text, I18n.t('statuses.over_character_limit', max: MAX_CHARS)) if too_long?(status)
end
private
def too_long?
countable_length > MAX_CHARS
def too_long?(status)
countable_length(combined_text(status)) > MAX_CHARS
end
def countable_length
total_text.mb_chars.grapheme_length
def countable_length(str)
str.mb_chars.grapheme_length
end
def total_text
[@status.spoiler_text, countable_text].join
def combined_text(status)
[status.spoiler_text, countable_text(status.text)].join
end
def countable_text
return '' if @status.text.nil?
def countable_text(str)
return '' if str.blank?
@status.text.dup.tap do |new_text|
new_text.gsub!(FetchLinkCardService::URL_PATTERN, URL_PLACEHOLDER)
new_text.gsub!(Account::MENTION_RE, '@\2')
# To ensure that we only give length concessions to entities that
# will be correctly parsed during formatting, we go through full
# entity extraction
entities = Extractor.remove_overlapping_entities(Extractor.extract_urls_with_indices(str, extract_url_without_protocol: false) + Extractor.extract_mentions_or_lists_with_indices(str))
rewrite_entities(str, entities) do |entity|
if entity[:url]
URL_PLACEHOLDER
elsif entity[:screen_name]
"@#{entity[:screen_name].split('@').first}"
end
end
end
def rewrite_entities(str, entities)
entities.sort_by! { |entity| entity[:indices].first }
result = ''.dup
last_index = entities.reduce(0) do |index, entity|
result << str[index...entity[:indices].first]
result << yield(entity)
entity[:indices].last
end
result << str[last_index..-1]
result
end
end

View File

@ -5,17 +5,17 @@
.account__header__fields
- fields.each do |field|
%dl
%dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
%dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
%dd{ title: field.value, class: custom_field_classes(field) }
- if field.verified?
%span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
= fa_icon 'check'
= Formatter.instance.format_field(account, field.value, custom_emojify: true)
= prerender_custom_emojis(account_field_value_format(field), account.emojis)
= account_badge(account)
- if account.note.present?
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
.account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
.public-account-bio__extra
= t 'accounts.joined', date: l(account.created_at, format: :month)

View File

@ -1,4 +1,4 @@
.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', account.suspended? && 'batch-table__row--muted'] }
.batch-table__row{ class: [!account.suspended? && account.user_pending? && 'batch-table__row--attention', (account.suspended? || account.user_unconfirmed?) && 'batch-table__row--muted'] }
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
= f.check_box :account_ids, { multiple: true, include_hidden: false }, account.id
.batch-table__row__content.batch-table__row__content--unpadded

View File

@ -16,16 +16,16 @@
.account__header__fields
- fields.each do |field|
%dl
%dt.emojify{ title: field.name }= Formatter.instance.format_field(account, field.name, custom_emojify: true)
%dt.emojify{ title: field.name }= prerender_custom_emojis(h(field.name), account.emojis)
%dd{ title: field.value, class: custom_field_classes(field) }
- if field.verified?
%span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) }
= fa_icon 'check'
= Formatter.instance.format_field(account, field.value, custom_emojify: true)
= prerender_custom_emojis(account_field_value_format(field, with_rel_me: false), account.emojis)
- if account.note.present?
%div
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
.account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis)
.dashboard__counters.admin-account-counters
%div

View File

@ -84,7 +84,8 @@
- else
%span.negative-hint
= t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
%span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
%span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
- if @instance.purgeable?
%p= t('admin.instances.purge_description_html')

View File

@ -4,12 +4,12 @@
.batch-table__row__content
.status__content><
- if status.proper.spoiler_text.blank?
= Formatter.instance.format(status.proper, custom_emojify: true)
= prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
- else
%details<
%summary><
%strong> Content warning: #{Formatter.instance.format_spoiler(status.proper)}
= Formatter.instance.format(status.proper, custom_emojify: true)
%strong> Content warning: #{prerender_custom_emojis(h(status.proper.spoiler_text), status.proper.emojis)}
= prerender_custom_emojis(status_content_format(status.proper), status.proper.emojis)
- unless status.proper.ordered_media_attachments.empty?
- if status.proper.ordered_media_attachments.first.video?

View File

@ -23,7 +23,7 @@
= fa_icon('lock') if @report.target_account.locked?
- if @report.target_account.note.present?
.account-card__bio.emojify
= Formatter.instance.simplified_format(@report.target_account, custom_emojify: true)
= prerender_custom_emojis(account_bio_format(@report.target_account), @report.target_account.emojis)
.account-card__actions
.account-card__counters
.account-card__counters__item

View File

@ -8,7 +8,7 @@
.dashboard
.dashboard__item
= react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure')
= react_admin_component :counter, measure: 'tag_accounts', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_accounts_measure'), href: tag_url(@tag), target: '_blank'
.dashboard__item
= react_admin_component :counter, measure: 'tag_uses', start_at: @time_period.first, end_at: @time_period.last, params: { id: @tag.id }, label: t('admin.trends.tags.dashboard.tag_uses_measure')
.dashboard__item

View File

@ -34,7 +34,7 @@
= fa_icon('lock') if account.locked?
- if account.note.present?
.account-card__bio.emojify
= Formatter.instance.simplified_format(account, custom_emojify: true)
= prerender_custom_emojis(account_bio_format(account), account.emojis)
- else
.flex-spacer
.account-card__actions

View File

@ -26,7 +26,7 @@
%p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain
- unless @strike.text.blank?
= Formatter.instance.linkify(@strike.text)
= linkify(@strike.text)
- if @strike.report && !@strike.report.other?
%p

View File

@ -28,10 +28,10 @@
- if status.spoiler_text?
%div.auto-dir
%p
= Formatter.instance.format_spoiler(status)
= status.spoiler_text
%div.auto-dir
= Formatter.instance.format(status)
= status_content_format(status)
- if status.ordered_media_attachments.size > 0
%p

View File

@ -3,6 +3,6 @@
> ----
>
<% end %>
> <%= raw word_wrap(Formatter.instance.plaintext(status), break_sequence: "\n> ") %>
> <%= raw word_wrap(extract_status_plain_text(status), break_sequence: "\n> ") %>
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>

View File

@ -5,7 +5,7 @@
* <%= raw t('notification_mailer.digest.mention', name: notification.from_account.pretty_acct) %>
<%= raw Formatter.instance.plaintext(notification.target_status) %>
<%= raw extract_status_plain_text(notification.target_status) %>
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{notification.target_status.id}") %>
<% end %>

View File

@ -18,10 +18,11 @@
.status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text?
%p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: prefers_autoplay?)}&nbsp;
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more')
.e-content
= Formatter.instance.format(status, custom_emojify: true, autoplay: prefers_autoplay?)
= prerender_custom_emojis(status_content_format(status), status.emojis)
- if status.preloadable_poll
= render_poll_component(status)

View File

@ -12,7 +12,7 @@
%span.poll__number><
= "#{percent.round}%"
%span.poll__option__text
= Formatter.instance.format_poll_option(status, option, autoplay: prefers_autoplay?)
= prerender_custom_emojis(h(option.title), status.emojis)
- if own_votes.include?(index)
%span.poll__voted
%i.poll__voted__mark.fa.fa-check
@ -23,7 +23,7 @@
%label.poll__option><
%span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><
%span.poll__option__text
= Formatter.instance.format_poll_option(status, option, autoplay: prefers_autoplay?)
= prerender_custom_emojis(h(option.title), status.emojis)
.poll__footer
- unless show_results
%button.button.button-secondary{ disabled: true }

View File

@ -30,10 +30,11 @@
.status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text?
%p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: prefers_autoplay?)}&nbsp;
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more')
.e-content<
= Formatter.instance.format(status, custom_emojify: true, autoplay: prefers_autoplay?)
= prerender_custom_emojis(status_content_format(status), status.emojis)
- if status.preloadable_poll
= render_poll_component(status)

View File

@ -40,7 +40,7 @@
%p= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance
- unless @warning.text.blank?
= Formatter.instance.linkify(@warning.text)
= linkify(@warning.text)
- if @warning.report && !@warning.report.other?
%p

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
lock '3.16.0'
lock '3.17.0'
set :repo_url, ENV.fetch('REPO', 'https://github.com/mastodon/mastodon.git')
set :branch, ENV.fetch('BRANCH', 'master')

View File

@ -91,11 +91,13 @@ Rails.application.configure do
# E-mails
outgoing_email_address = ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost')
outgoing_mail_domain = Mail::Address.new(outgoing_email_address).domain
outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain
config.action_mailer.default_options = {
from: outgoing_email_address,
reply_to: ENV['SMTP_REPLY_TO'],
'Message-ID': -> { "<#{Mail.random_tag}@#{outgoing_mail_domain}>" },
return_path: ENV['SMTP_RETURN_PATH'],
message_id: -> { "<#{Mail.random_tag}@#{outgoing_email_domain}>" },
}
config.action_mailer.smtp_settings = {

View File

@ -78,30 +78,4 @@ module Twitter::TwitterText
)
}iox
end
module Extractor
# Extracts a list of all XMPP and magnet URIs included in the Toot <tt>text</tt> along
# with the indices. If the <tt>text</tt> is <tt>nil</tt> or contains no
# 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_extra_uris_with_indices(text, _options = {}) # :yields: uri, start, end
return [] unless text && text.index(":")
urls = []
text.to_s.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
valid_uri_match_data = $~
start_position = valid_uri_match_data.char_begin(3)
end_position = valid_uri_match_data.char_end(3)
urls << {
:url => valid_uri_match_data[3],
:indices => [start_position, end_position]
}
end
urls.each{|url| yield url[:url], url[:indices].first, url[:indices].last} if block_given?
urls
end
end
end

View File

@ -9,7 +9,7 @@ ku:
agreement: Peymana karûbarê
email: Navnîşana E-nameyê
locale: Herêmî
password: Pêborîn
password: Borînpeyv
user/account:
username: Navê bikarhêneriyê
user/invite_request:

View File

@ -24,7 +24,7 @@ nl:
status:
attributes:
reblog:
taken: van toot bestaat al
taken: van bericht bestaat al
user:
attributes:
email:

View File

@ -474,9 +474,6 @@ ar:
delivery_error_days: أيام أخطاء التوصيل
delivery_error_hint: إذا كان التوصيل غير ممكناً لـ%{count} يوم، فستوضع عليها علامة {غير قابلة للتسليم} تلقائياً.
empty: لم يتم العثور على نطاقات.
known_accounts:
one: "%{count} حساب معروف"
other: "%{count} حسابات معروفة"
moderation:
all: كافتها
limited: محدود

View File

@ -168,7 +168,6 @@ ca:
previous_strikes_description_html:
one: Aquest compte té <strong>una</strong> acció.
other: Aquest compte té <strong>%{count}</strong> accions.
zero: Aquest compte està <strong>al dia</strong>.
promote: Promociona
protocol: Protocol
public: Públic
@ -490,6 +489,7 @@ ca:
other: Intents fallits en %{count} diferents dies.
no_failures_recorded: Sense errors registrats.
title: Disponibilitat
warning: El darrer intent de connectar a aquest servidor no ha tingut èxit
back_to_all: Totes
back_to_limited: Limitades
back_to_warning: Avís
@ -529,7 +529,6 @@ ca:
known_accounts:
one: "%{count} compte conegut"
other: "%{count} comptes coneguts"
zero: Cap compte conegut
moderation:
all: Totes
limited: Limitades
@ -774,6 +773,11 @@ ca:
system_checks:
database_schema_check:
message_html: Hi ha pendents migracions de la base de dades. Si us plau executa-les per a assegurar que l'aplicació es comporta com s'espera
elasticsearch_running_check:
message_html: No s'ha pogut connectar a Elasticsearch. Si us plau verifica que estigui funcionant o desactiva la cerca de text complet
elasticsearch_version_check:
message_html: 'Versió incompatible de Elasticsearch: %{value}'
version_comparison: Elasticsearch %{running_version} està funcionant mentre %{required_version} és requerida
rules_check:
action: Gestiona les normes del servidor
message_html: No has definit cap norma del servidor.
@ -794,9 +798,8 @@ ca:
disallow: No permetre l'enllaç
disallow_provider: No permetre el mitjà
shared_by_over_week:
one: Compartit per un usuari en la darrera setmana
other: Compartit per %{count} usuaris en la darrera setmana
zero: Compartit per ningú en la darrera setmana
one: Compartit per una persona en la darrera setmana
other: Compartit per %{count} persones en la darrera setmana
title: Enllaços en tendència
usage_comparison: Compartit %{today} vegades avui, comparat amb %{yesterday} d'ahir
pending_review: Revisió pendent
@ -837,9 +840,8 @@ ca:
usable: Pot ser emprat
usage_comparison: Usat %{today} vegades avui, comparat amb %{yesterday} d'ahir
used_by_over_week:
one: Emprat per un usuari en la darrera setmana
other: Emprat per %{count} usuaris en la darrera setmana
zero: Emprat per ningú en la darrera setmana
one: Emprat per una persona en la darrera setmana
other: Emprat per %{count} persones en la darrera setmana
title: Tendència
warning_presets:
add_new: Afegir-ne un de nou

View File

@ -168,6 +168,11 @@ cs:
not_subscribed: Neodebírá
pending: Čeká na posouzení
perform_full_suspension: Pozastavit
previous_strikes_description_html:
few: Tento účet má <strong>%{count}</strong> strajky.
many: Tento účet má <strong>%{count}</strong> strajků.
one: Tento účet má <strong>jeden</strong> strajk.
other: Tento účet má <strong>%{count}</strong> strajků.
promote: Povýšit
protocol: Protokol
public: Veřejný
@ -366,6 +371,7 @@ cs:
enable: Povolit
enabled: Povoleno
enabled_msg: Emoji bylo úspěšně povoleno
image_hint: PNG nebo GIF do %{size}
list: Uvést
listed: Uvedeno
new:
@ -446,6 +452,8 @@ cs:
title: Doporučená sledování
unsuppress: Obnovit doporučení sledování
instances:
availability:
warning: Poslední pokus o připojení k tomuto serveru byl neúspěšný
back_to_all: Vše
back_to_limited: Omezený
back_to_warning: Varování
@ -527,12 +535,15 @@ cs:
one: "%{count} poznámka"
other: "%{count} poznámek"
action_taken_by: Akci vykonal uživatel
are_you_sure: Opravu?
are_you_sure: Jste si jisti?
assign_to_self: Přidělit ke mně
assigned: Přiřazený moderátor
by_target_domain: Doména nahlášeného účtu
category: Kategorie
category_description_html: Důvod nahlášení tohoto účtu a/nebo obsahu bude uveden v komunikaci s nahlášeným účtem
comment:
none: Žádné
comment_description_html: 'Pro upřesnění uživatel %{name} napsal:'
created_at: Nahlášené
forwarded: Přeposláno
forwarded_to: Přeposláno na %{domain}
@ -544,13 +555,19 @@ cs:
create_and_unresolve: Znovu otevřít s poznámkou
delete: Smazat
placeholder: Popište, jaké akce byly vykonány, nebo jakékoliv jiné související aktuality…
title: Poznámky
notes_description_html: Zobrazit a zanechat poznámky pro ostatní moderátory i sebe v budoucnu
remote_user_placeholder: vzdálený uživatel z %{instance}
reopen: Znovu otevřít hlášení
report: 'Nahlásit #%{id}'
reported_account: Nahlášený účet
reported_by: Nahlášeno uživatelem
resolved: Vyřešeno
resolved_msg: Hlášení úspěšně vyřešeno!
skip_to_actions: Přeskočit k akcím
status: Stav
statuses: Nahlášený obsah
statuses_description_html: Obsah porušující pravidla bude uveden v komunikaci s nahlášeným účtem
target_origin: Původ nahlášeného účtu
title: Hlášení
unassign: Odebrat
@ -666,6 +683,11 @@ cs:
system_checks:
database_schema_check:
message_html: Na spuštění čekají databázové migrace. Nechte je prosím proběhnout pro zajištění očekávaného chování aplikace
elasticsearch_running_check:
message_html: Nelze se připojit k Elasticsearch. Prosím zkontrolujte, že běží, nebo vypněte fulltextové vyhledávání
elasticsearch_version_check:
message_html: 'Nekompatibilní verze Elasticsearch: %{value}'
version_comparison: Je spuštěn Elasticsearch %{running_version} místo vyžadovaného %{required_version}
rules_check:
action: Spravovat pravidla serveru
message_html: Nedefinovali jste žádná pravidla serveru.
@ -1041,6 +1063,9 @@ cs:
carry_mutes_over_text: Tento účet se přesunul z %{acct}, který jste skryli.
copy_account_note_text: 'Tento účet se přesunul z %{acct}, zde byly Vaše předchozí poznámky o něm:'
notification_mailer:
admin:
sign_up:
subject: Uživatel %{name} se zaregistroval
digest:
action: Zobrazit všechna oznámení
body: Zde najdete stručný souhrn zpráv, které jste zmeškali od vaší poslední návštěvy %{since}
@ -1082,6 +1107,8 @@ cs:
title: Nový boost
status:
subject: Nový příspěvek od %{name}
update:
subject: Uživatel %{name} upravil příspěvek
notifications:
email_events: Události pro e-mailová oznámení
email_events_hint: 'Vyberte události, pro které chcete dostávat oznámení:'
@ -1163,6 +1190,9 @@ cs:
reply:
proceed: Pokračovat k odpovědi
prompt: 'Chcete odpovědět na tento příspěvek:'
reports:
errors:
invalid_rules: neodkazuje na platná pravidla
scheduled_statuses:
over_daily_limit: Překročili jste limit %{limit} příspěvků naplánovaných na tento den
over_total_limit: Překročili jste limit %{limit} naplánovaných příspěvků
@ -1251,6 +1281,7 @@ cs:
other: "%{count} videí"
boosted_from_html: Boostnuto z %{acct_link}
content_warning: 'Varování o obsahu: %{warning}'
default_language: Stejný jako jazyk rozhraní
disallowed_hashtags:
few: 'obsahoval nepovolené hashtagy: %{tags}'
many: 'obsahoval nepovolené hashtagy: %{tags}'
@ -1450,6 +1481,8 @@ cs:
subject: Potvrďte prosím pokus o přihlášení
title: Pokus o přihlášení
warning:
explanation:
delete_statuses: Bylo shledáno, že některé vaše příspěvky porušují jednu nebo více zásad komunity a následně byly odstraněny moderátory %{instance}.
subject:
disable: Váš účet %{acct} byl zmrazen
none: Varování pro %{acct}

View File

@ -166,9 +166,8 @@ da:
perform_full_suspension: Suspendér
previous_strikes: Tidligere anmeldelser (strikes)
previous_strikes_description_html:
one: Denne konto har <strong>et</strong> anmeldelse.
one: Denne konto har <strong>en</strong> anmeldelse.
other: Denne konto har <strong>%{count}</strong> anmeldelser.
zero: Denne konto er <strong>på god fod</strong>.
promote: Forfrem
protocol: Protokol
public: Offentlig
@ -490,6 +489,7 @@ da:
other: Mislykkede forsøg på %{count} forskellige dage.
no_failures_recorded: Ingen fejl noteret.
title: Tilgængelighed
warning: Seneste forsøg på at oprette forbindelse til denne server mislykkedes
back_to_all: Alle
back_to_limited: Begrænset
back_to_warning: Advarsel
@ -529,7 +529,6 @@ da:
known_accounts:
one: "%{count} kendt konto"
other: "%{count} kendte konti"
zero: Ingen kendt konto
moderation:
all: Alle
limited: Begrænset
@ -774,6 +773,11 @@ da:
system_checks:
database_schema_check:
message_html: Databasemigreringer afventer. Kør dem for at sikre den forventede adfærd fra applikationen
elasticsearch_running_check:
message_html: Kunne ikke oprette forbindelse til Elasticsearch. Tjek, at den kører, eller deaktivér fuldtekstsøgning
elasticsearch_version_check:
message_html: 'Inkompatibel Elasticsearch-version: %{value}'
version_comparison: Elasticsearch %{running_version} kører, men %{required_version} kræves
rules_check:
action: Håndtér serverregler
message_html: Ingen serverregler defineret.
@ -794,9 +798,8 @@ da:
disallow: Tillad ikke link
disallow_provider: Tillad ikke udgiver
shared_by_over_week:
one: Delt af én person i løbet af den seneste uge
other: Delt af %{count} personer i løbet af den seneste uge
zero: Ikke delt af nogen i løbet af den seneste uge
one: Delt af én person den seneste uge
other: Delt af %{count} personer den seneste uge
title: Populære links
usage_comparison: Delt %{today} gange i dag, sammenlignet med %{yesterday} i går
pending_review: Afventer revision
@ -837,9 +840,8 @@ da:
usable: Kan anvendes
usage_comparison: Anvendt %{today} gange i dag, sammenlignet med %{yesterday} i går
used_by_over_week:
one: Anvendt af én person i løbet af den seneste uge
other: Anvendt af %{count} personer i løbet af den seneste uge
zero: Ikke anvendt af nogen i løbet af den seneste uge
one: Brugt af én person den seneste uge
other: Brugt af %{count} personer den seneste uge
title: Trends
warning_presets:
add_new: Tilføj ny

View File

@ -165,10 +165,6 @@ de:
pending: In Warteschlange
perform_full_suspension: Verbannen
previous_strikes: Vorherige Strikes
previous_strikes_description_html:
one: Dieses Konto hat <strong>einen</strong> Strike.
other: Dieses Konto hat <strong>%{count}</strong> Strikes.
zero: Dieses Konto ist <strong>in gutem Stand</strong>.
promote: Befördern
protocol: Protokoll
public: Öffentlich
@ -373,6 +369,7 @@ de:
enable: Aktivieren
enabled: Aktiviert
enabled_msg: Das Emoji wurde aktiviert
image_hint: PNG oder GIF bis %{size}
list: Liste
listed: Gelistet
new:
@ -489,6 +486,7 @@ de:
other: Fehlgeschlagener Versuch am %{count}. Tag.
no_failures_recorded: Keine Fehler bei der Aufzeichnung.
title: Verfügbarkeit
warning: Der letzte Versuch, sich mit diesem Server zu verbinden, war nicht erfolgreich
back_to_all: Alle
back_to_limited: Beschränkt
back_to_warning: Warnung
@ -525,10 +523,6 @@ de:
delivery_error_hint: Wenn eine Lieferung für %{count} Tage nicht möglich ist, wird sie automatisch als nicht lieferbar markiert.
destroyed_msg: Daten von %{domain} sind nun in der Warteschlange für die bevorstehende Löschung.
empty: Keine Domains gefunden.
known_accounts:
one: "%{count} bekanntes Konto"
other: "%{count} bekannte Konten"
zero: Kein bekanntes Konto
moderation:
all: Alle
limited: Beschränkt
@ -792,10 +786,6 @@ de:
description_html: Dies sind Links, die derzeit von Konten geteilt werden, von denen dein Server Beiträge sieht. Es kann deinen Benutzern helfen, herauszufinden, was in der Welt vor sich geht. Es werden keine Links öffentlich angezeigt, bis du den Publisher genehmigst. Du kannst auch einzelne Links zulassen oder ablehnen.
disallow: Verbiete Link
disallow_provider: Verbiete Herausgeber
shared_by_over_week:
one: In der letzten Woche geteilt von einer Person
other: In der letzten Woche geteilt von %{count} Personen
zero: Geteilt von niemandem in der letzten Woche
title: Angesagte Links
usage_comparison: Heute %{today} mal geteilt, gestern %{yesterday} mal
pending_review: Überprüfung ausstehend
@ -835,10 +825,6 @@ de:
trending_rank: 'Trend #%{rank}'
usable: Kann verwendet werden
usage_comparison: Heute %{today} mal genutzt, gestern %{yesterday} mal
used_by_over_week:
one: In der letzten Woche genutzt von einer Person
other: In der letzten Woche genutzt von %{count} Personen
zero: Genutzt von niemandem in der letzten Woche
title: Trends
warning_presets:
add_new: Neu hinzufügen
@ -1436,6 +1422,7 @@ de:
disallowed_hashtags:
one: 'enthält einen verbotenen Hashtag: %{tags}'
other: 'enthält verbotene Hashtags: %{tags}'
edited_at_html: Bearbeitet %{date}
errors:
in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren.
open_in_web: Im Web öffnen

View File

@ -8,10 +8,10 @@ ku:
failure:
already_authenticated: Jixwe te berê têketin kiriye.
inactive: Ajimêra te hîn nehatiye çalakkirin.
invalid: Nederbasdar %{authentication_keys} an jî şîfre.
invalid: "%{authentication_keys} an jî borînpeyv nederbasdar e."
last_attempt: Peşiya kilît kirina ajimêra te carek din jî biceribîne.
locked: Ajimêra ye hat kilît kirin.
not_found_in_database: Nederbasdar %{authentication_keys} an jî şîfre.
not_found_in_database: "%{authentication_keys} an jî borînpeyv nederbasdar e."
pending: Ajimêra te hîn tê vekolandin.
timeout: Danişîna te qedîya. Ji kerema xwe ji bo berdewamiyê dîsa têkeve.
unauthenticated: Peşiya berdewamiya te têketina xwe bike an jî xwe tomar bike.
@ -33,10 +33,10 @@ ku:
subject: 'Mastodon: E-name hate guhertin'
title: Navnîşana e-nameya nû
password_change:
explanation: Pêborîna ajimêra te hate guhertin.
explanation: Borînpeyva ajimêra te hate guhertin.
extra: Heke te ajimêra xwe ne guhertiye. Ew tê wateya ku kesek ketiye ajimêrê te. Jkx pêborîna xwe zû biguherîne an jî bi rêveberiya rajekar re têkeve têkiliyê heke tu êdî nikare ajimêra xwe bi kar bînî.
subject: 'Mastodon: pêborîn hate guhertin'
title: Pêborîn hate guhertin
title: Borînpeyv hate guhertin
reconfirmation_instructions:
explanation: Navnîşana nû piştrast bike da ku tu e-nameya xwe biguherînî.
extra: |-
@ -45,13 +45,13 @@ ku:
subject: 'Mastodon: E-nameyê piştrast bike bo %{instance}'
title: Navnîşana e-nameyê piştrast bike
reset_password_instructions:
action: Pêborînê biguherîne
action: Borînpeyvê biguherîne
explanation: Te ji bo ajimêra xwe daxwaza pêborîneke nû kiriye.
extra: Heke te ev daxwaz nekir, jkx guh nede vê e-nameyê. Pêborîna te wê neyê guhertin heya ku tu li girêdana Jêrin bitikînî û yeka nû çê bikî.
extra: Heke te ev daxwaz nekir, jkx guh nede vê e-nameyê. Borînpeyva te wê neyê guhertin heya ku tu li girêdana Jêrin bitikînî û yeka nû çê bikî.
subject: 'Mastodon: rêwerzên jê birina pêborîn'
title: Pêborîn ji nû ve saz bike
title: Borînpeyv ji nû ve saz bike
two_factor_disabled:
explanation: Ji bo ajimêrê te piştrastkirina du-faktorî hat asteng kirin. Niha tu tenê bi navnîşana e-name û şîfre ya xwe dikarî têketin bikî.
explanation: Ji bo ajimêrê te piştrastkirina du-faktorî hat asteng kirin. Niha tu tenê bi navnîşana e-name û borînpeyva xwe dikarî têketinê bikî.
subject: 'Mastodon: piştrastkirina du- faktorî neçalak bike'
title: 2FA Neçalak e
two_factor_enabled:
@ -85,11 +85,11 @@ ku:
failure: Nikare ji %{kind} rastandinê bikê ji bo " %{reason}".
success: Ji ajimêra %{kind} bi serkeftî hate rastandin.
passwords:
no_token: Tu nikarî xwe bigihînî vê rûpelê bêyî ku tu ji e-nameya ji nû ve sazkirina borînê wernegerî. Heke tu ji e-nameya ji nû ve sazkirina borînê tê, ji kerema xwe pê ewle be ku tu girêdanê ya tevahî bi kar tînî.
send_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina borînê bistînî. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
send_paranoid_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina borînê bistînî di hundir çend xulkan de. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
updated: Pêborîna te bi serkeftî hate guhertin. Niha tu têketî ye.
updated_not_active: Pêborîna te bi serkeftî hate guhertin.
no_token: Tu nikarî xwe bigihînî vê rûpelê bêyî ku tu ji e-nameya ji nû ve sazkirina borînpeyvê wernegerî. Heke tu ji e-nameya ji nû ve sazkirina borînpeyvê tê, ji kerema xwe pê ewle be ku tu girêdanê ya tevahî bi kar tînî.
send_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina borînpeyvê bistînî. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
send_paranoid_instructions: Heke navnîşana te ya e-nameyê di danegeha me da hebê, tu yê di navnîşana xwe ya e-nameyê da girêdana rizgarkirina borînpeyvê bistînî di hundir çend xulkan de. Heke te ev e-name wernegirtibe, ji kerema xwe peldanka xwe ya spamê kontrol bike.
updated: Borînpeyva te bi serkeftî hate guhertin. Niha tu têketî ye.
updated_not_active: Borînpeyva te bi serkeftî hate guhertin.
registrations:
destroyed: Xatirê te! Ajimêra te bi serkeftî hate pûçkirin. Em hêvî dikin ku tu di nêzîk de te dîsa bibînin.
signed_up: Bi xêr hatî! Te bi serkeftî tomarkirin kir.

View File

@ -12,7 +12,7 @@ th:
last_attempt: คุณลองได้อีกหนึ่งครั้งก่อนที่บัญชีของคุณจะถูกล็อค
locked: บัญชีของคุณถูกล็อค
not_found_in_database: "%{authentication_keys} หรือรหัสผ่านไม่ถูกต้อง"
pending: บัญชีของคุณยังอยู่ระหว่างการตรวจทาน
pending: บัญชีของคุณยังคงอยู่ระหว่างการตรวจทาน
timeout: เซสชันของคุณหมดอายุแล้ว โปรดลงชื่อเข้าอีกครั้งเพื่อดำเนินการต่อ
unauthenticated: คุณจำเป็นต้องลงชื่อเข้าหรือลงทะเบียนก่อนดำเนินการต่อ
unconfirmed: คุณต้องยืนยันที่อยู่อีเมลของคุณก่อนดำเนินการต่อ

View File

@ -29,7 +29,7 @@ fa:
edit:
title: ویرایش برنامه
form:
error: اوخ! ببینید چیزی را اشتباهی در فرم وارد نکرده‌اید؟
error: اوخ! ببینید چیزی را اشتباهی در فرم وارد نکرده‌اید
help:
native_redirect_uri: برای آزمایش‌های محلی %{native_redirect_uri} را به کار ببرید
redirect_uri: هر URI را در یک سطر جدا بنویسید
@ -60,6 +60,7 @@ fa:
error:
title: خطایی رخ داد
new:
review_permissions: بازبینی اجازه‌ها
title: نیاز به اجازه دادن
show:
title: این کد مجوز را کپی کرده و در برنامه وارد کنید.
@ -69,6 +70,7 @@ fa:
confirmations:
revoke: آیا مطمئن هستید؟
index:
scopes: اجازه‌ها
title: برنامه‌های مجاز
errors:
messages:
@ -104,6 +106,29 @@ fa:
authorized_applications:
destroy:
notice: برنامه فسخ شد.
grouped_scopes:
access:
read: فقط دسترسی خواندن
read/write: دسترسی خواندن و نوشتن
write: فقط دسترسی نوشتن
title:
accounts: حساب‌ها
all: همه چیز
blocks: مسدودها
bookmarks: نشانک‌ها
conversations: گفت‌وگوها
crypto: رمزگذاری سرتاسری
favourites: پسندیده‌ها
filters: پالایه‌ها
follows: پی‌گرفتگان
lists: سیاهه‌ها
media: پیوست‌های رسانه‌ای
mutes: خموش‌ها
notifications: آگاهی‌ها
push: آگاهی‌های ارسالی
reports: گزارش‌ها
search: جست‌وجو
statuses: فرسته‌ها
layouts:
admin:
nav:
@ -118,11 +143,12 @@ fa:
admin:write: تغییر تمام داده‌ها روی کارساز
admin:write:accounts: انجام کنش مدیریتی روی حساب‌ها
admin:write:reports: انجام کنش مدیریتی روی گزارش‌ها
crypto: از رمزگذاری سرتاسر استفاده کنید
follow: پیگیری، مسدودسازی، لغو مسدودسازی، و لغو پیگیری حساب‌ها
push: دریافت آگاهی‌ای ارسالیتان
read: خواندن اطلاعات حساب شما
read:accounts: دیدن اطّلاعات حساب
read:blocks: دیدن انسدادهایتان
read:blocks: دیدن مسدودهایتان
read:bookmarks: دیدن نشانک‌هایتان
read:favourites: دیدن برگزیده‌هایتان
read:filters: دیدن پالایه‌هایتان
@ -137,6 +163,7 @@ fa:
write:accounts: تغییر نمایه‌تان
write:blocks: انسداد حساب‌ها و دامنه‌ها
write:bookmarks: نشانک‌گذاری وضعیت‌ها
write:conversations: مکالمات را بی‌صدا و حذف کنید
write:favourites: برگزیدن وضعیت‌ها
write:filters: ایحاد پالایش‌ها
write:follows: پی‌گیری افراد

View File

@ -60,6 +60,8 @@ gd:
error:
title: Thachair mearachd
new:
prompt_html: Bu mhiann le %{client_name} cead gus an cunntas agad inntrigeadh. Seo aplacaid threas-phàrtaidh. <strong>Mur eil earbsa agad ann, na ùghdarraich e.</strong>
review_permissions: Thoir sùil air na ceadan
title: Tha feum air ùghdarrachadh
show:
title: Dèan lethbhreac dhen chòd ùghdarrachaidh seo s cuir san aplacaid e.
@ -69,6 +71,12 @@ gd:
confirmations:
revoke: A bheil thu cinnteach?
index:
authorized_at: Air ùghdarrachadh %{date}
description_html: Seo na h-aplacaidean as urrainn dhaibh an cunntas agad inntrigeadh leis an API. Ma tha aplacaid an-seo nach aithne dhut no ma tha droch-ghiùlan air aplacaid, s urrainn dhut an t-inntrigeadh aice a chùl-ghairm.
last_used_at: Air a chleachdadh %{date} an turas mu dheireadh
never_used: Cha deach a chleachdadh a-riamh
scopes: Ceadan
superapp: Inntearnail
title: Na h-aplacaidean ùghdarraichte agad
errors:
messages:
@ -104,6 +112,33 @@ gd:
authorized_applications:
destroy:
notice: Chaidh an t-iarrtas a chùl-ghairm.
grouped_scopes:
access:
read: Inntrigeadh leughaidh a-mhàin
read/write: Inntrigeadh leughaidh is sgrìobhaidh
write: Inntrigeadh sgrìobhaidh a-mhàin
title:
accounts: Cunntasan
admin/accounts: Rianachd nan cunntas
admin/all: Gach gleus na rianachd
admin/reports: Rianachd nan gearan
all: A h-uile rud
blocks: Bacaidhean
bookmarks: Comharran-lìn
conversations: Còmhraidhean
crypto: Crioptachadh o cheann gu ceann
favourites: Annsachdan
filters: Criathragan
follow: Dàimhean
follows: Leantainn
lists: Liostaichean
media: Ceanglachain mheadhanan
mutes: Mùchaidhean
notifications: Brathan
push: Brathan putaidh
reports: Gearanan
search: Lorg
statuses: Postaichean
layouts:
admin:
nav:
@ -118,6 +153,7 @@ gd:
admin:write: dàta sam bith atharrachadh air an fhrithealaiche
admin:write:accounts: gnìomhan na maorsainneachd a ghabhail air cunntasan
admin:write:reports: gnìomhan na maorsainneachd a ghabhail air gearanan
crypto: crioptachadh o cheann gu ceann a chleachdadh
follow: dàimhean chunntasan atharrachadh
push: na brathan putaidh agad fhaighinn
read: dàta sam bith a cunntais agad a leughadh
@ -137,6 +173,7 @@ gd:
write:accounts: a phròifil agad atharrachadh
write:blocks: cunntasan is àrainnean a bhacadh
write:bookmarks: comharran-lìn a dhèanamh de phostaichean
write:conversations: còmhraidhean a mhùchadh is a sguabadh às
write:favourites: postaichean a chur ris na h-annsachdan
write:filters: criathragan a chruthachadh
write:follows: leantainn air daoine

View File

@ -73,6 +73,10 @@ id:
index:
authorized_at: Diberi hak otorisasi pada %{date}
description_html: Ini adalah aplikasi yang dapat mengakses akun Anda menggunakan API. Jika ada aplikasi yang tidak Anda kenal di sini, atau aplikasi yang berperilaku aneh, Anda dapat mencabut hak aksesnya.
last_used_at: Terakhir dipakai pada %{date}
never_used: Tidak pernah dipakai
scopes: Hak akses
superapp: Internal
title: Aplikasi yang anda izinkan
errors:
messages:
@ -108,6 +112,33 @@ id:
authorized_applications:
destroy:
notice: Aplikasi dicabut.
grouped_scopes:
access:
read: Akses baca-saja
read/write: Akses baca dan tulis
write: Akses tulis-saja
title:
accounts: Akun
admin/accounts: Administrasi akun
admin/all: Semua fungsi administratif
admin/reports: Administrasi laporan
all: Segalanya
blocks: Blokir
bookmarks: Markah
conversations: Percakapan
crypto: Enkripsi end-to-end
favourites: Favorit
filters: Saringan
follow: Hubungan
follows: Mengikuti
lists: Daftar
media: Lampiran media
mutes: Bisukan
notifications: Notifikasi
push: Notifikasi dorong
reports: Laporan
search: Pencarian
statuses: Kiriman
layouts:
admin:
nav:
@ -122,6 +153,7 @@ id:
admin:write: ubah semua data di server
admin:write:accounts: lakukan aksi moderasi akun
admin:write:reports: lakukan aksi moderasi laporan
crypto: menggunakan enkripsi end-to-end
follow: mengikuti, blokir, menghapus blokir, dan berhenti mengikuti akun
push: terima notifikasi dorong
read: membaca data pada akun anda
@ -141,6 +173,7 @@ id:
write:accounts: ubah profil Anda
write:blocks: blokir akun dan domain
write:bookmarks: status markah
write:conversations: bisukan dan hapus percakapan
write:favourites: status favorit
write:filters: buat saringan
write:follows: ikuti orang

View File

@ -31,7 +31,7 @@ ku:
form:
error: Wey li min! kontrol bikeku form çewtî tê de tune
help:
native_redirect_uri: Bo testên herêmî %{native_redirect_uri} bikar bîne
native_redirect_uri: Bo testên herêmî %{native_redirect_uri} bi kar bîne
redirect_uri: Serê URl de rêzek bikarbînin
scopes: Berfirehî bi valahîyan re veqetîne. Bo bikaranîna berfirehî ya standard vala bihêle.
index:
@ -81,7 +81,7 @@ ku:
errors:
messages:
access_denied: Xwedîyê çavkanîyê an jî destûrmendê rajeker daxwazî red kirin.
credential_flow_not_configured: Herikîna borînê bawername ya xwediyê çavkaniyê, ji ber Doorkeeper.configure.resource_owner_from_credentials nehat pevsazkirin.
credential_flow_not_configured: Herikîna borînpeyvê bawername ya xwediyê çavkaniyê, ji ber Doorkeeper.configure.resource_owner_from_credentials nehat pevsazkirin.
invalid_client: Erêkirina nasnameyê rajegir ji ber rajegirê nediyar têk çû, erêkirina nasnameyê rajegir di nav da tinne an jî rêbaza erêkirinê ne piştgirêdayî ye.
invalid_grant: Mafê ku hatiye peyda kirin ne derbasdar e, qediya ye, pûç e, girêdana ya ku di daxwaza mafê de tê bikaranîn li hev nagire an jî rajegirekî din hildaye.
invalid_redirect_uri: Girêdan beralîkirî ya di nav da ne derbasdar e.
@ -130,7 +130,7 @@ ku:
favourites: Bijarte
filters: Parzûn
follow: Pêwendî
follows: Şopîner
follows: Dişopîne
lists: Rêzok
media: Pêvekên medya
mutes: Bêdengkirin
@ -162,7 +162,7 @@ ku:
read:bookmarks: şûnpelên xwe bibîne
read:favourites: bijarteyên xwe bibîne
read:filters: parzûnûn xwe bibîne
read:follows: şopînerên xwe bibîne
read:follows: ên tu dişopînî bibîne
read:lists: rêzoka xwe bibîne
read:mutes: ajimêrên bêdeng kirî bibîne
read:notifications: agahdariyên xwe bibîne

View File

@ -60,6 +60,8 @@ nl:
error:
title: Er is een fout opgetreden
new:
prompt_html: "%{client_name} heeft toestemming nodig om toegang te krijgen tot jouw account. Het betreft een third-party-toepassing.<strong>Als je dit niet vertrouwt, moet je geen toestemming verlenen.</strong>"
review_permissions: Toestemmingen beoordelen
title: Autorisatie vereist
show:
title: Kopieer deze autorisatiecode en plak het in de toepassing.
@ -69,6 +71,11 @@ nl:
confirmations:
revoke: Weet je het zeker?
index:
authorized_at: Toestemming verleent op %{date}
last_used_at: Voor het laatst gebruikt op %{date}
never_used: Nooit gebruikt
scopes: Toestemmingen
superapp: Intern
title: Jouw geautoriseerde toepassingen
errors:
messages:
@ -104,6 +111,33 @@ nl:
authorized_applications:
destroy:
notice: Toepassing ingetrokken.
grouped_scopes:
access:
read: Alleen leestoegang
read/write: Lees- en schrijftoegang
write: Alleen schrijftoegang
title:
accounts: Accounts
admin/accounts: Accountbeheer
admin/all: Alle beheerfuncties
admin/reports: Rapportagebeheer
all: Alles
blocks: Blokkeren
bookmarks: Bladwijzers
conversations: Gesprekken
crypto: End-to-end-encryptie
favourites: Favorieten
filters: Filters
follow: Relaties
follows: Volgend
lists: Lijsten
media: Mediabijlagen
mutes: Negeren
notifications: Meldingen
push: Pushmeldingen
reports: Rapportages
search: Zoeken
statuses: Berichten
layouts:
admin:
nav:
@ -118,6 +152,7 @@ nl:
admin:write: wijzig alle gegevens op de server
admin:write:accounts: moderatieacties op accounts uitvoeren
admin:write:reports: moderatieacties op rapportages uitvoeren
crypto: end-to-end-encryptie gebruiken
follow: relaties tussen accounts bewerken
push: jouw pushmeldingen ontvangen
read: alle gegevens van jouw account lezen
@ -130,14 +165,15 @@ nl:
read:lists: jouw lijsten bekijken
read:mutes: jouw genegeerde gebruikers bekijken
read:notifications: jouw meldingen bekijken
read:reports: jouw gerapporteerde toots bekijken
read:reports: jouw gerapporteerde berichten bekijken
read:search: namens jou zoeken
read:statuses: alle toots bekijken
read:statuses: alle berichten bekijken
write: alle gegevens van jouw account bewerken
write:accounts: jouw profiel bewerken
write:blocks: accounts en domeinen blokkeren
write:bookmarks: toots aan bladwijzers toevoegen
write:favourites: toots als favoriet markeren
write:bookmarks: berichten aan bladwijzers toevoegen
write:conversations: gespreken negeren en verwijderen
write:favourites: berichten als favoriet markeren
write:filters: filters aanmaken
write:follows: mensen volgen
write:lists: lijsten aanmaken
@ -145,4 +181,4 @@ nl:
write:mutes: mensen en gesprekken negeren
write:notifications: meldingen verwijderen
write:reports: andere mensen rapporteren
write:statuses: toots publiceren
write:statuses: berichten plaatsen

View File

@ -113,6 +113,7 @@ pt-BR:
accounts: Contas
all: Tudo
blocks: Blocos
bookmarks: Salvos
conversations: Conversas
crypto: Criptografia de ponta a ponta
favourites: Favoritos

View File

@ -60,6 +60,7 @@ uk:
error:
title: Сталася помилка
new:
review_permissions: Переглянути дозволи
title: Необхідна авторизація
show:
title: Скопіюйте цей код авторизації та вставте його у додаток.
@ -69,8 +70,11 @@ uk:
confirmations:
revoke: Ви впевнені?
index:
authorized_at: Авторизовано %{date}
last_used_at: Востаннє використано %{date}
never_used: Ніколи не використовувалися
scopes: Дозволи
superapp: Внутрішній
title: Ваші авторизовані додатки
errors:
messages:
@ -107,17 +111,27 @@ uk:
destroy:
notice: Авторизацію додатка відкликано.
grouped_scopes:
access:
read: Доступ без права внесення змін
read/write: Доступ до читання і запису
write: Доступ лише для запису
title:
accounts: Облікові записи
admin/accounts: Адміністрація облікових записів
admin/all: Усі адміністративні функції
admin/reports: Адміністрація звітів
all: Усе
blocks: Блокування
bookmarks: Закладки
conversations: Бесіди
crypto: Наскрізне шифрування
favourites: Вподобане
filters: Фільтри
follow: Взаємозв'язки
follows: Підписки
lists: Списки
media: Мультимедійні вкладення
mutes: Заглушені
notifications: Сповіщення
push: Push-сповіщення
reports: Скарги
@ -157,6 +171,7 @@ uk:
write:accounts: змінювати ваш профіль
write:blocks: блокувати облікові записи і домени
write:bookmarks: додавати пости в закладки
write:conversations: заглушити і видалити розмови
write:favourites: вподобані статуси
write:filters: створювати фільтри
write:follows: підписуйтесь на людей

View File

@ -168,7 +168,6 @@ en:
previous_strikes_description_html:
one: This account has <strong>one</strong> strike.
other: This account has <strong>%{count}</strong> strikes.
zero: This account is <strong>in good standing</strong>.
promote: Promote
protocol: Protocol
public: Public
@ -530,7 +529,6 @@ en:
known_accounts:
one: "%{count} known account"
other: "%{count} known accounts"
zero: No known account
moderation:
all: All
limited: Limited
@ -775,6 +773,11 @@ en:
system_checks:
database_schema_check:
message_html: There are pending database migrations. Please run them to ensure the application behaves as expected
elasticsearch_running_check:
message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search
elasticsearch_version_check:
message_html: 'Incompatible Elasticsearch version: %{value}'
version_comparison: Elasticsearch %{running_version} is running while %{required_version} is required
rules_check:
action: Manage server rules
message_html: You haven't defined any server rules.
@ -797,7 +800,6 @@ en:
shared_by_over_week:
one: Shared by one person over the last week
other: Shared by %{count} people over the last week
zero: Shared by noone over the last week
title: Trending links
usage_comparison: Shared %{today} times today, compared to %{yesterday} yesterday
pending_review: Pending review
@ -840,7 +842,6 @@ en:
used_by_over_week:
one: Used by one person over the last week
other: Used by %{count} people over the last week
zero: Used by noone over the last week
title: Trends
warning_presets:
add_new: Add new

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