mirror of
https://framagit.org/tykayn/mastodon.git
synced 2023-08-25 08:33:12 +02:00
Merge remote-tracking branch 'glitch-soc/main'
This commit is contained in:
commit
034466d82c
@ -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:
|
||||
|
13
CHANGELOG.md
13
CHANGELOG.md
@ -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
|
||||
|
||||
|
2
Gemfile
2
Gemfile
@ -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'
|
||||
|
18
Gemfile.lock
18
Gemfile.lock
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
31
app/controllers/api/v2/admin/accounts_controller.rb
Normal file
31
app/controllers/api/v2/admin/accounts_controller.rb
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
28
app/helpers/formatting_helper.rb
Normal file
28
app/helpers/formatting_helper.rb
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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",
|
||||
|
@ -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": "نمایش کمتر",
|
||||
|
@ -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 l’auteur·rice",
|
||||
"keyboard_shortcuts.reply": "Répondre au message",
|
||||
|
@ -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",
|
||||
|
@ -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": "投稿",
|
||||
|
@ -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",
|
||||
|
@ -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 emoji’s",
|
||||
@ -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.",
|
||||
|
@ -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.",
|
||||
|
@ -18,7 +18,7 @@
|
||||
"account.followers": "Ndjekës",
|
||||
"account.followers.empty": "Këtë përdorues ende s’e 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 s’ndjek 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",
|
||||
|
@ -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": "ฉันไม่ชอบโพสต์",
|
||||
|
@ -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": "Розширений формат пошуку",
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -5,6 +5,7 @@ class Admin::SystemCheck
|
||||
Admin::SystemCheck::DatabaseSchemaCheck,
|
||||
Admin::SystemCheck::SidekiqProcessCheck,
|
||||
Admin::SystemCheck::RulesCheck,
|
||||
Admin::SystemCheck::ElasticsearchCheck,
|
||||
].freeze
|
||||
|
||||
def self.perform
|
||||
|
39
app/lib/admin/system_check/elasticsearch_check.rb
Normal file
39
app/lib/admin/system_check/elasticsearch_check.rb
Normal 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
|
131
app/lib/advanced_text_formatter.rb
Normal file
131
app/lib/advanced_text_formatter.rb
Normal 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
|
98
app/lib/emoji_formatter.rb
Normal file
98
app/lib/emoji_formatter.rb
Normal 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
|
@ -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 = $'
|
||||
|
||||
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)
|
||||
|
||||
possible_entries << {
|
||||
screen_name: screen_name,
|
||||
indices: [start_position, end_position],
|
||||
@ -29,18 +51,21 @@ 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
|
||||
start_position = match_data.char_begin(1) - 1
|
||||
end_position = match_data.char_end(1)
|
||||
after = $'
|
||||
|
||||
if %r{\A://}.match?(after)
|
||||
hash_text.match(/(.+)(https?\Z)/) do |matched|
|
||||
hash_text = matched[1]
|
||||
@ -48,17 +73,48 @@ module Extractor
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
42
app/lib/html_aware_formatter.rb
Normal file
42
app/lib/html_aware_formatter.rb
Normal 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
|
@ -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)
|
||||
|
||||
|
30
app/lib/plain_text_formatter.rb
Normal file
30
app/lib/plain_text_formatter.rb
Normal 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
|
@ -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
158
app/lib/text_formatter.rb
Normal 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
|
@ -5,6 +5,7 @@ class ApplicationMailer < ActionMailer::Base
|
||||
|
||||
helper :application
|
||||
helper :instance
|
||||
helper :formatting
|
||||
|
||||
protected
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
36
app/models/concerns/status_snapshot_concern.rb
Normal file
36
app/models/concerns/status_snapshot_concern.rb
Normal 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
|
@ -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)
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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}") %>
|
||||
|
@ -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 %>
|
||||
|
@ -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?)}
|
||||
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
|
||||
%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)
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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?)}
|
||||
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
|
||||
%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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -24,7 +24,7 @@ nl:
|
||||
status:
|
||||
attributes:
|
||||
reblog:
|
||||
taken: van toot bestaat al
|
||||
taken: van bericht bestaat al
|
||||
user:
|
||||
attributes:
|
||||
email:
|
||||
|
@ -474,9 +474,6 @@ ar:
|
||||
delivery_error_days: أيام أخطاء التوصيل
|
||||
delivery_error_hint: إذا كان التوصيل غير ممكناً لـ%{count} يوم، فستوضع عليها علامة {غير قابلة للتسليم} تلقائياً.
|
||||
empty: لم يتم العثور على نطاقات.
|
||||
known_accounts:
|
||||
one: "%{count} حساب معروف"
|
||||
other: "%{count} حسابات معروفة"
|
||||
moderation:
|
||||
all: كافتها
|
||||
limited: محدود
|
||||
|
@ -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
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 pêborînê wernegerî. Heke tu ji e-nameya ji nû ve sazkirina pê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 pê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 pê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.
|
||||
|
@ -12,7 +12,7 @@ th:
|
||||
last_attempt: คุณลองได้อีกหนึ่งครั้งก่อนที่บัญชีของคุณจะถูกล็อค
|
||||
locked: บัญชีของคุณถูกล็อค
|
||||
not_found_in_database: "%{authentication_keys} หรือรหัสผ่านไม่ถูกต้อง"
|
||||
pending: บัญชีของคุณยังอยู่ระหว่างการตรวจทาน
|
||||
pending: บัญชีของคุณยังคงอยู่ระหว่างการตรวจทาน
|
||||
timeout: เซสชันของคุณหมดอายุแล้ว โปรดลงชื่อเข้าอีกครั้งเพื่อดำเนินการต่อ
|
||||
unauthenticated: คุณจำเป็นต้องลงชื่อเข้าหรือลงทะเบียนก่อนดำเนินการต่อ
|
||||
unconfirmed: คุณต้องยืนยันที่อยู่อีเมลของคุณก่อนดำเนินการต่อ
|
||||
|
@ -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: پیگیری افراد
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 pê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
|
||||
|
@ -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
|
||||
|
@ -113,6 +113,7 @@ pt-BR:
|
||||
accounts: Contas
|
||||
all: Tudo
|
||||
blocks: Blocos
|
||||
bookmarks: Salvos
|
||||
conversations: Conversas
|
||||
crypto: Criptografia de ponta a ponta
|
||||
favourites: Favoritos
|
||||
|
@ -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: підписуйтесь на людей
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user