Merge pull request #974 from kmycode/upstream-20240128
Upstream 20240128
This commit is contained in:
commit
80cace410f
278 changed files with 2999 additions and 998 deletions
|
@ -10,6 +10,7 @@ services:
|
|||
RAILS_ENV: development
|
||||
NODE_ENV: development
|
||||
BIND: 0.0.0.0
|
||||
BOOTSNAP_CACHE_DIR: /tmp
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: '6379'
|
||||
DB_HOST: db
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# syntax=docker/dockerfile:1.12
|
||||
|
||||
# This file is designed for production server deployment, not local development work
|
||||
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
|
||||
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/docs/DEVELOPMENT.md#docker
|
||||
|
||||
# Please see https://docs.docker.com/engine/reference/builder for information about
|
||||
# the extended buildx capabilities used in this file.
|
||||
|
@ -9,6 +9,7 @@
|
|||
# See: https://docs.docker.com/build/building/multi-platform/
|
||||
ARG TARGETPLATFORM=${TARGETPLATFORM}
|
||||
ARG BUILDPLATFORM=${BUILDPLATFORM}
|
||||
ARG BASE_REGISTRY="docker.io"
|
||||
|
||||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
|
||||
# renovate: datasource=docker depName=docker.io/ruby
|
||||
|
@ -19,9 +20,9 @@ ARG NODE_MAJOR_VERSION="22"
|
|||
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
|
||||
ARG DEBIAN_VERSION="bookworm"
|
||||
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
|
||||
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
|
||||
FROM ${BASE_REGISTRY}/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
|
||||
# Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm)
|
||||
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
||||
FROM ${BASE_REGISTRY}/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
||||
|
||||
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
|
||||
# Example: v4.3.0-nightly.2023.11.09+pr-123456
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -100,6 +100,8 @@ gem 'json-ld'
|
|||
gem 'json-ld-preloaded', '~> 3.2'
|
||||
gem 'rdf-normalize', '~> 0.5'
|
||||
|
||||
gem 'prometheus_exporter', '~> 2.2', require: false
|
||||
|
||||
gem 'opentelemetry-api', '~> 1.4.0'
|
||||
|
||||
group :opentelemetry do
|
||||
|
|
15
Gemfile.lock
15
Gemfile.lock
|
@ -370,7 +370,7 @@ GEM
|
|||
marcel (~> 1.0.1)
|
||||
mime-types
|
||||
terrapin (>= 0.6.0, < 2.0)
|
||||
language_server-protocol (3.17.0.3)
|
||||
language_server-protocol (3.17.0.4)
|
||||
launchy (3.0.1)
|
||||
addressable (~> 2.8)
|
||||
childprocess (~> 5.0)
|
||||
|
@ -530,7 +530,7 @@ GEM
|
|||
opentelemetry-instrumentation-rack (0.26.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||
opentelemetry-instrumentation-rails (0.35.0)
|
||||
opentelemetry-instrumentation-rails (0.35.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-action_mailer (~> 0.4.0)
|
||||
opentelemetry-instrumentation-action_pack (~> 0.11.0)
|
||||
|
@ -557,7 +557,7 @@ GEM
|
|||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ostruct (0.6.1)
|
||||
ox (2.14.20)
|
||||
ox (2.14.21)
|
||||
bigdecimal (>= 3.0)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.7.0)
|
||||
|
@ -580,6 +580,8 @@ GEM
|
|||
net-smtp
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
prettyprint (0.2.0)
|
||||
prometheus_exporter (2.2.0)
|
||||
webrick
|
||||
propshaft (1.1.0)
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
|
@ -714,7 +716,7 @@ GEM
|
|||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 8)
|
||||
rspec-support (3.13.2)
|
||||
rubocop (1.70.0)
|
||||
rubocop (1.71.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
|
@ -724,14 +726,14 @@ GEM
|
|||
rubocop-ast (>= 1.36.2, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.37.0)
|
||||
rubocop-ast (1.38.0)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-capybara (2.21.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-performance (1.23.1)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rails (2.28.0)
|
||||
rubocop-rails (2.29.1)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.52.0, < 2.0)
|
||||
|
@ -984,6 +986,7 @@ DEPENDENCIES
|
|||
pg (~> 1.5)
|
||||
pghero
|
||||
premailer-rails
|
||||
prometheus_exporter (~> 2.2)
|
||||
propshaft
|
||||
public_suffix (~> 6.0)
|
||||
puma (~> 6.3)
|
||||
|
|
|
@ -34,7 +34,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:admin_account_action).permit(:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses)
|
||||
params
|
||||
.expect(admin_account_action: [:type, :report_id, :warning_preset_id, :text, :send_email_notification, :include_statuses])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -29,10 +29,8 @@ module Admin
|
|||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:account_moderation_note).permit(
|
||||
:content,
|
||||
:target_account_id
|
||||
)
|
||||
params
|
||||
.expect(account_moderation_note: [:content, :target_account_id])
|
||||
end
|
||||
|
||||
def set_account_moderation_note
|
||||
|
|
|
@ -172,7 +172,8 @@ module Admin
|
|||
end
|
||||
|
||||
def form_account_batch_params
|
||||
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||
params
|
||||
.expect(form_account_batch: [:action, account_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -84,6 +84,7 @@ class Admin::AnnouncementsController < Admin::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
|
||||
params
|
||||
.expect(announcement: [:text, :scheduled_at, :starts_at, :ends_at, :all_day])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,9 +41,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:user).permit(
|
||||
:unconfirmed_email
|
||||
)
|
||||
params
|
||||
.expect(user: [:unconfirmed_email])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,11 +67,13 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_emoji).permit(:shortcode, :image, :category_id, :visible_in_picker, :aliases_raw, :license)
|
||||
params
|
||||
.expect(custom_emoji: [:shortcode, :image, :category_id, :visible_in_picker, :aliases_raw, :license])
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:custom_emoji).permit(:category_id, :visible_in_picker, :aliases_raw, :license)
|
||||
params
|
||||
.expect(custom_emoji: [:category_id, :visible_in_picker, :aliases_raw, :license])
|
||||
end
|
||||
|
||||
def filtered_custom_emojis
|
||||
|
@ -101,7 +103,8 @@ module Admin
|
|||
end
|
||||
|
||||
def form_custom_emoji_batch_params
|
||||
params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
|
||||
params
|
||||
.expect(form_custom_emoji_batch: [:action, :category_id, :category_name, custom_emoji_ids: []])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,6 +37,7 @@ class Admin::DomainAllowsController < Admin::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_allow).permit(:domain)
|
||||
params
|
||||
.expect(domain_allow: [:domain])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,7 +35,9 @@ module Admin
|
|||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.domain_blocks.not_permitted')
|
||||
else
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
flash[:notice] = I18n.t('admin.domain_blocks.created_msg')
|
||||
ensure
|
||||
redirect_to admin_instances_path(limited: '1')
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -124,9 +126,14 @@ module Admin
|
|||
end
|
||||
|
||||
def form_domain_block_batch_params
|
||||
params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_favourite, :reject_reply_exclude_followers,
|
||||
:reject_send_sensitive, :reject_hashtag, :reject_straight_follow, :reject_new_follow, :reject_friend, :block_trends, :detect_invalid_subscription,
|
||||
:reject_reports, :private_comment, :public_comment, :obfuscate, :hidden])
|
||||
params
|
||||
.expect(
|
||||
form_domain_block_batch: [
|
||||
domain_blocks_attributes: [[:enabled, :domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate,
|
||||
:reject_favourite, :reject_reply_exclude_followers, :reject_send_sensitive, :reject_hashtag,
|
||||
:reject_straight_follow, :reject_new_follow, :reject_friend, :block_trends, :detect_invalid_subscription, :hidden]],
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -62,11 +62,13 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:email_domain_block).permit(:domain, :allow_with_approval, other_domains: [])
|
||||
params
|
||||
.expect(email_domain_block: [:domain, :allow_with_approval, other_domains: []])
|
||||
end
|
||||
|
||||
def form_email_domain_block_batch_params
|
||||
params.require(:form_email_domain_block_batch).permit(email_domain_block_ids: [])
|
||||
params
|
||||
.expect(form_email_domain_block_batch: [email_domain_block_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -37,7 +37,8 @@ module Admin
|
|||
end
|
||||
|
||||
def form_account_batch_params
|
||||
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||
params
|
||||
.expect(form_account_batch: [:action, account_ids: []])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
|
|
|
@ -79,11 +79,11 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:friend_domain).permit(:domain, :inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts)
|
||||
params.expect(friend_domain: [:domain, :inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts])
|
||||
end
|
||||
|
||||
def update_resource_params
|
||||
params.require(:friend_domain).permit(:inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts)
|
||||
params.expect(friend_domain: [:inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts])
|
||||
end
|
||||
|
||||
def warn_signatures_not_enabled!
|
||||
|
|
|
@ -39,7 +39,8 @@ module Admin
|
|||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:invite).permit(:max_uses, :expires_in)
|
||||
params
|
||||
.expect(invite: [:max_uses, :expires_in])
|
||||
end
|
||||
|
||||
def filtered_invites
|
||||
|
|
|
@ -44,7 +44,8 @@ module Admin
|
|||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:ip_block).permit(:ip, :severity, :comment, :expires_in)
|
||||
params
|
||||
.expect(ip_block: [:ip, :severity, :comment, :expires_in])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
@ -52,7 +53,8 @@ module Admin
|
|||
end
|
||||
|
||||
def form_ip_block_batch_params
|
||||
params.require(:form_ip_block_batch).permit(ip_block_ids: [])
|
||||
params
|
||||
.expect(form_ip_block_batch: [ip_block_ids: []])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,16 +82,16 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:ng_rule).permit(:title, :expires_in, :available, :account_domain, :account_username, :account_display_name,
|
||||
:account_note, :account_field_name, :account_field_value, :account_avatar_state,
|
||||
:account_header_state, :account_include_local, :status_spoiler_text, :status_text, :status_tag,
|
||||
:status_sensitive_state, :status_cw_state, :status_media_state, :status_poll_state,
|
||||
:status_mention_state, :status_reference_state,
|
||||
:status_quote_state, :status_reply_state, :status_media_threshold, :status_poll_threshold,
|
||||
:status_mention_threshold, :status_allow_follower_mention,
|
||||
:reaction_allow_follower, :emoji_reaction_name, :emoji_reaction_origin_domain,
|
||||
:status_reference_threshold, :account_allow_followed_by_local, :record_history_also_local,
|
||||
status_visibility: [], status_searchability: [], reaction_type: [])
|
||||
params.expect(ng_rule: [:title, :expires_in, :available, :account_domain, :account_username, :account_display_name,
|
||||
:account_note, :account_field_name, :account_field_value, :account_avatar_state,
|
||||
:account_header_state, :account_include_local, :status_spoiler_text, :status_text, :status_tag,
|
||||
:status_sensitive_state, :status_cw_state, :status_media_state, :status_poll_state,
|
||||
:status_mention_state, :status_reference_state,
|
||||
:status_quote_state, :status_reply_state, :status_media_threshold, :status_poll_threshold,
|
||||
:status_mention_threshold, :status_allow_follower_mention,
|
||||
:reaction_allow_follower, :emoji_reaction_name, :emoji_reaction_origin_domain,
|
||||
:status_reference_threshold, :account_allow_followed_by_local, :record_history_also_local,
|
||||
status_visibility: [], status_searchability: [], reaction_type: []])
|
||||
end
|
||||
|
||||
def test_words!
|
||||
|
|
|
@ -36,7 +36,7 @@ module Admin
|
|||
private
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
|
||||
end
|
||||
|
||||
def settings_params_test
|
||||
|
|
|
@ -57,7 +57,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:relay).permit(:inbox_url)
|
||||
params
|
||||
.expect(relay: [:inbox_url])
|
||||
end
|
||||
|
||||
def warn_signatures_not_enabled!
|
||||
|
|
|
@ -47,10 +47,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:report_note).permit(
|
||||
:content,
|
||||
:report_id
|
||||
)
|
||||
params
|
||||
.expect(report_note: [:content, :report_id])
|
||||
end
|
||||
|
||||
def set_report_note
|
||||
|
|
|
@ -61,7 +61,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:user_role).permit(:name, :color, :highlighted, :position, permissions_as_keys: [])
|
||||
params
|
||||
.expect(user_role: [:name, :color, :highlighted, :position, permissions_as_keys: []])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,7 +53,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:rule).permit(:text, :hint, :priority)
|
||||
params
|
||||
.expect(rule: [:text, :hint, :priority])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ module Admin
|
|||
end
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
|
||||
end
|
||||
|
||||
def settings_params_test
|
||||
|
|
|
@ -28,7 +28,8 @@ module Admin
|
|||
end
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
params
|
||||
.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module Admin
|
|||
end
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ module Admin
|
|||
end
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
params.expect(form_admin_settings: [*Form::AdminSettings::KEYS])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -98,7 +98,8 @@ module Admin
|
|||
helper_method :batched_ordered_status_edits
|
||||
|
||||
def admin_status_batch_action_params
|
||||
params.require(:admin_status_batch_action).permit(status_ids: [])
|
||||
params
|
||||
.expect(admin_status_batch_action: [status_ids: []])
|
||||
end
|
||||
|
||||
def after_create_redirect_path
|
||||
|
|
|
@ -37,7 +37,8 @@ module Admin
|
|||
end
|
||||
|
||||
def tag_params
|
||||
params.require(:tag).permit(:name, :display_name, :trendable, :usable, :listable)
|
||||
params
|
||||
.expect(tag: [:name, :display_name, :trendable, :usable, :listable])
|
||||
end
|
||||
|
||||
def filtered_tags
|
||||
|
|
|
@ -31,6 +31,7 @@ class Admin::TermsOfService::DraftsController < Admin::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:terms_of_service).permit(:text, :changelog)
|
||||
params
|
||||
.expect(terms_of_service: [:text, :changelog])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,7 @@ class Admin::TermsOfService::GeneratesController < Admin::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:terms_of_service_generator).permit(*TermsOfService::Generator::VARIABLES)
|
||||
params
|
||||
.expect(terms_of_service_generator: [*TermsOfService::Generator::VARIABLES])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,7 +31,8 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
|
|||
end
|
||||
|
||||
def trends_preview_card_provider_batch_params
|
||||
params.require(:trends_preview_card_provider_batch).permit(:action, preview_card_provider_ids: [])
|
||||
params
|
||||
.expect(trends_preview_card_provider_batch: [:action, preview_card_provider_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -31,7 +31,8 @@ class Admin::Trends::LinksController < Admin::BaseController
|
|||
end
|
||||
|
||||
def trends_preview_card_batch_params
|
||||
params.require(:trends_preview_card_batch).permit(:action, preview_card_ids: [])
|
||||
params
|
||||
.expect(trends_preview_card_batch: [:action, preview_card_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -31,7 +31,8 @@ class Admin::Trends::StatusesController < Admin::BaseController
|
|||
end
|
||||
|
||||
def trends_status_batch_params
|
||||
params.require(:trends_status_batch).permit(:action, status_ids: [])
|
||||
params
|
||||
.expect(trends_status_batch: [:action, status_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -31,7 +31,8 @@ class Admin::Trends::TagsController < Admin::BaseController
|
|||
end
|
||||
|
||||
def trends_tag_batch_params
|
||||
params.require(:trends_tag_batch).permit(:action, tag_ids: [])
|
||||
params
|
||||
.expect(trends_tag_batch: [:action, tag_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -28,7 +28,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:user).permit(:role_id)
|
||||
params
|
||||
.expect(user: [:role_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,7 +52,8 @@ module Admin
|
|||
end
|
||||
|
||||
def warning_preset_params
|
||||
params.require(:account_warning_preset).permit(:title, :text)
|
||||
params
|
||||
.expect(account_warning_preset: [:title, :text])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -74,7 +74,8 @@ module Admin
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:webhook).permit(:url, :template, events: [])
|
||||
params
|
||||
.expect(webhook: [:url, :template, events: []])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -73,7 +73,7 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email, :password, :otp_attempt, :disable_css, credential: {})
|
||||
params.expect(user: [:email, :password, :otp_attempt, :disable_css, credential: {}])
|
||||
end
|
||||
|
||||
def login_page_params
|
||||
|
|
|
@ -24,6 +24,6 @@ module Admin::ExportControllerConcern
|
|||
end
|
||||
|
||||
def import_params
|
||||
params.require(:admin_import).permit(:data)
|
||||
params.expect(admin_import: [:data])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -58,6 +58,6 @@ module ChallengableConcern
|
|||
end
|
||||
|
||||
def challenge_params
|
||||
params.require(:form_challenge).permit(:current_password, :return_to)
|
||||
params.expect(form_challenge: [:current_password, :return_to])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -117,7 +117,7 @@ module SignatureVerification
|
|||
|
||||
def verify_signature_strength!
|
||||
raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
|
||||
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest')
|
||||
raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(HttpSignatureDraft::REQUEST_TARGET) || signed_headers.include?('digest')
|
||||
raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
|
||||
raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
|
||||
end
|
||||
|
@ -155,14 +155,14 @@ module SignatureVerification
|
|||
def build_signed_string(include_query_string: true)
|
||||
signed_headers.map do |signed_header|
|
||||
case signed_header
|
||||
when Request::REQUEST_TARGET
|
||||
when HttpSignatureDraft::REQUEST_TARGET
|
||||
if include_query_string
|
||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
|
||||
"#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
|
||||
else
|
||||
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
|
||||
# Therefore, temporarily support such incorrect signatures for compatibility.
|
||||
# TODO: remove eventually some time after release of the fixed version
|
||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||
"#{HttpSignatureDraft::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||
end
|
||||
when '(created)'
|
||||
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
|
||||
|
|
|
@ -34,7 +34,7 @@ class Filters::StatusesController < ApplicationController
|
|||
end
|
||||
|
||||
def status_filter_batch_action_params
|
||||
params.require(:form_status_filter_batch_action).permit(status_filter_ids: [])
|
||||
params.expect(form_status_filter_batch_action: [status_filter_ids: []])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
|
@ -36,7 +36,7 @@ class RelationshipsController < ApplicationController
|
|||
end
|
||||
|
||||
def form_account_batch_params
|
||||
params.require(:form_account_batch).permit(:action, account_ids: [])
|
||||
params.expect(form_account_batch: [:action, account_ids: []])
|
||||
end
|
||||
|
||||
def following_relationship?
|
||||
|
|
|
@ -60,16 +60,12 @@ class Settings::ApplicationsController < Settings::BaseController
|
|||
end
|
||||
|
||||
def application_params
|
||||
params.require(:doorkeeper_application).permit(
|
||||
:name,
|
||||
:redirect_uri,
|
||||
:scopes,
|
||||
:website
|
||||
)
|
||||
params
|
||||
.expect(doorkeeper_application: [:name, :redirect_uri, :scopes, :website])
|
||||
end
|
||||
|
||||
def prepare_scopes
|
||||
scopes = params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
|
||||
scopes = application_params.fetch(:doorkeeper_application, {}).fetch(:scopes, nil)
|
||||
params[:doorkeeper_application][:scopes] = scopes.join(' ') if scopes.is_a? Array
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,10 +25,10 @@ class Settings::Preferences::BaseController < Settings::BaseController
|
|||
end
|
||||
|
||||
def original_user_params
|
||||
params.require(:user).permit(:locale, :time_zone, :custom_css_text, chosen_languages: [], settings_attributes: UserSettings.keys)
|
||||
params.expect(user: [:locale, :time_zone, :custom_css_text, chosen_languages: [], settings_attributes: UserSettings.keys])
|
||||
end
|
||||
|
||||
def disabled_visibilities_params
|
||||
params.require(:user).permit(settings_attributes: { enabled_visibilities: [] })
|
||||
params.expect(user: [settings_attributes: { enabled_visibilities: [] }])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ class Settings::PrivacyExtraController < Settings::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(settings: UserSettings.keys)
|
||||
params.expect(account: [settings: UserSettings.keys])
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
|
|
@ -38,7 +38,7 @@ module Settings
|
|||
private
|
||||
|
||||
def confirmation_params
|
||||
params.require(:form_two_factor_confirmation).permit(:otp_attempt)
|
||||
params.expect(form_two_factor_confirmation: [:otp_attempt])
|
||||
end
|
||||
|
||||
def prepare_two_factor_form
|
||||
|
|
|
@ -18,7 +18,7 @@ class Settings::VerificationsController < Settings::BaseController
|
|||
private
|
||||
|
||||
def account_params
|
||||
params.require(:account).permit(:attribution_domains).tap do |params|
|
||||
params.expect(account: [:attribution_domains]).tap do |params|
|
||||
params[:attribution_domains] = params[:attribution_domains].split if params[:attribution_domains]
|
||||
end
|
||||
end
|
||||
|
|
55
app/javascript/hooks/useSelectableClick.ts
Normal file
55
app/javascript/hooks/useSelectableClick.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { useRef, useCallback } from 'react';
|
||||
|
||||
type Position = [number, number];
|
||||
|
||||
export const useSelectableClick = (
|
||||
onClick: React.MouseEventHandler,
|
||||
maxDelta = 5,
|
||||
) => {
|
||||
const clickPositionRef = useRef<Position | null>(null);
|
||||
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
clickPositionRef.current = [e.clientX, e.clientY];
|
||||
}, []);
|
||||
|
||||
const handleMouseUp = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (!clickPositionRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [startX, startY] = clickPositionRef.current;
|
||||
const [deltaX, deltaY] = [
|
||||
Math.abs(e.clientX - startX),
|
||||
Math.abs(e.clientY - startY),
|
||||
];
|
||||
|
||||
let element: EventTarget | null = e.target;
|
||||
|
||||
while (element && element instanceof HTMLElement) {
|
||||
if (
|
||||
element.localName === 'button' ||
|
||||
element.localName === 'a' ||
|
||||
element.localName === 'label'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
element = element.parentNode;
|
||||
}
|
||||
|
||||
if (
|
||||
deltaX + deltaY < maxDelta &&
|
||||
(e.button === 0 || e.button === 1) &&
|
||||
e.detail >= 1
|
||||
) {
|
||||
onClick(e);
|
||||
}
|
||||
|
||||
clickPositionRef.current = null;
|
||||
},
|
||||
[maxDelta, onClick],
|
||||
);
|
||||
|
||||
return [handleMouseDown, handleMouseUp] as const;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useCallback, useRef } from 'react';
|
||||
import { useState, useCallback, useRef, useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -8,12 +8,15 @@ import type {
|
|||
UsePopperOptions,
|
||||
} from 'react-overlays/esm/usePopper';
|
||||
|
||||
import { useSelectableClick } from '@/hooks/useSelectableClick';
|
||||
|
||||
const offset = [0, 4] as OffsetValue;
|
||||
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
||||
|
||||
export const AltTextBadge: React.FC<{
|
||||
description: string;
|
||||
}> = ({ description }) => {
|
||||
const accessibilityId = useId();
|
||||
const anchorRef = useRef<HTMLButtonElement>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
|
@ -25,12 +28,16 @@ export const AltTextBadge: React.FC<{
|
|||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClose);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
ref={anchorRef}
|
||||
className='media-gallery__alt__label'
|
||||
onClick={handleClick}
|
||||
aria-expanded={open}
|
||||
aria-controls={accessibilityId}
|
||||
>
|
||||
ALT
|
||||
</button>
|
||||
|
@ -47,9 +54,12 @@ export const AltTextBadge: React.FC<{
|
|||
>
|
||||
{({ props }) => (
|
||||
<div {...props} className='hover-card-controller'>
|
||||
<div
|
||||
<div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
|
||||
className='media-gallery__alt__popover dropdown-animation'
|
||||
role='tooltip'
|
||||
role='region'
|
||||
id={accessibilityId}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
|
||||
const messages = defineMessages({
|
||||
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
||||
|
@ -17,10 +18,12 @@ interface Props<T> {
|
|||
|
||||
export const LoadGap = <T,>({ disabled, param, onClick }: Props<T>) => {
|
||||
const intl = useIntl();
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setLoading(true);
|
||||
onClick(param);
|
||||
}, [param, onClick]);
|
||||
}, [setLoading, param, onClick]);
|
||||
|
||||
return (
|
||||
<button
|
||||
|
@ -28,8 +31,13 @@ export const LoadGap = <T,>({ disabled, param, onClick }: Props<T>) => {
|
|||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
aria-label={intl.formatMessage(messages.load_more)}
|
||||
title={intl.formatMessage(messages.load_more)}
|
||||
>
|
||||
<Icon id='ellipsis-h' icon={MoreHorizIcon} />
|
||||
{loading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<Icon id='ellipsis-h' icon={MoreHorizIcon} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -161,7 +161,6 @@ class Item extends PureComponent {
|
|||
srcSet={srcSet}
|
||||
sizes={sizes}
|
||||
alt={description}
|
||||
title={description}
|
||||
lang={lang}
|
||||
style={{ objectPosition: `${x}% ${y}%` }}
|
||||
onLoad={this.handleImageLoad}
|
||||
|
@ -183,7 +182,6 @@ class Item extends PureComponent {
|
|||
<video
|
||||
className='media-gallery__item-gifv-thumbnail'
|
||||
aria-label={description}
|
||||
title={description}
|
||||
lang={lang}
|
||||
role='application'
|
||||
src={attachment.get('url')}
|
||||
|
|
|
@ -342,7 +342,7 @@ class Status extends ImmutablePureComponent {
|
|||
const { onToggleHidden } = this.props;
|
||||
const status = this._properStatus();
|
||||
|
||||
if (status.get('matched_filters')) {
|
||||
if (this.props.status.get('matched_filters')) {
|
||||
const expandedBecauseOfCW = !status.get('hidden') || status.get('spoiler_text').length === 0;
|
||||
const expandedBecauseOfFilter = this.state.showDespiteFilter;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
@ -16,6 +16,7 @@ export const DomainPill: React.FC<{
|
|||
username: string;
|
||||
isSelf: boolean;
|
||||
}> = ({ domain, username, isSelf }) => {
|
||||
const accessibilityId = useId();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const triggerRef = useRef(null);
|
||||
|
@ -34,6 +35,8 @@ export const DomainPill: React.FC<{
|
|||
className={classNames('account__domain-pill', { active: open })}
|
||||
ref={triggerRef}
|
||||
onClick={handleClick}
|
||||
aria-expanded={open}
|
||||
aria-controls={accessibilityId}
|
||||
>
|
||||
{domain}
|
||||
</button>
|
||||
|
@ -48,6 +51,8 @@ export const DomainPill: React.FC<{
|
|||
{({ props }) => (
|
||||
<div
|
||||
{...props}
|
||||
role='region'
|
||||
id={accessibilityId}
|
||||
className='account__domain-pill__popout dropdown-animation'
|
||||
>
|
||||
<div className='account__domain-pill__popout__header'>
|
||||
|
|
|
@ -93,7 +93,6 @@ export const MediaItem: React.FC<{
|
|||
<img
|
||||
src={previewUrl || avatarUrl}
|
||||
alt={description}
|
||||
title={description}
|
||||
lang={lang}
|
||||
onLoad={handleImageLoad}
|
||||
/>
|
||||
|
@ -113,7 +112,6 @@ export const MediaItem: React.FC<{
|
|||
<img
|
||||
src={previewUrl}
|
||||
alt={description}
|
||||
title={description}
|
||||
lang={lang}
|
||||
style={{ objectPosition: `${x}% ${y}%` }}
|
||||
onLoad={handleImageLoad}
|
||||
|
@ -131,7 +129,6 @@ export const MediaItem: React.FC<{
|
|||
<video
|
||||
className='media-gallery__item-gifv-thumbnail'
|
||||
aria-label={description}
|
||||
title={description}
|
||||
lang={lang}
|
||||
src={fullUrl}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
|
||||
import { useSelectableClick } from '@/hooks/useSelectableClick';
|
||||
import QuestionMarkIcon from '@/material-icons/400-24px/question_mark.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
help: { id: 'info_button.label', defaultMessage: 'Help' },
|
||||
});
|
||||
|
||||
export const InfoButton: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const [open, setOpen] = useState(false);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const accessibilityId = useId();
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setOpen(!open);
|
||||
}, [open, setOpen]);
|
||||
|
||||
const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClick);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
type='button'
|
||||
className={classNames('help-button', { active: open })}
|
||||
ref={triggerRef}
|
||||
onClick={handleClick}
|
||||
aria-expanded={open}
|
||||
aria-controls={accessibilityId}
|
||||
aria-label={intl.formatMessage(messages.help)}
|
||||
>
|
||||
<Icon id='' icon={QuestionMarkIcon} />
|
||||
</button>
|
||||
|
||||
<Overlay
|
||||
show={open}
|
||||
rootClose
|
||||
placement='top'
|
||||
onHide={handleClick}
|
||||
offset={[5, 5]}
|
||||
target={triggerRef}
|
||||
>
|
||||
{({ props }) => (
|
||||
<div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
|
||||
{...props}
|
||||
className='dialog-modal__popout prose dropdown-animation'
|
||||
role='region'
|
||||
id={accessibilityId}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='info_button.what_is_alt_text'
|
||||
defaultMessage='<h1>What is alt text?</h1>
|
||||
|
||||
<p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p>
|
||||
|
||||
<p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p>
|
||||
|
||||
<ul>
|
||||
<li>Capture important elements</li>
|
||||
<li>Summarize text in images</li>
|
||||
<li>Use regular sentence structure</li>
|
||||
<li>Avoid redundant information</li>
|
||||
<li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li>
|
||||
</ul>'
|
||||
values={{
|
||||
h1: (node) => <h1>{node}</h1>,
|
||||
p: (node) => <p>{node}</p>,
|
||||
ul: (node) => <ul>{node}</ul>,
|
||||
li: (node) => <li>{node}</li>,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Overlay>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -36,6 +36,8 @@ import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
|||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
|
||||
import { InfoButton } from './components/info_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholderVisual: {
|
||||
id: 'alt_text_modal.describe_for_people_with_visual_impairments',
|
||||
|
@ -504,6 +506,13 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
|||
</div>
|
||||
|
||||
<div className='input__toolbar'>
|
||||
<CharacterCounter
|
||||
max={MAX_LENGTH}
|
||||
text={isDetecting ? '' : description}
|
||||
/>
|
||||
|
||||
<div className='spacer' />
|
||||
|
||||
<button
|
||||
className='link-button'
|
||||
onClick={handleDetectClick}
|
||||
|
@ -515,10 +524,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
|||
/>
|
||||
</button>
|
||||
|
||||
<CharacterCounter
|
||||
max={MAX_LENGTH}
|
||||
text={isDetecting ? '' : description}
|
||||
/>
|
||||
<InfoButton />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -10,6 +10,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
|
||||
import { length } from 'stringz';
|
||||
|
||||
import { missingAltTextModal } from 'mastodon/initial_state';
|
||||
|
||||
import AutosuggestInput from '../../../components/autosuggest_input';
|
||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||
import { Button } from '../../../components/button';
|
||||
|
@ -17,7 +19,6 @@ import CircleDropdownContainer from '../containers/circle_dropdown_container';
|
|||
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
|
||||
import ExpirationDropdownContainer from '../containers/expiration_dropdown_container';
|
||||
import FeaturedTagsDropdownContainer from '../containers/featured_tags_dropdown_container';
|
||||
import LanguageDropdown from '../containers/language_dropdown_container';
|
||||
import MarkdownButtonContainer from '../containers/markdown_button_container';
|
||||
import PollButtonContainer from '../containers/poll_button_container';
|
||||
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||
|
@ -29,6 +30,7 @@ import { countableText } from '../util/counter';
|
|||
|
||||
import { CharacterCounter } from './character_counter';
|
||||
import { EditIndicator } from './edit_indicator';
|
||||
import { LanguageDropdown } from './language_dropdown';
|
||||
import { NavigationBar } from './navigation_bar';
|
||||
import { PollForm } from "./poll_form";
|
||||
import { ReplyIndicator } from './reply_indicator';
|
||||
|
@ -73,6 +75,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
autoFocus: PropTypes.bool,
|
||||
withoutNavigation: PropTypes.bool,
|
||||
anyMedia: PropTypes.bool,
|
||||
missingAltText: PropTypes.bool,
|
||||
isInReply: PropTypes.bool,
|
||||
singleColumn: PropTypes.bool,
|
||||
lang: PropTypes.string,
|
||||
|
@ -126,7 +129,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit();
|
||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText);
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
|
|
@ -1,330 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
|
||||
import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import TranslateIcon from '@/material-icons/400-24px/translate.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { languages as preloadedLanguages } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
changeLanguage: { id: 'compose.language.change', defaultMessage: 'Change language' },
|
||||
search: { id: 'compose.language.search', defaultMessage: 'Search languages...' },
|
||||
clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
|
||||
});
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||
|
||||
class LanguageDropdownMenu extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
guess: PropTypes.string,
|
||||
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
languages: preloadedLanguages,
|
||||
};
|
||||
|
||||
state = {
|
||||
searchValue: '',
|
||||
};
|
||||
|
||||
handleDocumentClick = e => {
|
||||
if (this.node && !this.node.contains(e.target)) {
|
||||
this.props.onClose();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
|
||||
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
|
||||
// to wait for a frame before focusing
|
||||
requestAnimationFrame(() => {
|
||||
if (this.node) {
|
||||
const element = this.node.querySelector('input[type="search"]');
|
||||
if (element) element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('click', this.handleDocumentClick, { capture: true });
|
||||
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
setListRef = c => {
|
||||
this.listNode = c;
|
||||
};
|
||||
|
||||
handleSearchChange = ({ target }) => {
|
||||
this.setState({ searchValue: target.value });
|
||||
};
|
||||
|
||||
search () {
|
||||
const { languages, value, frequentlyUsedLanguages, guess } = this.props;
|
||||
const { searchValue } = this.state;
|
||||
|
||||
if (searchValue === '') {
|
||||
return [...languages].sort((a, b) => {
|
||||
|
||||
if (guess && a[0] === guess) { // Push guessed language higher than current selection
|
||||
return -1;
|
||||
} else if (guess && b[0] === guess) {
|
||||
return 1;
|
||||
} else if (a[0] === value) { // Push current selection to the top of the list
|
||||
return -1;
|
||||
} else if (b[0] === value) {
|
||||
return 1;
|
||||
} else {
|
||||
// Sort according to frequently used languages
|
||||
|
||||
const indexOfA = frequentlyUsedLanguages.indexOf(a[0]);
|
||||
const indexOfB = frequentlyUsedLanguages.indexOf(b[0]);
|
||||
|
||||
return ((indexOfA > -1 ? indexOfA : Infinity) - (indexOfB > -1 ? indexOfB : Infinity));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fuzzysort.go(searchValue, languages, {
|
||||
keys: ['0', '1', '2'],
|
||||
limit: 5,
|
||||
threshold: -10000,
|
||||
}).map(result => result.obj);
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
this.props.onClose();
|
||||
this.props.onChange(value);
|
||||
};
|
||||
|
||||
handleKeyDown = e => {
|
||||
const { onClose } = this.props;
|
||||
const index = Array.from(this.listNode.childNodes).findIndex(node => node === e.currentTarget);
|
||||
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Escape':
|
||||
onClose();
|
||||
break;
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
this.handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = this.listNode.childNodes[index - 1] || this.listNode.lastChild;
|
||||
} else {
|
||||
element = this.listNode.childNodes[index + 1] || this.listNode.firstChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = this.listNode.firstChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = this.listNode.lastChild;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
handleSearchKeyDown = e => {
|
||||
const { onChange, onClose } = this.props;
|
||||
const { searchValue } = this.state;
|
||||
|
||||
let element = null;
|
||||
|
||||
switch(e.key) {
|
||||
case 'Tab':
|
||||
case 'ArrowDown':
|
||||
element = this.listNode.firstChild;
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
element = this.listNode.firstChild;
|
||||
|
||||
if (element) {
|
||||
onChange(element.getAttribute('data-index'));
|
||||
onClose();
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
if (searchValue !== '') {
|
||||
e.preventDefault();
|
||||
this.handleClear();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleClear = () => {
|
||||
this.setState({ searchValue: '' });
|
||||
};
|
||||
|
||||
renderItem = lang => {
|
||||
const { value } = this.props;
|
||||
|
||||
return (
|
||||
<div key={lang[0]} role='option' tabIndex={0} data-index={lang[0]} className={classNames('language-dropdown__dropdown__results__item', { active: lang[0] === value })} aria-selected={lang[0] === value} onClick={this.handleClick} onKeyDown={this.handleKeyDown}>
|
||||
<span className='language-dropdown__dropdown__results__item__native-name' lang={lang[0]}>{lang[2]}</span> <span className='language-dropdown__dropdown__results__item__common-name'>({lang[1]})</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
const { searchValue } = this.state;
|
||||
const isSearching = searchValue !== '';
|
||||
const results = this.search();
|
||||
|
||||
return (
|
||||
<div ref={this.setRef}>
|
||||
<div className='emoji-mart-search'>
|
||||
<input type='search' value={searchValue} onChange={this.handleSearchChange} onKeyDown={this.handleSearchKeyDown} placeholder={intl.formatMessage(messages.search)} />
|
||||
<button type='button' className='emoji-mart-search-icon' disabled={!isSearching} aria-label={intl.formatMessage(messages.clear)} onClick={this.handleClear}><Icon icon={!isSearching ? SearchIcon : CancelIcon} /></button>
|
||||
</div>
|
||||
|
||||
<div className='language-dropdown__dropdown__results emoji-mart-scroll' role='listbox' ref={this.setListRef}>
|
||||
{results.map(this.renderItem)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LanguageDropdown extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
frequentlyUsedLanguages: PropTypes.arrayOf(PropTypes.string),
|
||||
guess: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
open: false,
|
||||
placement: 'bottom',
|
||||
};
|
||||
|
||||
handleToggle = () => {
|
||||
if (this.state.open && this.activeElement) {
|
||||
this.activeElement.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
this.setState({ open: !this.state.open });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
if (this.state.open && this.activeElement) {
|
||||
this.activeElement.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
handleChange = value => {
|
||||
const { onChange } = this.props;
|
||||
onChange(value);
|
||||
};
|
||||
|
||||
setTargetRef = c => {
|
||||
this.target = c;
|
||||
};
|
||||
|
||||
findTarget = () => {
|
||||
return this.target;
|
||||
};
|
||||
|
||||
handleOverlayEnter = (state) => {
|
||||
this.setState({ placement: state.placement });
|
||||
};
|
||||
|
||||
render () {
|
||||
const { value, guess, intl, frequentlyUsedLanguages } = this.props;
|
||||
const { open, placement } = this.state;
|
||||
const current = preloadedLanguages.find(lang => lang[0] === value) ?? [];
|
||||
|
||||
return (
|
||||
<div ref={this.setTargetRef} onKeyDown={this.handleKeyDown}>
|
||||
<button
|
||||
type='button'
|
||||
title={intl.formatMessage(messages.changeLanguage)}
|
||||
aria-expanded={open}
|
||||
onClick={this.handleToggle}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onKeyDown={this.handleButtonKeyDown}
|
||||
className={classNames('dropdown-button', { active: open, warning: guess !== '' && guess !== value })}
|
||||
>
|
||||
<Icon icon={TranslateIcon} />
|
||||
<span className='dropdown-button__label'>{current[2] ?? value}</span>
|
||||
</button>
|
||||
|
||||
<Overlay show={open} offset={[5, 5]} placement={placement} flip target={this.findTarget} popperConfig={{ strategy: 'fixed', onFirstUpdate: this.handleOverlayEnter }}>
|
||||
{({ props, placement }) => (
|
||||
<div {...props}>
|
||||
<div className={`dropdown-animation language-dropdown__dropdown ${placement}`} >
|
||||
<LanguageDropdownMenu
|
||||
value={value}
|
||||
guess={guess}
|
||||
frequentlyUsedLanguages={frequentlyUsedLanguages}
|
||||
onClose={this.handleClose}
|
||||
onChange={this.handleChange}
|
||||
intl={intl}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(LanguageDropdown);
|
|
@ -0,0 +1,427 @@
|
|||
import { useCallback, useRef, useState, useEffect, useMemo } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import Overlay from 'react-overlays/Overlay';
|
||||
import type { State, Placement } from 'react-overlays/usePopper';
|
||||
|
||||
import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import TranslateIcon from '@/material-icons/400-24px/translate.svg?react';
|
||||
import { changeComposeLanguage } from 'mastodon/actions/compose';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { languages as preloadedLanguages } from 'mastodon/initial_state';
|
||||
import type { RootState } from 'mastodon/store';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { debouncedGuess } from '../util/language_detection';
|
||||
|
||||
const messages = defineMessages({
|
||||
changeLanguage: {
|
||||
id: 'compose.language.change',
|
||||
defaultMessage: 'Change language',
|
||||
},
|
||||
search: {
|
||||
id: 'compose.language.search',
|
||||
defaultMessage: 'Search languages...',
|
||||
},
|
||||
clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' },
|
||||
});
|
||||
|
||||
type Language = [string, string, string];
|
||||
|
||||
const getFrequentlyUsedLanguages = createSelector(
|
||||
[
|
||||
(state: RootState) =>
|
||||
(state.settings as ImmutableMap<string, unknown>).get(
|
||||
'frequentlyUsedLanguages',
|
||||
ImmutableMap(),
|
||||
) as ImmutableMap<string, number>,
|
||||
],
|
||||
(languageCounters) =>
|
||||
languageCounters
|
||||
.keySeq()
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(languageCounters.get(a) ?? 0) - (languageCounters.get(b) ?? 0),
|
||||
)
|
||||
.reverse()
|
||||
.toArray(),
|
||||
);
|
||||
|
||||
const LanguageDropdownMenu: React.FC<{
|
||||
value: string;
|
||||
guess?: string;
|
||||
onClose: () => void;
|
||||
onChange: (arg0: string) => void;
|
||||
}> = ({ value, guess, onClose, onChange }) => {
|
||||
const languages = preloadedLanguages as Language[];
|
||||
const intl = useIntl();
|
||||
const [searchValue, setSearchValue] = useState('');
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const listNodeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const frequentlyUsedLanguages = useAppSelector(getFrequentlyUsedLanguages);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchValue(target.value);
|
||||
},
|
||||
[setSearchValue],
|
||||
);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
onClose();
|
||||
onChange(value);
|
||||
},
|
||||
[onClose, onChange],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (!listNodeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = Array.from(listNodeRef.current.childNodes).findIndex(
|
||||
(node) => node === e.currentTarget,
|
||||
);
|
||||
|
||||
let element = null;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
onClose();
|
||||
break;
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element =
|
||||
listNodeRef.current.childNodes[index + 1] ??
|
||||
listNodeRef.current.firstChild;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element =
|
||||
listNodeRef.current.childNodes[index - 1] ??
|
||||
listNodeRef.current.lastChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element =
|
||||
listNodeRef.current.childNodes[index - 1] ??
|
||||
listNodeRef.current.lastChild;
|
||||
} else {
|
||||
element =
|
||||
listNodeRef.current.childNodes[index + 1] ??
|
||||
listNodeRef.current.firstChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = listNodeRef.current.firstChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = listNodeRef.current.lastChild;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element && element instanceof HTMLElement) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
[onClose, handleClick],
|
||||
);
|
||||
|
||||
const handleSearchKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
let element = null;
|
||||
|
||||
if (!listNodeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case 'Tab':
|
||||
case 'ArrowDown':
|
||||
element = listNodeRef.current.firstChild;
|
||||
|
||||
if (element && element instanceof HTMLElement) {
|
||||
element.focus();
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
element = listNodeRef.current.firstChild;
|
||||
|
||||
if (element && element instanceof HTMLElement) {
|
||||
const value = element.getAttribute('data-index');
|
||||
|
||||
if (value) {
|
||||
onChange(value);
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Escape':
|
||||
if (searchValue !== '') {
|
||||
e.preventDefault();
|
||||
setSearchValue('');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
},
|
||||
[setSearchValue, onChange, onClose, searchValue],
|
||||
);
|
||||
|
||||
const handleClear = useCallback(() => {
|
||||
setSearchValue('');
|
||||
}, [setSearchValue]);
|
||||
|
||||
const isSearching = searchValue !== '';
|
||||
|
||||
useEffect(() => {
|
||||
const handleDocumentClick = (e: MouseEvent) => {
|
||||
if (
|
||||
nodeRef.current &&
|
||||
e.target instanceof HTMLElement &&
|
||||
!nodeRef.current.contains(e.target)
|
||||
) {
|
||||
onClose();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleDocumentClick, { capture: true });
|
||||
|
||||
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
|
||||
// to wait for a frame before focusing
|
||||
requestAnimationFrame(() => {
|
||||
if (nodeRef.current) {
|
||||
const element = nodeRef.current.querySelector<HTMLInputElement>(
|
||||
'input[type="search"]',
|
||||
);
|
||||
if (element) element.focus();
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick);
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
const results = useMemo(() => {
|
||||
if (searchValue === '') {
|
||||
return [...languages].sort((a, b) => {
|
||||
if (guess && a[0] === guess) {
|
||||
// Push guessed language higher than current selection
|
||||
return -1;
|
||||
} else if (guess && b[0] === guess) {
|
||||
return 1;
|
||||
} else if (a[0] === value) {
|
||||
// Push current selection to the top of the list
|
||||
return -1;
|
||||
} else if (b[0] === value) {
|
||||
return 1;
|
||||
} else {
|
||||
// Sort according to frequently used languages
|
||||
|
||||
const indexOfA = frequentlyUsedLanguages.indexOf(a[0]);
|
||||
const indexOfB = frequentlyUsedLanguages.indexOf(b[0]);
|
||||
|
||||
return (
|
||||
(indexOfA > -1 ? indexOfA : Infinity) -
|
||||
(indexOfB > -1 ? indexOfB : Infinity)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return fuzzysort
|
||||
.go(searchValue, languages, {
|
||||
keys: ['0', '1', '2'],
|
||||
limit: 5,
|
||||
threshold: -10000,
|
||||
})
|
||||
.map((result) => result.obj);
|
||||
}, [searchValue, languages, guess, frequentlyUsedLanguages, value]);
|
||||
|
||||
return (
|
||||
<div ref={nodeRef}>
|
||||
<div className='emoji-mart-search'>
|
||||
<input
|
||||
type='search'
|
||||
value={searchValue}
|
||||
onChange={handleSearchChange}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
placeholder={intl.formatMessage(messages.search)}
|
||||
/>
|
||||
<button
|
||||
type='button'
|
||||
className='emoji-mart-search-icon'
|
||||
disabled={!isSearching}
|
||||
aria-label={intl.formatMessage(messages.clear)}
|
||||
onClick={handleClear}
|
||||
>
|
||||
<Icon id='' icon={!isSearching ? SearchIcon : CancelIcon} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className='language-dropdown__dropdown__results emoji-mart-scroll'
|
||||
role='listbox'
|
||||
ref={listNodeRef}
|
||||
>
|
||||
{results.map((lang) => (
|
||||
<div
|
||||
key={lang[0]}
|
||||
role='option'
|
||||
tabIndex={0}
|
||||
data-index={lang[0]}
|
||||
className={classNames(
|
||||
'language-dropdown__dropdown__results__item',
|
||||
{ active: lang[0] === value },
|
||||
)}
|
||||
aria-selected={lang[0] === value}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
>
|
||||
<span
|
||||
className='language-dropdown__dropdown__results__item__native-name'
|
||||
lang={lang[0]}
|
||||
>
|
||||
{lang[2]}
|
||||
</span>{' '}
|
||||
<span className='language-dropdown__dropdown__results__item__common-name'>
|
||||
({lang[1]})
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const LanguageDropdown: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [placement, setPlacement] = useState<Placement | undefined>('bottom');
|
||||
const [guess, setGuess] = useState('');
|
||||
const activeElementRef = useRef<HTMLElement | null>(null);
|
||||
const targetRef = useRef(null);
|
||||
|
||||
const intl = useIntl();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const value = useAppSelector(
|
||||
(state) => state.compose.get('language') as string,
|
||||
);
|
||||
const text = useAppSelector((state) => state.compose.get('text') as string);
|
||||
|
||||
const current =
|
||||
(preloadedLanguages as Language[]).find((lang) => lang[0] === value) ?? [];
|
||||
|
||||
const handleMouseDown = useCallback(() => {
|
||||
if (!open && document.activeElement instanceof HTMLElement) {
|
||||
activeElementRef.current = document.activeElement;
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
if (open && activeElementRef.current)
|
||||
activeElementRef.current.focus({ preventScroll: true });
|
||||
|
||||
setOpen(!open);
|
||||
}, [open, setOpen]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (open && activeElementRef.current)
|
||||
activeElementRef.current.focus({ preventScroll: true });
|
||||
|
||||
setOpen(false);
|
||||
}, [open, setOpen]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string) => {
|
||||
dispatch(changeComposeLanguage(value));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleOverlayEnter = useCallback(
|
||||
(state: Partial<State>) => {
|
||||
setPlacement(state.placement);
|
||||
},
|
||||
[setPlacement],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (text.length > 20) {
|
||||
debouncedGuess(text, setGuess);
|
||||
} else {
|
||||
setGuess('');
|
||||
}
|
||||
}, [text, setGuess]);
|
||||
|
||||
return (
|
||||
<div ref={targetRef}>
|
||||
<button
|
||||
type='button'
|
||||
title={intl.formatMessage(messages.changeLanguage)}
|
||||
aria-expanded={open}
|
||||
onClick={handleToggle}
|
||||
onMouseDown={handleMouseDown}
|
||||
className={classNames('dropdown-button', {
|
||||
active: open,
|
||||
warning: guess !== '' && guess !== value,
|
||||
})}
|
||||
>
|
||||
<Icon id='' icon={TranslateIcon} />
|
||||
<span className='dropdown-button__label'>{current[2] ?? value}</span>
|
||||
</button>
|
||||
|
||||
<Overlay
|
||||
show={open}
|
||||
offset={[5, 5]}
|
||||
placement={placement}
|
||||
flip
|
||||
target={targetRef}
|
||||
popperConfig={{ strategy: 'fixed', onFirstUpdate: handleOverlayEnter }}
|
||||
>
|
||||
{({ props, placement }) => (
|
||||
<div {...props}>
|
||||
<div
|
||||
className={`dropdown-animation language-dropdown__dropdown ${placement}`}
|
||||
>
|
||||
<LanguageDropdownMenu
|
||||
value={value}
|
||||
guess={guess}
|
||||
onClose={handleClose}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Overlay>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -11,7 +11,9 @@ import {
|
|||
insertExpirationCompose,
|
||||
insertFeaturedTagCompose,
|
||||
uploadCompose,
|
||||
} from '../../../actions/compose';
|
||||
} from 'mastodon/actions/compose';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
||||
import ComposeForm from '../components/compose_form';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
@ -29,6 +31,7 @@ const mapStateToProps = state => ({
|
|||
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
circleId: state.getIn(['compose', 'circle_id']),
|
||||
|
@ -41,8 +44,15 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit () {
|
||||
dispatch(submitCompose());
|
||||
onSubmit (missingAltText) {
|
||||
if (missingAltText) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_MISSING_ALT_TEXT',
|
||||
modalProps: {},
|
||||
}));
|
||||
} else {
|
||||
dispatch(submitCompose());
|
||||
}
|
||||
},
|
||||
|
||||
onClearSuggestions () {
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import lande from 'lande';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { changeComposeLanguage } from 'mastodon/actions/compose';
|
||||
|
||||
import LanguageDropdown from '../components/language_dropdown';
|
||||
|
||||
const getFrequentlyUsedLanguages = createSelector([
|
||||
state => state.getIn(['settings', 'frequentlyUsedLanguages'], ImmutableMap()),
|
||||
], languageCounters => (
|
||||
languageCounters.keySeq()
|
||||
.sort((a, b) => languageCounters.get(a) - languageCounters.get(b))
|
||||
.reverse()
|
||||
.toArray()
|
||||
));
|
||||
import { urlRegex } from './url_regex';
|
||||
|
||||
const ISO_639_MAP = {
|
||||
afr: 'af', // Afrikaans
|
||||
|
@ -71,38 +56,21 @@ const ISO_639_MAP = {
|
|||
vie: 'vi', // Vietnamese
|
||||
};
|
||||
|
||||
const debouncedLande = debounce((text) => lande(text), 500, { trailing: true });
|
||||
const guessLanguage = (text) => {
|
||||
text = text
|
||||
.replace(urlRegex, '')
|
||||
.replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '');
|
||||
|
||||
const detectedLanguage = createSelector([
|
||||
state => state.getIn(['compose', 'text']),
|
||||
], text => {
|
||||
if (text.length > 20) {
|
||||
const guesses = debouncedLande(text);
|
||||
if (!guesses)
|
||||
return '';
|
||||
const [lang, confidence] = lande(text)[0];
|
||||
|
||||
const [lang, confidence] = guesses[0];
|
||||
|
||||
if (confidence > 0.8) {
|
||||
if (confidence > 0.8)
|
||||
return ISO_639_MAP[lang];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
frequentlyUsedLanguages: getFrequentlyUsedLanguages(state),
|
||||
value: state.getIn(['compose', 'language']),
|
||||
guess: detectedLanguage(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (value) {
|
||||
dispatch(changeComposeLanguage(value));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LanguageDropdown);
|
||||
export const debouncedGuess = debounce((text, setGuess) => {
|
||||
setGuess(guessLanguage(text));
|
||||
}, 500, { maxWait: 1500, leading: true, trailing: true });
|
|
@ -55,11 +55,11 @@ export const Card = ({ id, source }) => {
|
|||
</div>
|
||||
|
||||
<div className='explore__suggestions__card__body'>
|
||||
<Link to={`/@${account.get('acct')}`}><Avatar account={account} size={48} /></Link>
|
||||
<Link to={`/@${account.get('acct')}`} data-hover-card-account={account.id}><Avatar account={account} size={48} /></Link>
|
||||
|
||||
<div className='explore__suggestions__card__body__main'>
|
||||
<div className='explore__suggestions__card__body__main__name-button'>
|
||||
<Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`}><DisplayName account={account} /></Link>
|
||||
<Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`} data-hover-card-account={account.id}><DisplayName account={account} /></Link>
|
||||
<IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
||||
<FollowButton accountId={account.get('id')} />
|
||||
</div>
|
||||
|
|
|
@ -145,13 +145,13 @@ const Card: React.FC<{
|
|||
/>
|
||||
|
||||
<div className='inline-follow-suggestions__body__scrollable__card__avatar'>
|
||||
<Link to={`/@${account?.acct}`}>
|
||||
<Link to={`/@${account?.acct}`} data-hover-card-account={account?.id}>
|
||||
<Avatar account={account} size={72} />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className='inline-follow-suggestions__body__scrollable__card__text-stack'>
|
||||
<Link to={`/@${account?.acct}`}>
|
||||
<Link to={`/@${account?.acct}`} data-hover-card-account={account?.id}>
|
||||
<DisplayName account={account} />
|
||||
</Link>
|
||||
{firstVerifiedField ? (
|
||||
|
|
|
@ -56,14 +56,6 @@ export const ConfirmationModal: React.FC<
|
|||
|
||||
<div className='safety-action-modal__bottom'>
|
||||
<div className='safety-action-modal__actions'>
|
||||
{secondary && (
|
||||
<>
|
||||
<Button onClick={handleSecondary}>{secondary}</Button>
|
||||
|
||||
<div className='spacer' />
|
||||
</>
|
||||
)}
|
||||
|
||||
<button onClick={handleCancel} className='link-button'>
|
||||
<FormattedMessage
|
||||
id='confirmation_modal.cancel'
|
||||
|
@ -71,6 +63,15 @@ export const ConfirmationModal: React.FC<
|
|||
/>
|
||||
</button>
|
||||
|
||||
{secondary && (
|
||||
<>
|
||||
<div className='spacer' />
|
||||
<button onClick={handleSecondary} className='link-button'>
|
||||
{secondary}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
|
||||
<Button onClick={handleClick} autoFocus>
|
||||
{confirm}
|
||||
|
|
|
@ -10,3 +10,4 @@ export { ConfirmUnfollowModal } from './unfollow';
|
|||
export { ConfirmClearNotificationsModal } from './clear_notifications';
|
||||
export { ConfirmLogOutModal } from './log_out';
|
||||
export { ConfirmFollowToListModal } from './follow_to_list';
|
||||
export { ConfirmMissingAltTextModal } from './missing_alt_text';
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import { submitCompose } from 'mastodon/actions/compose';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.missing_alt_text.title',
|
||||
defaultMessage: 'Add alt text?',
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.missing_alt_text.confirm',
|
||||
defaultMessage: 'Add alt text',
|
||||
},
|
||||
message: {
|
||||
id: 'confirmations.missing_alt_text.message',
|
||||
defaultMessage:
|
||||
'Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.',
|
||||
},
|
||||
secondary: {
|
||||
id: 'confirmations.missing_alt_text.secondary',
|
||||
defaultMessage: 'Post anyway',
|
||||
},
|
||||
});
|
||||
|
||||
export const ConfirmMissingAltTextModal: React.FC<
|
||||
BaseConfirmationModalProps
|
||||
> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const mediaId = useAppSelector(
|
||||
(state) =>
|
||||
(
|
||||
(state.compose as ImmutableMap<string, unknown>).get(
|
||||
'media_attachments',
|
||||
) as ImmutableList<MediaAttachment>
|
||||
)
|
||||
.find(
|
||||
(media) =>
|
||||
['image', 'gifv'].includes(media.get('type') as string) &&
|
||||
((media.get('description') ?? '') as string).length === 0,
|
||||
)
|
||||
?.get('id') as string,
|
||||
);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
dispatch(
|
||||
openModal({
|
||||
modalType: 'FOCAL_POINT',
|
||||
modalProps: {
|
||||
mediaId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}, [dispatch, mediaId]);
|
||||
|
||||
const handleSecondary = useCallback(() => {
|
||||
dispatch(submitCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
message={intl.formatMessage(messages.message)}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
secondary={intl.formatMessage(messages.secondary)}
|
||||
onConfirm={handleConfirm}
|
||||
onSecondary={handleSecondary}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -43,6 +43,7 @@ import {
|
|||
ConfirmClearNotificationsModal,
|
||||
ConfirmLogOutModal,
|
||||
ConfirmFollowToListModal,
|
||||
ConfirmMissingAltTextModal,
|
||||
} from './confirmation_modals';
|
||||
import ImageModal from './image_modal';
|
||||
import MediaModal from './media_modal';
|
||||
|
@ -67,6 +68,7 @@ export const MODAL_COMPONENTS = {
|
|||
'CONFIRM_CLEAR_NOTIFICATIONS': () => Promise.resolve({ default: ConfirmClearNotificationsModal }),
|
||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'MUTE': MuteModal,
|
||||
'BLOCK': BlockModal,
|
||||
'DOMAIN_BLOCK': DomainBlockModal,
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
* @property {boolean} bookmark_category_needed
|
||||
* @property {boolean=} boost_modal
|
||||
* @property {boolean=} delete_modal
|
||||
* @property {boolean=} missing_alt_text_modal
|
||||
* @property {boolean=} disable_swiping
|
||||
* @property {boolean=} disable_hover_cards
|
||||
* @property {string=} disabled_account_id
|
||||
|
@ -130,6 +131,7 @@ export const autoPlayGif = getMeta('auto_play_gif');
|
|||
export const bookmarkCategoryNeeded = getMeta('bookmark_category_needed');
|
||||
export const boostModal = getMeta('boost_modal');
|
||||
export const deleteModal = getMeta('delete_modal');
|
||||
export const missingAltTextModal = getMeta('missing_alt_text_modal');
|
||||
export const disableSwiping = getMeta('disable_swiping');
|
||||
export const disableHoverCards = getMeta('disable_hover_cards');
|
||||
export const disabledAccountId = getMeta('disabled_account_id');
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
"alert.unexpected.message": "لقد طرأ خطأ غير متوقّع.",
|
||||
"alert.unexpected.title": "المعذرة!",
|
||||
"alt_text_badge.title": "نص بديل",
|
||||
"alt_text_modal.cancel": "إلغاء",
|
||||
"announcement.announcement": "إعلان",
|
||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||
"attachments_list.unprocessed": "(غير معالَج)",
|
||||
|
@ -120,13 +121,16 @@
|
|||
"column.blocks": "المُستَخدِمون المَحظورون",
|
||||
"column.bookmarks": "الفواصل المرجعية",
|
||||
"column.community": "الخيط الزمني المحلي",
|
||||
"column.create_list": "إنشاء القائمة",
|
||||
"column.direct": "الإشارات الخاصة",
|
||||
"column.directory": "تَصَفُّحُ المَلفات الشخصية",
|
||||
"column.domain_blocks": "النطاقات المحظورة",
|
||||
"column.edit_list": "تعديل القائمة",
|
||||
"column.favourites": "المفضلة",
|
||||
"column.firehose": "الموجزات الحية",
|
||||
"column.follow_requests": "طلبات المتابعة",
|
||||
"column.home": "الرئيسية",
|
||||
"column.list_members": "إدارة أعضاء القائمة",
|
||||
"column.lists": "القوائم",
|
||||
"column.mutes": "المُستَخدِمون المَكتومون",
|
||||
"column.notifications": "الإشعارات",
|
||||
|
@ -139,6 +143,7 @@
|
|||
"column_header.pin": "تثبيت",
|
||||
"column_header.show_settings": "إظهار الإعدادات",
|
||||
"column_header.unpin": "إلغاء التَّثبيت",
|
||||
"column_search.cancel": "إلغاء",
|
||||
"column_subheading.settings": "الإعدادات",
|
||||
"community.column_settings.local_only": "المحلي فقط",
|
||||
"community.column_settings.media_only": "الوسائط فقط",
|
||||
|
@ -157,6 +162,7 @@
|
|||
"compose_form.poll.duration": "مُدَّة اِستطلاع الرأي",
|
||||
"compose_form.poll.multiple": "متعدد الخيارات",
|
||||
"compose_form.poll.option_placeholder": "الخيار {number}",
|
||||
"compose_form.poll.single": "خيار واحد",
|
||||
"compose_form.poll.switch_to_multiple": "تغيِير الاستطلاع للسماح باِخيارات مُتعدِّدة",
|
||||
"compose_form.poll.switch_to_single": "تغيِير الاستطلاع للسماح باِخيار واحد فقط",
|
||||
"compose_form.poll.type": "الطراز",
|
||||
|
@ -195,6 +201,7 @@
|
|||
"confirmations.unfollow.title": "إلغاء متابعة المستخدم؟",
|
||||
"content_warning.hide": "إخفاء المنشور",
|
||||
"content_warning.show": "إظهار على أي حال",
|
||||
"content_warning.show_more": "إظهار المزيد",
|
||||
"conversation.delete": "احذف المحادثة",
|
||||
"conversation.mark_as_read": "اعتبرها كمقروءة",
|
||||
"conversation.open": "اعرض المحادثة",
|
||||
|
@ -325,6 +332,7 @@
|
|||
"footer.privacy_policy": "سياسة الخصوصية",
|
||||
"footer.source_code": "الاطلاع على الشفرة المصدرية",
|
||||
"footer.status": "الحالة",
|
||||
"footer.terms_of_service": "شروط الخدمة",
|
||||
"generic.saved": "تم الحفظ",
|
||||
"getting_started.heading": "استعدّ للبدء",
|
||||
"hashtag.column_header.tag_mode.all": "و {additional}",
|
||||
|
@ -358,6 +366,7 @@
|
|||
"ignore_notifications_modal.ignore": "تجاهل الإشعارات",
|
||||
"ignore_notifications_modal.limited_accounts_title": "تجاهل الإشعارات من الحسابات التي هي تحت الإشراف؟",
|
||||
"ignore_notifications_modal.new_accounts_title": "تجاهل الإشعارات الصادرة من الحسابات الجديدة؟",
|
||||
"interaction_modal.no_account_yet": "لا تملك حساباً بعد؟",
|
||||
"interaction_modal.on_another_server": "على خادم مختلف",
|
||||
"interaction_modal.on_this_server": "على هذا الخادم",
|
||||
"interaction_modal.title.favourite": "إضافة منشور {name} إلى المفضلة",
|
||||
|
@ -408,11 +417,21 @@
|
|||
"limited_account_hint.title": "تم إخفاء هذا الملف الشخصي من قبل مشرفي {domain}.",
|
||||
"link_preview.author": "مِن {name}",
|
||||
"link_preview.more_from_author": "المزيد من {name}",
|
||||
"lists.add_member": "إضافة",
|
||||
"lists.add_to_list": "إضافة إلى القائمة",
|
||||
"lists.add_to_lists": "إضافة {name} إلى القوائم",
|
||||
"lists.create": "إنشاء",
|
||||
"lists.create_list": "إنشاء قائمة",
|
||||
"lists.delete": "احذف القائمة",
|
||||
"lists.done": "تمّ",
|
||||
"lists.edit": "عدّل القائمة",
|
||||
"lists.exclusive": "إخفاء الأعضاء في الصفحة الرئيسية",
|
||||
"lists.remove_member": "إزالة",
|
||||
"lists.replies_policy.followed": "أي مستخدم متابَع",
|
||||
"lists.replies_policy.list": "أعضاء القائمة",
|
||||
"lists.replies_policy.none": "لا أحد",
|
||||
"lists.save": "حفظ",
|
||||
"lists.search": "بحث",
|
||||
"load_pending": "{count, plural, one {# عنصر جديد} other {# عناصر جديدة}}",
|
||||
"loading_indicator.label": "جاري التحميل…",
|
||||
"media_gallery.hide": "إخفاء",
|
||||
|
@ -464,7 +483,7 @@
|
|||
"notification.label.private_reply": "رد خاص",
|
||||
"notification.label.reply": "ردّ",
|
||||
"notification.mention": "إشارة",
|
||||
"notification.mentioned_you": "{name} mentioned you",
|
||||
"notification.mentioned_you": "أشارَ إليك {name}",
|
||||
"notification.moderation-warning.learn_more": "اعرف المزيد",
|
||||
"notification.moderation_warning": "لقد تلقيت تحذيرًا بالإشراف",
|
||||
"notification.moderation_warning.action_delete_statuses": "تم حذف بعض من منشوراتك.",
|
||||
|
@ -740,6 +759,7 @@
|
|||
"subscribed_languages.target": "تغيير اللغات المشتركة لـ {target}",
|
||||
"tabs_bar.home": "الرئيسية",
|
||||
"tabs_bar.notifications": "الإشعارات",
|
||||
"terms_of_service.title": "شروط الخدمة",
|
||||
"time_remaining.days": "{number, plural, one {# يوم} other {# أيام}} متبقية",
|
||||
"time_remaining.hours": "{number, plural, one {# ساعة} other {# ساعات}} متبقية",
|
||||
"time_remaining.minutes": "{number, plural, one {# دقيقة} other {# دقائق}} متبقية",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Възникна неочаквана грешка.",
|
||||
"alert.unexpected.title": "Опаа!",
|
||||
"alt_text_badge.title": "Алтернативен текст",
|
||||
"alt_text_modal.add_alt_text": "Добавяне на алтернативен текст",
|
||||
"alt_text_modal.add_text_from_image": "Добавяне на текст от образ",
|
||||
"alt_text_modal.cancel": "Отказ",
|
||||
"alt_text_modal.change_thumbnail": "Промяна на миниобраза",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Опишете това за хора със слухови увреждания…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Опишете това за хора със зрителни увреждания…",
|
||||
"alt_text_modal.done": "Готово",
|
||||
"announcement.announcement": "Оповестяване",
|
||||
"annual_report.summary.archetype.booster": "Якият подсилвател",
|
||||
"annual_report.summary.archetype.lurker": "Дебнещото",
|
||||
|
@ -407,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Пренебрегвате ли известията от хора, които не са ви последвали?",
|
||||
"ignore_notifications_modal.not_following_title": "Пренебрегвате ли известията от хора, които не сте последвали?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Пренебрегвате ли известия от непоискани лични споменавания?",
|
||||
"info_button.label": "Помощ",
|
||||
"info_button.what_is_alt_text": "<h1>Какво е алтернативен текст?</h1> <p>Алтернативният текст осигурява описания на изображение за хора със зрителни увреждания, връзки с ниска честотна лента или търсещите допълнителен контекст.</p> <p>Може да подобрите достъпността и разбираемостта за всеки, пишейки ясен, кратък и обективен алтернативен текст.</p> <ul> <li>Уловете важните елементи</li> <li>Обобщете текста в образите</li> <li>Употребявайте правилна структура на изречението</li> <li>Избягвайте излишна информация</li> <li>Съсредоточете се върху тенденциите и ключови констатации в сложни онагледявания (като диаграми и карти)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Трябва да направите любимо от акаунта си, за да продължите.",
|
||||
"interaction_modal.action.follow": "Трябва да последвате от акаунта си, за да продължите.",
|
||||
"interaction_modal.action.reblog": "Трябва да разпространите нечий блог от акаунта си, за да продължите.",
|
||||
|
|
|
@ -90,8 +90,8 @@
|
|||
"alt_text_modal.add_text_from_image": "Afegiu text d'una imatge",
|
||||
"alt_text_modal.cancel": "Cancel·la",
|
||||
"alt_text_modal.change_thumbnail": "Canvia la miniatura",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Descriu això per a persones amb problemes d'audició…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Descriu això per a persones amb problemes visuals…",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Descriviu això per a persones amb problemes d'audició…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Descriviu això per a persones amb problemes visuals…",
|
||||
"alt_text_modal.done": "Fet",
|
||||
"announcement.announcement": "Anunci",
|
||||
"annual_report.summary.archetype.booster": "Sempre a la moda",
|
||||
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Tanca la sessió",
|
||||
"confirmations.logout.message": "Segur que vols tancar la sessió?",
|
||||
"confirmations.logout.title": "Tancar la sessió?",
|
||||
"confirmations.missing_alt_text.confirm": "Afegiu text alternatiu",
|
||||
"confirmations.missing_alt_text.message": "La vostra publicació té contingut sense text alternatiu. Afegir-hi descripcions la farà accessible a més persones.",
|
||||
"confirmations.missing_alt_text.secondary": "Publica-la igualment",
|
||||
"confirmations.missing_alt_text.title": "Hi voleu afegir text alternatiu?",
|
||||
"confirmations.mute.confirm": "Silencia",
|
||||
"confirmations.redraft.confirm": "Esborra i reescriu",
|
||||
"confirmations.redraft.message": "Segur que vols eliminar aquest tut i tornar a escriure'l? Es perdran tots els impulsos i els favorits, i les respostes al tut original quedaran aïllades.",
|
||||
|
@ -414,6 +418,7 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Voleu ignorar les notificacions de qui no us segueix?",
|
||||
"ignore_notifications_modal.not_following_title": "Voleu ignorar les notificacions de qui no seguiu?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Voleu ignorar les notificacions de mencions privades no sol·licitades?",
|
||||
"info_button.label": "Ajuda",
|
||||
"interaction_modal.action.favourite": "Per a continuar heu d'afavorir des del vostre compte.",
|
||||
"interaction_modal.action.follow": "Per a continuar heu de seguir des del vostre compte.",
|
||||
"interaction_modal.action.reblog": "Per a continuar heu d'impulsar des del vostre compte.",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Objevila se neočekávaná chyba.",
|
||||
"alert.unexpected.title": "Jejda!",
|
||||
"alt_text_badge.title": "Popisek",
|
||||
"alt_text_modal.add_alt_text": "Přidat alt text",
|
||||
"alt_text_modal.add_text_from_image": "Přidat text z obrázku",
|
||||
"alt_text_modal.cancel": "Zrušit",
|
||||
"alt_text_modal.change_thumbnail": "Změnit miniaturu",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Popište to pro osoby se sluchovým postižením…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Popište to pro osoby se zrakovým postižením…",
|
||||
"alt_text_modal.done": "Hotovo",
|
||||
"announcement.announcement": "Oznámení",
|
||||
"annual_report.summary.archetype.booster": "Lovec obsahu",
|
||||
"annual_report.summary.archetype.lurker": "Špión",
|
||||
|
@ -407,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignorovat oznámení od lidí, kteří vás nesledují?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorovat oznámení od lidí, které nesledujete?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorovat oznámení z nevyžádaných soukromých zmínek?",
|
||||
"info_button.label": "Nápověda",
|
||||
"info_button.what_is_alt_text": "<h1>Co je to alt text?</h1> <p>Alt text poskytuje popis obrázků pro lidi se zrakovými postižením, špatným připojením něbo těm, kteří potřebují více kontextu.</p> <p>Můžete zlepšit přístupnost a porozumění napsáním jasného, stručného a objektivního alt textu.</p> <ul> <li>Zachyťte důležité prvky</li> <li>Shrňte text v obrázku</li> <li>Použijte pravidelnou větnou skladbu</li> <li>Vyhněte se nadbytečným informacím</li> <li>U komplexních vizualizací (diagramy, mapy...) se zaměřte na trendy a klíčová zjištění</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Chcete-li pokračovat, musíte oblíbit z vašeho účtu.",
|
||||
"interaction_modal.action.follow": "Chcete-li pokračovat, musíte sledovat z vašeho účtu.",
|
||||
"interaction_modal.action.reblog": "Chcete-li pokračovat, musíte dát boost z vašeho účtu.",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Log ud",
|
||||
"confirmations.logout.message": "Er du sikker på, at du vil logge ud?",
|
||||
"confirmations.logout.title": "Log ud?",
|
||||
"confirmations.missing_alt_text.confirm": "Tilføj alt-tekst",
|
||||
"confirmations.missing_alt_text.message": "Indlægget indeholder medier uden alt-tekst. Tilføjelse af beskrivelser bidrager til at gøre indholdet tilgængeligt for flere brugere.",
|
||||
"confirmations.missing_alt_text.secondary": "Læg op alligevel",
|
||||
"confirmations.missing_alt_text.title": "Tilføj alt-tekst?",
|
||||
"confirmations.mute.confirm": "Skjul (mute)",
|
||||
"confirmations.redraft.confirm": "Slet og omformulér",
|
||||
"confirmations.redraft.message": "Sikker på, at dette indlæg skal slettes og omskrives? Favoritter og boosts går tabt, og svar til det oprindelige indlæg mister tilknytningen.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignorér notifikationer fra folk, som ikke er følgere?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorér notifikationer fra folk, man ikke følger?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorér notifikationer fra uopfordrede Private omtaler?",
|
||||
"info_button.label": "Hjælp",
|
||||
"info_button.what_is_alt_text": "<h1>Hvad er alt-tekst?</h1> <p>Alt-tekst leverer billedbeskrivelser til folk med synsnedsættelser, lav båndbredde-forbindelser eller med ønske om ekstra kontekst.</p> <p>Tilgængelighed og forståelse kan forbedres for alle ved at skrive klar, kortfattet og objektiv alt-tekst.</p> <ul> <li>Fang vigtige elementer</li> <li>Opsummér tekst i billeder</li> <li>Brug almindelig sætningsstruktur</li> <li>Undgå overflødig information</li> <li>Fokusér på tendenser og centrale resultater i kompleks grafik (såsom diagrammer eller kort)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "For at fortsætte, skal man vælge Gør til favorit fra sin konto.",
|
||||
"interaction_modal.action.follow": "For at fortsætte, skal man vælge Følg fra sin konto.",
|
||||
"interaction_modal.action.reblog": "For at fortsætte, skal man vælge Genblog fra sin konto.",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"about.disclaimer": "Mastodon ist eine freie, quelloffene Software und eine Marke der Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Grund unbekannt",
|
||||
"about.domain_blocks.preamble": "Mastodon erlaubt es dir grundsätzlich, alle Inhalte von allen Nutzer*innen auf allen Servern im Fediverse zu sehen und mit ihnen zu interagieren. Für diesen Server gibt es aber ein paar Ausnahmen.",
|
||||
"about.domain_blocks.silenced.explanation": "Alle Inhalte und Profile dieses Servers werden zunächst nicht angezeigt. Du kannst die Profile und Inhalte aber dennoch sehen, wenn du explizit nach diesen suchst oder diesen folgst.",
|
||||
"about.domain_blocks.silenced.explanation": "Standardmäßig werden von diesem Server keine Inhalte oder Profile angezeigt. Du kannst die Profile und Inhalte aber dennoch sehen, wenn du explizit nach diesen suchst oder diesen folgst.",
|
||||
"about.domain_blocks.silenced.title": "Stummgeschaltet",
|
||||
"about.domain_blocks.suspended.explanation": "Es werden keine Daten von diesem Server verarbeitet, gespeichert oder ausgetauscht, sodass eine Interaktion oder Kommunikation mit Nutzer*innen dieses Servers nicht möglich ist.",
|
||||
"about.domain_blocks.suspended.title": "Gesperrt",
|
||||
|
@ -42,8 +42,8 @@
|
|||
"account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden",
|
||||
"account.in_memoriam": "Zum Andenken.",
|
||||
"account.joined_short": "Mitglied seit",
|
||||
"account.languages": "Ausgewählte Sprachen ändern",
|
||||
"account.link_verified_on": "Das Profil mit dieser E-Mail-Adresse wurde bereits am {date} bestätigt",
|
||||
"account.languages": "Sprache ändern.",
|
||||
"account.link_verified_on": "Das Profil mit dieser E-Mail-Adresse wurde bereits am {date} verifiziert",
|
||||
"account.locked_info": "Die Privatsphäre dieses Kontos wurde auf „geschützt“ gesetzt. Die Person bestimmt manuell, wer ihrem Profil folgen darf.",
|
||||
"account.media": "Medien",
|
||||
"account.mention": "@{name} erwähnen",
|
||||
|
@ -63,7 +63,7 @@
|
|||
"account.share": "Profil von @{name} teilen",
|
||||
"account.show_reblogs": "Geteilte Beiträge von @{name} anzeigen",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
|
||||
"account.unblock": "Blockierung von @{name} aufheben",
|
||||
"account.unblock": "{name} nicht mehr blockieren",
|
||||
"account.unblock_domain": "Blockierung von {domain} aufheben",
|
||||
"account.unblock_short": "Blockierung aufheben",
|
||||
"account.unendorse": "Im Profil nicht mehr empfehlen",
|
||||
|
@ -72,12 +72,12 @@
|
|||
"account.unmute_notifications_short": "Stummschaltung der Benachrichtigungen aufheben",
|
||||
"account.unmute_short": "Stummschaltung aufheben",
|
||||
"account_note.placeholder": "Klicken, um Notiz hinzuzufügen",
|
||||
"admin.dashboard.daily_retention": "Verweildauer der Benutzer*innen pro Tag nach der Registrierung",
|
||||
"admin.dashboard.monthly_retention": "Verweildauer der Benutzer*innen pro Monat nach der Registrierung",
|
||||
"admin.dashboard.daily_retention": "Verweildauer der Nutzer*innen pro Tag nach der Registrierung",
|
||||
"admin.dashboard.monthly_retention": "Verweildauer der Nutzer*innen pro Monat nach der Registrierung",
|
||||
"admin.dashboard.retention.average": "Durchschnitt",
|
||||
"admin.dashboard.retention.cohort": "Monat der Registrierung",
|
||||
"admin.dashboard.retention.cohort_size": "Neue Konten",
|
||||
"admin.impact_report.instance_accounts": "Kontenprofile, die dadurch gelöscht würden",
|
||||
"admin.impact_report.instance_accounts": "Profilkonten, die dadurch gelöscht würden",
|
||||
"admin.impact_report.instance_followers": "Follower, die unsere Nutzer*innen verlieren würden",
|
||||
"admin.impact_report.instance_follows": "Follower, die deren Nutzer*innen verlieren würden",
|
||||
"admin.impact_report.title": "Zusammenfassung der Auswirkung",
|
||||
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Abmelden",
|
||||
"confirmations.logout.message": "Möchtest du dich wirklich abmelden?",
|
||||
"confirmations.logout.title": "Abmelden?",
|
||||
"confirmations.missing_alt_text.confirm": "Bildbeschreibung hinzufügen",
|
||||
"confirmations.missing_alt_text.message": "Dein Beitrag enthält Medien ohne Bildbeschreibung. Mit Alt-Texten erreichst du auch Menschen mit einer Sehschwäche.",
|
||||
"confirmations.missing_alt_text.secondary": "Trotzdem veröffentlichen",
|
||||
"confirmations.missing_alt_text.title": "Bildbeschreibung hinzufügen?",
|
||||
"confirmations.mute.confirm": "Stummschalten",
|
||||
"confirmations.redraft.confirm": "Löschen und neu erstellen",
|
||||
"confirmations.redraft.message": "Möchtest du diesen Beitrag wirklich löschen und neu verfassen? Alle Favoriten sowie die bisher geteilten Beiträge werden verloren gehen und Antworten auf den ursprünglichen Beitrag verlieren den Zusammenhang.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Benachrichtigungen von Profilen ignorieren, die dir nicht folgen?",
|
||||
"ignore_notifications_modal.not_following_title": "Benachrichtigungen von Profilen ignorieren, denen du nicht folgst?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Benachrichtigungen von unerwünschten privaten Erwähnungen ignorieren?",
|
||||
"info_button.label": "Hilfe",
|
||||
"info_button.what_is_alt_text": "<h1>Was ist Alt-Text?</h1> <p>Alt-Text bietet Bildbeschreibungen für Personen mit einer Sehschwäche, einer schlechten Internetverbindung und für alle, die zusätzlichen Kontext möchten.</p> <p>Du kannst die Zugänglichkeit und die Verständlichkeit für alle verbessern, indem du eine klare, genaue und objektive Bildbeschreibung hinzufügst.</p> <ul> <li>Erfasse wichtige Elemente</li> <li>Fasse Text in Bildern zusammen</li> <li>Verwende einen korrekten Satzbau</li> <li>Vermeide unwichtige Informationen</li> <li>Konzentriere dich bei komplexen Darstellungen (z. B. Diagramme oder Karten) auf Trends und wichtige Erkenntnisse</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Du musst von deinem Konto aus favorisieren, um fortzufahren.",
|
||||
"interaction_modal.action.follow": "Du musst von deinem Konto aus folgen, um fortzufahren.",
|
||||
"interaction_modal.action.reblog": "Du musst von deinem Konto aus teilen, um fortzufahren.",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Προέκυψε απροσδόκητο σφάλμα.",
|
||||
"alert.unexpected.title": "Ουπς!",
|
||||
"alt_text_badge.title": "Εναλλακτικό κείμενο",
|
||||
"alt_text_modal.add_alt_text": "Προσθήκη εναλλακτικού κειμένου",
|
||||
"alt_text_modal.add_text_from_image": "Προσθήκη κειμένου από εικόνα",
|
||||
"alt_text_modal.cancel": "Ακύρωση",
|
||||
"alt_text_modal.change_thumbnail": "Αλλαγή μικρογραφίας",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Περιέγραψε αυτό για άτομα με προβλήματα ακοής…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Περιέγραψε αυτό για άτομα με προβλήματα όρασης…",
|
||||
"alt_text_modal.done": "Ολοκληρώθηκε",
|
||||
"announcement.announcement": "Ανακοίνωση",
|
||||
"annual_report.summary.archetype.booster": "Ο κυνηγός των φοβερών",
|
||||
"annual_report.summary.archetype.lurker": "Ο διακριτικός",
|
||||
|
@ -407,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Αγνόηση ειδοποιήσεων από άτομα που δε σας ακολουθούν;",
|
||||
"ignore_notifications_modal.not_following_title": "Αγνόηση ειδοποιήσεων από άτομα που δεν ακολουθείς;",
|
||||
"ignore_notifications_modal.private_mentions_title": "Αγνόηση ειδοποιήσεων από μη ζητηθείσες ιδιωτικές αναφορές;",
|
||||
"info_button.label": "Βοήθεια",
|
||||
"info_button.what_is_alt_text": "Το εναλλακτικό κείμενο παρέχει περιγραφές εικόνας για άτομα με προβλήματα όρασης, διαδικτυακές συνδέσεις χαμηλής ταχύτητας ή για άτομα που αναζητούν επιπλέον περιεχόμενο.\\n\\nΜπορείς να βελτιώσεις την προσβασιμότητα και την κατανόηση για όλους, γράφοντας σαφές, συνοπτικό και αντικειμενικό εναλλακτικό κείμενο.\\n\\n<ul><li>Κατέγραψε σημαντικά στοιχεία</li>\\n<li>Συνόψισε το κείμενο στις εικόνες</li>\\n<li>Χρησιμοποίησε δομή κανονικής πρότασης</li>\\n<li>Απέφυγε περιττές πληροφορίες</li>\\n<li>Εστίασε στις τάσεις και τα βασικά ευρήματα σε σύνθετα οπτικά στοιχεία (όπως διαγράμματα ή χάρτες)</li></ul>",
|
||||
"interaction_modal.action.favourite": "Για να συνεχίσεις, θα πρέπει να αγαπήσεις από τον λογαριασμό σου.",
|
||||
"interaction_modal.action.follow": "Για να συνεχίσεις, θα πρέπει να ακολουθήσεις από τον λογαριασμό σου.",
|
||||
"interaction_modal.action.reblog": "Για να συνεχίσεις, θα πρέπει να αναδημοσιεύσεις από τον λογαριασμό σου.",
|
||||
|
@ -457,6 +466,7 @@
|
|||
"keyboard_shortcuts.toggle_hidden": "Εμφάνιση/απόκρυψη κειμένου πίσω από το CW",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Εμφάνιση/απόκρυψη πολυμέσων",
|
||||
"keyboard_shortcuts.toot": "Δημιουργία νέας ανάρτησης",
|
||||
"keyboard_shortcuts.translate": "να μεταφράσει μια δημοσίευση",
|
||||
"keyboard_shortcuts.unfocus": "Αποεστίαση του πεδίου σύνθεσης/αναζήτησης",
|
||||
"keyboard_shortcuts.up": "Μετακίνηση προς τα πάνω στη λίστα",
|
||||
"lightbox.close": "Κλείσιμο",
|
||||
|
@ -836,6 +846,7 @@
|
|||
"status.reblogs.empty": "Κανείς δεν ενίσχυσε αυτή την ανάρτηση ακόμα. Μόλις το κάνει κάποιος, θα εμφανιστεί εδώ.",
|
||||
"status.redraft": "Σβήσε & ξαναγράψε",
|
||||
"status.remove_bookmark": "Αφαίρεση σελιδοδείκτη",
|
||||
"status.remove_favourite": "Κατάργηση από τα αγαπημένα",
|
||||
"status.replied_in_thread": "Απαντήθηκε σε νήμα",
|
||||
"status.replied_to": "Απάντησε στον {name}",
|
||||
"status.reply": "Απάντησε",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "An unexpected error occurred.",
|
||||
"alert.unexpected.title": "Oops!",
|
||||
"alt_text_badge.title": "Alt text",
|
||||
"alt_text_modal.add_alt_text": "Add alt text",
|
||||
"alt_text_modal.add_text_from_image": "Add text from image",
|
||||
"alt_text_modal.cancel": "Cancel",
|
||||
"alt_text_modal.change_thumbnail": "Change thumbnail",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Describe this for people with hearing impairments…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Describe this for people with visual impairments…",
|
||||
"alt_text_modal.done": "Done",
|
||||
"announcement.announcement": "Announcement",
|
||||
"annual_report.summary.archetype.booster": "The cool-hunter",
|
||||
"annual_report.summary.archetype.lurker": "The lurker",
|
||||
|
@ -407,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignore notifications from people not following you?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignore notifications from people you don't follow?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?",
|
||||
"info_button.label": "Help",
|
||||
"info_button.what_is_alt_text": "<h1>What is alt text?</h1> <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p> <p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p> <ul> <li>Capture important elements</li> <li>Summarise text in images</li> <li>Use regular sentence structure</li> <li>Avoid redundant information</li> <li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "To continue, you need to favourite from your account.",
|
||||
"interaction_modal.action.follow": "To continue, you need to follow from your account.",
|
||||
"interaction_modal.action.reblog": "To continue, you need to reblog from your account.",
|
||||
|
|
|
@ -357,6 +357,10 @@
|
|||
"confirmations.logout.confirm": "Log out",
|
||||
"confirmations.logout.message": "Are you sure you want to log out?",
|
||||
"confirmations.logout.title": "Log out?",
|
||||
"confirmations.missing_alt_text.confirm": "Add alt text",
|
||||
"confirmations.missing_alt_text.message": "Your post contains media without alt text. Adding descriptions helps make your content accessible to more people.",
|
||||
"confirmations.missing_alt_text.secondary": "Post anyway",
|
||||
"confirmations.missing_alt_text.title": "Add alt text?",
|
||||
"confirmations.mute.confirm": "Mute",
|
||||
"confirmations.redraft.confirm": "Delete & redraft",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||
|
@ -559,6 +563,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignore notifications from people not following you?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignore notifications from people you don't follow?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?",
|
||||
"info_button.label": "Help",
|
||||
"info_button.what_is_alt_text": "<h1>What is alt text?</h1> <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p> <p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p> <ul> <li>Capture important elements</li> <li>Summarize text in images</li> <li>Use regular sentence structure</li> <li>Avoid redundant information</li> <li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "To continue, you need to favorite from your account.",
|
||||
"interaction_modal.action.follow": "To continue, you need to follow from your account.",
|
||||
"interaction_modal.action.reblog": "To continue, you need to reblog from your account.",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Elsaluti",
|
||||
"confirmations.logout.message": "Ĉu vi certas, ke vi volas elsaluti?",
|
||||
"confirmations.logout.title": "Ĉu elsaluti?",
|
||||
"confirmations.missing_alt_text.confirm": "Aldoni alttekston",
|
||||
"confirmations.missing_alt_text.message": "Via afiŝo enhavas amaskomunikilaron sen altteksto. Aldono de priskriboj helpas fari vian enhavon alirebla por pli da homoj.",
|
||||
"confirmations.missing_alt_text.secondary": "Afiŝu ĉiukaze",
|
||||
"confirmations.missing_alt_text.title": "Ĉu aldoni alttekston?",
|
||||
"confirmations.mute.confirm": "Silentigi",
|
||||
"confirmations.redraft.confirm": "Forigi kaj reskribi",
|
||||
"confirmations.redraft.message": "Ĉu vi certas ke vi volas forigi tiun afiŝon kaj reskribi ĝin? Ĉiuj diskonigoj kaj stelumoj estos perditaj, kaj respondoj al la originala mesaĝo estos senparentaj.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ĉu ignori sciigojn de homoj, kiuj ne sekvas vin?",
|
||||
"ignore_notifications_modal.not_following_title": "Ĉu ignori sciigojn de homoj, kiujn vi ne sekvas?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ĉu ignori sciigojn de nepetitaj privataj mencioj?",
|
||||
"info_button.label": "Helpo",
|
||||
"info_button.what_is_alt_text": "<h1>Kio estas la alternativa teksto?</h1> <p>La alternativa teksto ofertas priskribojn de la bildoj por individuoj kun vidaj malfacilaĵoj, konektoj kun malalta larĝa bando aŭ kiuj serĉas plian kuntekston.</p> <p>Vi povas plibonigi alireblecon kaj komprenon por ĉiuj per skribado de klaraj, koncizaj, kaj objektivaj alternativaj tekstoj.</p><ul><li>Kaptu gravajn elementojn.</li><li> Resumu tekston en bildoj.</li> <li>Uzu regulan frazstrukturon.</li> <li>Evitu redundan informon.</li><li>Fokusu sur tendencoj kaj ĉefaj trovoj en kompleksaj visualoj (kiel diagramoj aŭ mapoj).</li></ul>",
|
||||
"interaction_modal.action.favourite": "Por daŭrigi, vi devas stelumi el via konto.",
|
||||
"interaction_modal.action.follow": "Por daŭrigi, vi devas sekvi el via konto.",
|
||||
"interaction_modal.action.reblog": "Por daŭrigi, vi devas diskonigi el via konto.",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Cerrar sesión",
|
||||
"confirmations.logout.message": "¿Estás seguro que querés cerrar la sesión?",
|
||||
"confirmations.logout.title": "¿Cerrar sesión?",
|
||||
"confirmations.missing_alt_text.confirm": "Añadir texto alternativo",
|
||||
"confirmations.missing_alt_text.message": "Tu publicación contiene medios sin texto alternativo. Añadir descripciones ayuda a que tu contenido sea accesible para más personas.",
|
||||
"confirmations.missing_alt_text.secondary": "Publicar de todos modos",
|
||||
"confirmations.missing_alt_text.title": "¿Deseas añadir texto alternativo?",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.redraft.confirm": "Eliminar mensaje original y editarlo",
|
||||
"confirmations.redraft.message": "¿Estás seguro que querés eliminar este mensaje y volver a editarlo? Se perderán las veces marcadas como favorito y sus adhesiones, y las respuestas al mensaje original quedarán huérfanas.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de cuentas que no te siguen?",
|
||||
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de cuentas a las que no seguís?",
|
||||
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
|
||||
"info_button.label": "Ayuda",
|
||||
"info_button.what_is_alt_text": "<h1>¿Qué es el texto alternativo?</h1> <p>El texto alternativo proporciona descripciones de las imágenes para personas con dificultades visuales, conexiones con escaso ancho de banda o que buscan un contexto adicional.</p> <p>Podés mejorar la accesibilidad y la comprensión para todos escribiendo un texto alternativo claro, conciso y objetivo.</p> <ul><li>Captura los elementos importantes.</li><li>Resumí el texto en imágenes.</li><li>Usá una estructura de frases normal.</li><li>Evitá la información redundante.</li><li>Focalizate en las tendencias y conclusiones clave de los elementos visuales complejos (como diagramas o mapas).</li></ul>",
|
||||
"interaction_modal.action.favourite": "Para continuar, tenés que marcar como favorito desde tu cuenta.",
|
||||
"interaction_modal.action.follow": "Para continuar, tenés que seguir desde tu cuenta.",
|
||||
"interaction_modal.action.reblog": "Para continuar, tenés que adherir desde tu cuenta.",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
"account.enable_notifications": "Notificarme cuando @{name} publique algo",
|
||||
"account.endorse": "Destacar en mi perfil",
|
||||
"account.featured_tags.last_status_at": "Última publicación el {date}",
|
||||
"account.featured_tags.last_status_never": "No hay publicaciones",
|
||||
"account.featured_tags.last_status_never": "Sin publicaciones",
|
||||
"account.featured_tags.title": "Etiquetas destacadas de {name}",
|
||||
"account.follow": "Seguir",
|
||||
"account.follow_back": "Seguir también",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"column.about": "Acerca de",
|
||||
"column.blocks": "Usuarios bloqueados",
|
||||
"column.bookmarks": "Marcadores",
|
||||
"column.community": "Línea de tiempo local",
|
||||
"column.community": "Cronología local",
|
||||
"column.create_list": "Crear lista",
|
||||
"column.direct": "Menciones privadas",
|
||||
"column.directory": "Buscar perfiles",
|
||||
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Cerrar sesión",
|
||||
"confirmations.logout.message": "¿Estás seguro de que quieres cerrar la sesión?",
|
||||
"confirmations.logout.title": "¿Deseas cerrar sesión?",
|
||||
"confirmations.missing_alt_text.confirm": "Añadir texto alternativo",
|
||||
"confirmations.missing_alt_text.message": "Tu publicación contiene contenido multimedia sin texto alternativo. Agregar descripciones ayuda a que tu contenido sea accesible para más personas.",
|
||||
"confirmations.missing_alt_text.secondary": "Publicar de todas maneras",
|
||||
"confirmations.missing_alt_text.title": "¿Añadir texto alternativo?",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.redraft.confirm": "Borrar y volver a borrador",
|
||||
"confirmations.redraft.message": "¿Estás seguro que quieres borrar esta publicación y editarla? Los favoritos e impulsos se perderán, y las respuestas a la publicación original quedarán separadas.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te siguen?",
|
||||
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
|
||||
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
|
||||
"info_button.label": "Ayuda",
|
||||
"info_button.what_is_alt_text": "<h1>¿Qué es el texto alternativo?</h1> <p>El texto alternativo ofrece descripciones de las imágenes para individuos con dificultades visuales, conexiones de bajo ancho de banda o que buscan un contexto adicional.</p> <p>Puedes mejorar la accesibilidad y la comprensión para todos redactando un texto alternativo claro, breve y objetivo.</p> <ul><li>Captura los elementos clave.</li><li>Resume el texto en imágenes.</li><li>Utiliza una estructura de oraciones estándar.</li><li>Evita la información repetitiva.</li><li>Enfócate en las tendencias y conclusiones principales de los elementos visuales complejos (como gráficos o mapas).</li></ul>",
|
||||
"interaction_modal.action.favourite": "Para continuar, debes marcar como favorito desde tu cuenta.",
|
||||
"interaction_modal.action.follow": "Para continuar, debes seguir desde tu cuenta.",
|
||||
"interaction_modal.action.reblog": "Para continuar, debes impulsar desde tu cuenta.",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Cerrar sesión",
|
||||
"confirmations.logout.message": "¿Seguro que quieres cerrar la sesión?",
|
||||
"confirmations.logout.title": "¿Cerrar sesión?",
|
||||
"confirmations.missing_alt_text.confirm": "Añadir texto alternativo",
|
||||
"confirmations.missing_alt_text.message": "Tu publicación contiene medios sin texto alternativo. Añadir descripciones ayuda a que tu contenido sea accesible para más personas.",
|
||||
"confirmations.missing_alt_text.secondary": "Publicar de todos modos",
|
||||
"confirmations.missing_alt_text.title": "¿Deseas añadir texto alternativo?",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.redraft.confirm": "Borrar y volver a borrador",
|
||||
"confirmations.redraft.message": "¿Estás seguro de querer borrar esta publicación y reescribirla? Los favoritos e impulsos se perderán, y las respuestas a la publicación original quedarán sin contexto.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te siguen?",
|
||||
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
|
||||
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
|
||||
"info_button.label": "Ayuda",
|
||||
"info_button.what_is_alt_text": "<h1>¿Qué es el texto alternativo?</h1> <p>El texto alternativo proporciona descripciones de las imágenes para personas con problemas de visión, conexiones con poco ancho de banda o que buscan un contexto adicional.</p> <p>Puedes mejorar la accesibilidad y la comprensión para todos escribiendo un texto alternativo claro, conciso y objetivo.</p> <ul><li>Captura los elementos importantes.</li><li>Resume el texto en imágenes.</li><li>Usa una estructura de frases normal.</li><li>Evita la información redundante.</li><li>Céntrate en las tendencias y conclusiones clave de los elementos visuales complejos (como diagramas o mapas).</li></ul>",
|
||||
"interaction_modal.action.favourite": "Para continuar, tienes que marcar como favorito desde tu cuenta.",
|
||||
"interaction_modal.action.follow": "Para continuar, tienes que seguir desde tu cuenta.",
|
||||
"interaction_modal.action.reblog": "Para continuar, tienes que impulsar desde tu cuenta.",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Tekkis ootamatu viga.",
|
||||
"alert.unexpected.title": "Oih!",
|
||||
"alt_text_badge.title": "Alternatiivtekst",
|
||||
"alt_text_modal.add_alt_text": "Lisa alt-tekst",
|
||||
"alt_text_modal.add_text_from_image": "Lisa tekst pildilt",
|
||||
"alt_text_modal.cancel": "Tühista",
|
||||
"alt_text_modal.change_thumbnail": "Muuda pisipilti",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Kirjelda seda kuulmispuudega inimeste jaoks…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Kirjelda seda nägemispuudega inimeste jaoks…",
|
||||
"alt_text_modal.done": "Valmis",
|
||||
"announcement.announcement": "Teadaanne",
|
||||
"annual_report.summary.archetype.booster": "Ägesisu küttija",
|
||||
"annual_report.summary.archetype.lurker": "Hiilija",
|
||||
|
@ -407,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignoreeri inimeste teavitusi, kes sind ei jälgi?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignoreeri inimeste teavitusi, keda sa ei jälgi?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignoreeri soovimatute eraviisiliste mainimiste teateid?",
|
||||
"info_button.label": "Abi",
|
||||
"info_button.what_is_alt_text": "<h1>Mis on alt-tekst?</h1> <p>Alt-tekst pakub pildi kirjeldust nägemispuudega inimeste jaoks või neile, kel on aeglane internet või neile, kes otsivad lisaselgitust</p> <p>Saad parandada ligipääsetavust ja mõistmist kõigi jaoks, kirjutades selge, lühida ja objektiivse alt-teksti.</p> <ul> <li>Lisa tähtsad elemendid</li> <li>Tee pildil olevast tekstist kokkuvõte</li> <li>Kasuta reeglipärast lausestruktuuri</li> <li>Väldi ebaolulist infot</li> <li>Keskendu keerukate vaadete puhul (näiteks diagrammid ja kaardid) puhul trendidele ja põhiseostele</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Jätkamiseks pead oma konto alt lemmikuks märkima.",
|
||||
"interaction_modal.action.follow": "Jätkamiseks pead oma konto alt lemmikuks märkima.",
|
||||
"interaction_modal.action.reblog": "Jätkamiseks pead jagama oma konto alt.",
|
||||
|
@ -457,6 +466,7 @@
|
|||
"keyboard_shortcuts.toggle_hidden": "Näita/peida teksti hoiatuse taga",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Näita/peida meediat",
|
||||
"keyboard_shortcuts.toot": "Alusta uut postitust",
|
||||
"keyboard_shortcuts.translate": "postituse tõlkimiseks",
|
||||
"keyboard_shortcuts.unfocus": "Fookus tekstialalt/otsingult ära",
|
||||
"keyboard_shortcuts.up": "Liigu loetelus üles",
|
||||
"lightbox.close": "Sulge",
|
||||
|
|
|
@ -86,12 +86,18 @@
|
|||
"alert.unexpected.message": "Ustekabeko errore bat gertatu da.",
|
||||
"alert.unexpected.title": "Ene!",
|
||||
"alt_text_badge.title": "Testu alternatiboa",
|
||||
"alt_text_modal.add_alt_text": "Gehitu ordezko testua",
|
||||
"alt_text_modal.add_text_from_image": "Gehitu testua iruditik",
|
||||
"alt_text_modal.cancel": "Utzi",
|
||||
"alt_text_modal.change_thumbnail": "Aldatu koadro txikia",
|
||||
"alt_text_modal.done": "Egina",
|
||||
"announcement.announcement": "Iragarpena",
|
||||
"annual_report.summary.followers.followers": "jarraitzaileak",
|
||||
"annual_report.summary.followers.total": "{count} guztira",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "egindako bidalketa gogokoena",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "egindako bidalketa zabalduena",
|
||||
"annual_report.summary.highlighted_post.by_replies": "erantzun gehien izan dituen bidalketa",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}-(r)ena",
|
||||
"annual_report.summary.most_used_app.most_used_app": "app erabiliena",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "traola erabiliena",
|
||||
"annual_report.summary.most_used_hashtag.none": "Bat ere ez",
|
||||
|
@ -120,6 +126,7 @@
|
|||
"bundle_column_error.routing.body": "Eskatutako orria ezin izan da aurkitu. Ziur helbide-barrako URLa zuzena dela?",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Itxi",
|
||||
"bundle_modal_error.message": "Zerbait okerra gertatu da pantaila hau kargatzean.",
|
||||
"bundle_modal_error.retry": "Saiatu berriro",
|
||||
"closed_registrations.other_server_instructions": "Mastodon deszentralizatua denez, beste kontu bat sortu dezakezu beste zerbitzari batean eta honekin komunikatu.",
|
||||
"closed_registrations_modal.description": "Une honetan ezin da konturik sortu {domain} zerbitzarian, baina kontuan izan Mastodon erabiltzeko ez duzula zertan konturik izan zehazki {domain} zerbitzarian.",
|
||||
|
|
|
@ -101,7 +101,7 @@
|
|||
"annual_report.summary.archetype.replier": "پاسخگو",
|
||||
"annual_report.summary.followers.followers": "دنبال کننده",
|
||||
"annual_report.summary.followers.total": "در مجموع {count}",
|
||||
"annual_report.summary.here_it_is": "بازبینی {year}تان:",
|
||||
"annual_report.summary.here_it_is": "بازبینی {year} تان:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "پرپسندترین فرسته",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "پرتقویتترین فرسته",
|
||||
"annual_report.summary.highlighted_post.by_replies": "پرپاسخترین فرسته",
|
||||
|
@ -175,7 +175,7 @@
|
|||
"community.column_settings.media_only": "فقط رسانه",
|
||||
"community.column_settings.remote_only": "تنها دوردست",
|
||||
"compose.language.change": "تغییر زبان",
|
||||
"compose.language.search": "جستوجوی زبانها…",
|
||||
"compose.language.search": "جستوجوی زبانها...",
|
||||
"compose.published.body": "فرسته منتشر شد.",
|
||||
"compose.published.open": "گشودن",
|
||||
"compose.saved.body": "فرسته ذخیره شد.",
|
||||
|
@ -256,7 +256,7 @@
|
|||
"domain_block_modal.they_cant_follow": "هیچکسی از این کارساز نمیتواند پیتان بگیرد.",
|
||||
"domain_block_modal.they_wont_know": "نخواهند دانست که مسدود شدهاند.",
|
||||
"domain_block_modal.title": "انسداد دامنه؟",
|
||||
"domain_block_modal.you_will_lose_num_followers": "تعداد {followersCount, plural,other {{followersCount}}} پیگیرنده و {followingCount, plural,other {{followingCount}}} شخص پیگرفته شده را از دست خواهید داد.",
|
||||
"domain_block_modal.you_will_lose_num_followers": "شما {followersCount, plural, one {{followersCountDisplay} پیگیرنده} other {{followersCountDisplay} پیگیرنده}} و {followingCount, plural, one {{followingCountDisplay} فرد پیگرفتهشده} other {{followingCountDisplay} فرد پیگرفتهشده}} را از دست خواهید داد.",
|
||||
"domain_block_modal.you_will_lose_relationships": "شما تمام پیگیرکنندگان و افرادی که از این کارساز پیگیری میکنید را از دست خواهید داد.",
|
||||
"domain_block_modal.you_wont_see_posts": "فرستهها یا آگاهیها از کاربران روی این کارساز را نخواهید دید.",
|
||||
"domain_pill.activitypub_lets_connect": "این به شما اجازه میدهد تا نه تنها در ماستودون، بلکه در برنامههای اجتماعی مختلف نیز با افراد ارتباط برقرار کرده و تعامل داشته باشید.",
|
||||
|
@ -304,7 +304,7 @@
|
|||
"empty_column.follow_requests": "شما هنوز هیچ درخواست پیگیریای ندارید. هنگامی که چنین درخواستی بگیرید، اینجا نشان داده خواهد شد.",
|
||||
"empty_column.followed_tags": "شما هیچ برچسبی را پینگرفتید. هنگامی که برچسبی را پیگیری کنید اینجا نمایان میشوند.",
|
||||
"empty_column.hashtag": "هنوز هیچ چیزی در این برچسب نیست.",
|
||||
"empty_column.home": "خط زمانی خانگیتان خالی است! برای پر کردنش، افراد بیشتری را پی بگیرید. {suggestions}",
|
||||
"empty_column.home": "خط زمانی خانگیتان خالی است! برای پر کردنش، افراد بیشتری را پی بگیرید.",
|
||||
"empty_column.list": "هنوز چیزی در این سیاهه نیست. هنگامی که اعضایش فرستههای جدیدی بفرستند، اینجا ظاهر خواهند شد.",
|
||||
"empty_column.mutes": "هنوز هیچ کاربری را خموش نکردهاید.",
|
||||
"empty_column.notification_requests": "همه چیز تمیز است! هیچچیزی اینجا نیست. هنگامی که آگاهیهای جدیدی دریافت کنید، بسته به تنظیماتتان اینجا ظاهر خواهند شد.",
|
||||
|
@ -414,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "چشمپوشی از آگاهیهای افرادی که پیتان نمیگیرند؟",
|
||||
"ignore_notifications_modal.not_following_title": "چشمپوشی از آگاهیهای افرادی که پیشان نمیگیرید؟",
|
||||
"ignore_notifications_modal.private_mentions_title": "چشمپوشی از نامبریهای خصوصی ناخواسته؟",
|
||||
"info_button.label": "راهنما",
|
||||
"info_button.what_is_alt_text": "<h1>متن جایگزین چیست؟</h1> <p>متن جایگزین توضیحات تصویری را برای افراد دارای اختلالات بینایی، اتصالات با پهنای باند کم یا کسانی که به دنبال زمینه اضافی هستند ارائه می دهد.</p> <p>با نوشتن متن جایگزین واضح، مختصر و عینی می توانید دسترسی و درک را برای همه بهبود بخشید.</p> <ul> <li>عناصر مهم را ضبط کنید</li> <li>متن را در تصاویر خلاصه کنید</li> <li>از ساختار جمله منظم استفاده کنید</li> <li>از اطلاعات اضافی خودداری کنید</li> <li>روی روندها و یافته های کلیدی در تصاویر پیچیده (مانند نمودارها یا نقشه ها) تمرکز کنید.</li> </ul>",
|
||||
"interaction_modal.action.favourite": "برای ادامه، باید از حساب خود به دلخواه انتخاب کنید.",
|
||||
"interaction_modal.action.follow": "برای ادامه، باید از حساب کاربری خود دنبال کنید.",
|
||||
"interaction_modal.action.reblog": "برای ادامه، باید از حساب خود مجددا بلاگ کنید.",
|
||||
|
@ -551,7 +553,7 @@
|
|||
"notification.admin.report_statuses_other": "{name}، {target} را گزارش داد",
|
||||
"notification.admin.sign_up": "{name} ثبت نام کرد",
|
||||
"notification.admin.sign_up.name_and_others": "{name} و {count, plural, one {# نفر دیگر} other {# نفر دیگر}} ثبتنام کردند",
|
||||
"notification.annual_report.message": "آمار #Wrapstodon {year}تان منتظر است! لحظههای به یاد ماندنی و نقاط پررنگ سال را روی ماستودون رونمایی کنید!",
|
||||
"notification.annual_report.message": "آمار #Wrapstodon {year} تان منتظر است! لحظههای به یاد ماندنی و نقاط پررنگ سال را روی ماستودون رونمایی کنید!",
|
||||
"notification.annual_report.view": "دیدن #Wrapstodon",
|
||||
"notification.favourite": "{name} فرستهتان را برگزید",
|
||||
"notification.favourite.name_and_others_with_link": "{name} و <a>{count, plural, one {# نفر دیگر} other {# نفر دیگر}}</a> فرستهتان را برگزیدند",
|
||||
|
@ -799,7 +801,7 @@
|
|||
"server_banner.is_one_of_many": "{domain} یکی از بسیاری از سرورهای مستقل ماستودون است که می توانید از آن برای شرکت در fediverse استفاده کنید.",
|
||||
"server_banner.server_stats": "آمار کارساز:",
|
||||
"sign_in_banner.create_account": "ایجاد حساب",
|
||||
"sign_in_banner.follow_anyone": "هر کسی را در سراسر fediverse دنبال کنید و همه را به ترتیب زمانی ببینید. هیچ الگوریتم، تبلیغات یا طعمه کلیکی در چشم نیست.",
|
||||
"sign_in_banner.follow_anyone": "هر کسی را در سراسر فدیورس دنبال کنید و همه را به ترتیب زمانی ببینید. هیچ الگوریتم، تبلیغات یا طعمه کلیکی در چشم نیست.",
|
||||
"sign_in_banner.mastodon_is": "ماستودون بهترین راه برای پیگیری اتفاقات است.",
|
||||
"sign_in_banner.sign_in": "ورود",
|
||||
"sign_in_banner.sso_redirect": "ورود یا ثبت نام",
|
||||
|
@ -888,7 +890,7 @@
|
|||
"upload_form.drag_and_drop.on_drag_over": "پیوست رسانه {item} منتقل شد.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "پیوست رسانه {item} برداشته شد.",
|
||||
"upload_form.edit": "ویرایش",
|
||||
"upload_progress.label": "در حال بارگذاری…",
|
||||
"upload_progress.label": "در حال بارگذاری...",
|
||||
"upload_progress.processing": "در حال پردازش…",
|
||||
"username.taken": "این نام کاربری گرفته شده. نام دیگری امتحان کنید",
|
||||
"video.close": "بستن ویدیو",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Tapahtui odottamaton virhe.",
|
||||
"alert.unexpected.title": "Hups!",
|
||||
"alt_text_badge.title": "Vaihtoehtoinen teksti",
|
||||
"alt_text_modal.add_alt_text": "Lisää vaihtoehtoinen teksti",
|
||||
"alt_text_modal.add_text_from_image": "Lisää teksti kuvasta",
|
||||
"alt_text_modal.cancel": "Peruuta",
|
||||
"alt_text_modal.change_thumbnail": "Vaihda pikkukuva",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Kuvaile tätä kuulovammallisille ihmisille…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Kuvaile tätä näkövammallisille ihmisille…",
|
||||
"alt_text_modal.done": "Valmis",
|
||||
"announcement.announcement": "Tiedote",
|
||||
"annual_report.summary.archetype.booster": "Tehostaja",
|
||||
"annual_report.summary.archetype.lurker": "Lymyilijä",
|
||||
|
@ -211,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Kirjaudu ulos",
|
||||
"confirmations.logout.message": "Haluatko varmasti kirjautua ulos?",
|
||||
"confirmations.logout.title": "Kirjaudutaanko ulos?",
|
||||
"confirmations.missing_alt_text.confirm": "Lisää vaihtoehtoinen teksti",
|
||||
"confirmations.missing_alt_text.message": "Julkaisussasi on mediaa ilman vaihtoehtoista tekstiä. Kuvausten lisääminen auttaa tekemään sisällöstäsi saavutettavamman useammille ihmisille.",
|
||||
"confirmations.missing_alt_text.secondary": "Julkaise silti",
|
||||
"confirmations.missing_alt_text.title": "Lisätäänkö vaihtoehtoinen teksti?",
|
||||
"confirmations.mute.confirm": "Mykistä",
|
||||
"confirmations.redraft.confirm": "Poista ja palauta muokattavaksi",
|
||||
"confirmations.redraft.message": "Haluatko varmasti poistaa julkaisun ja tehdä siitä luonnoksen? Suosikit ja tehostukset menetetään, ja alkuperäisen julkaisun vastaukset jäävät orvoiksi.",
|
||||
|
@ -407,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Sivuutetaanko ilmoitukset käyttäjiltä, jotka eivät seuraa sinua?",
|
||||
"ignore_notifications_modal.not_following_title": "Sivuutetaanko ilmoitukset käyttäjiltä, joita et seuraa?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Sivuutetaanko ilmoitukset pyytämättömistä yksityismaininnoista?",
|
||||
"info_button.label": "Ohje",
|
||||
"info_button.what_is_alt_text": "<h1>Mikä vaihtoehtoinen teksti on?</h1> <p>Vaihtoehtoinen teksti tarjoaa kuvauksen kuvista ihmisille, joilla on näkövamma tai matalan kaistanleveyden yhteys tai jotka kaipaavat lisäkontekstia.</p> <p>Voit parantaa saavutettavuutta ja ymmärrettävyyttä kaikkien näkökulmasta kirjoittamalla selkeän, tiiviin ja objektiivisen vaihtoehtoisen tekstin.</p> <ul> <li>Ota mukaan tärkeät elementit</li> <li>Tiivistä kuvissa oleva teksti</li> <li>Käytä tavallisia lauserakenteita</li> <li>Vältä turhaa tietoa</li> <li>Keskity trendeihin ja keskeisiin tuloksiin monimutkaisissa visuaalisissa esityksissä (kuten kaavioissa tai kartoissa)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Jotta voit jatkaa, sinun tulee lisätä julkaisu suosikiksesi omalta tililtäsi.",
|
||||
"interaction_modal.action.follow": "Jotta voit jatkaa, sinun tulee seurata käyttäjää omalta tililtäsi.",
|
||||
"interaction_modal.action.reblog": "Jotta voit jatkaa, sinun tulee uudelleenjulkaista omalta tililtäsi.",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Rita út",
|
||||
"confirmations.logout.message": "Ert tú vís/ur í, at tú vilt útrita teg?",
|
||||
"confirmations.logout.title": "Rita út?",
|
||||
"confirmations.missing_alt_text.confirm": "Legg alternativan tekst afturat",
|
||||
"confirmations.missing_alt_text.message": "Posturin hjá tær inniheldur miðlar uttan alternativan tekst. Leggur tú lýsingar afturat verður tilfarið hjá tær atkomuligt hjá fleiri.",
|
||||
"confirmations.missing_alt_text.secondary": "Posta allíkavæl",
|
||||
"confirmations.missing_alt_text.title": "Legg alternativan tekst afturat?",
|
||||
"confirmations.mute.confirm": "Doyv",
|
||||
"confirmations.redraft.confirm": "Sletta og skriva umaftur",
|
||||
"confirmations.redraft.message": "Vilt tú veruliga strika hendan postin og í staðin gera hann til eina nýggja kladdu? Yndisfrámerki og framhevjanir blíva burtur, og svar til upprunapostin missa tilknýtið.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Lat sum um tú ikki sær fráboðanir frá fólki, sum ikki fylgja tær?",
|
||||
"ignore_notifications_modal.not_following_title": "Lat sum um tú ikki sær fráboðanir frá fólki, sum tú ikki fylgir?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Lat sum um tú ikki sær fráboðanir frá óbiðnum privatum umrøðum?",
|
||||
"info_button.label": "Hjálp",
|
||||
"info_button.what_is_alt_text": "<h1>Hvat er alt tekstur?</h1> <p>Alt tekstur lýsir myndir fyri fólki, sum síggja illa, ella sum hava ringt net samband ella tey, sum vilja vita meira um samanhangin.</p> <p>Tú kanst bøta um atkomuna og fatanina hjá øllum við at skriva kláran, stuttan og objektivan alt tekst.</p> <ul> <li>Fanga týdningarmikil element</li> <li>Samanfata tekst í myndum</li> <li>Brúka reglubundnan setningsbygnað</li> <li>Lat vera við at siga ting upp í saman</li> <li>Fokusera á rák og høvuðsúrslit í kompleksum myndum (sosum diagrammir og kort)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Fyri at halda fram, so mást tú yndismerkja frá tínari kontu.",
|
||||
"interaction_modal.action.follow": "Fyri at halda fram, mást tú fylgja frá tínari kontu.",
|
||||
"interaction_modal.action.reblog": "Fyri at halda fram, mást tú endurblogga frá tínari kontu.",
|
||||
|
|
|
@ -414,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignorer les notifications provenant des personnes qui ne vous suivent pas ?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorer les notifications provenant des personnes que vous ne suivez pas ?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorer les notifications issues des mentions privées non sollicitées ?",
|
||||
"info_button.label": "Aide",
|
||||
"info_button.what_is_alt_text": "<h1>Qu'est-ce que le texte alternatif ?</h1> <p>Un texte alternatif fournit une description de l'image aux personnes avec un handicap visuel ou une connexion limitée ou qui souhaitent avoir un contexte supplémentaire.</p> <p>Vous pouvez améliorer l'accessibilité et la compression de tout le monde en écrivant un texte alternatif clair, concis et objectif.</p> <ul> <li>Identifiez les éléments importants</li> <li>Résumez le texte présent à l'image</li> <li>Utilisez une structure de phrase normale</li> <li>Évitez les informations redondantes</li> <li>Pour les visuels complexes (tels que les diagrammes ou les cartes), indiquez les tendances ou points-clés</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Pour continuer, vous devez ajouter en favori depuis votre compte.",
|
||||
"interaction_modal.action.follow": "Pour continuer, vous devez suivre depuis votre compte.",
|
||||
"interaction_modal.action.reblog": "Pour continuer, vous devez booster depuis votre compte.",
|
||||
|
|
|
@ -414,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignorer les notifications provenant des personnes qui ne vous suivent pas ?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorer les notifications provenant des personnes que vous ne suivez pas ?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorer les notifications issues des mentions privées non sollicitées ?",
|
||||
"info_button.label": "Aide",
|
||||
"info_button.what_is_alt_text": "<h1>Qu'est-ce que le texte alternatif ?</h1> <p>Un texte alternatif fournit une description de l'image aux personnes avec un handicap visuel ou une connexion limitée ou qui souhaitent avoir un contexte supplémentaire.</p> <p>Vous pouvez améliorer l'accessibilité et la compression de tout le monde en écrivant un texte alternatif clair, concis et objectif.</p> <ul> <li>Identifiez les éléments importants</li> <li>Résumez le texte présent à l'image</li> <li>Utilisez une structure de phrase normale</li> <li>Évitez les informations redondantes</li> <li>Pour les visuels complexes (tels que les diagrammes ou les cartes), indiquez les tendances ou points-clés</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Pour continuer, vous devez ajouter en favori depuis votre compte.",
|
||||
"interaction_modal.action.follow": "Pour continuer, vous devez suivre depuis votre compte.",
|
||||
"interaction_modal.action.reblog": "Pour continuer, vous devez booster depuis votre compte.",
|
||||
|
@ -549,7 +551,7 @@
|
|||
"notification.admin.report_account_other": "{name} a signalé {count, plural, one {un message} other {# messages}} depuis {target}",
|
||||
"notification.admin.report_statuses": "{name} a signalé {target} pour {category}",
|
||||
"notification.admin.report_statuses_other": "{name} a signalé {target}",
|
||||
"notification.admin.sign_up": "{name} s'est inscrit",
|
||||
"notification.admin.sign_up": "{name} s'est inscrit·e",
|
||||
"notification.admin.sign_up.name_and_others": "{name} et {count, plural, one {# autre} other {# autres}} se sont inscrit",
|
||||
"notification.annual_report.message": "Votre {year} #Wrapstodon attend ! Dévoilez les moments forts et mémorables de votre année sur Mastodon !",
|
||||
"notification.annual_report.view": "Voir #Wrapstodon",
|
||||
|
|
|
@ -86,14 +86,33 @@
|
|||
"alert.unexpected.message": "Der is in ûnferwachte flater bard.",
|
||||
"alert.unexpected.title": "Oepsy!",
|
||||
"alt_text_badge.title": "Alternative tekst",
|
||||
"alt_text_modal.add_alt_text": "Alt-tekst tafoegje",
|
||||
"alt_text_modal.add_text_from_image": "Tekst fan ôfbylding tafoegje",
|
||||
"alt_text_modal.cancel": "Annulearje",
|
||||
"alt_text_modal.change_thumbnail": "Miniatuerôfbylding wizigje",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Beskriuw dit foar dôven en hurdhearrige…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Beskriuw dit foar blinen en fisueel beheinde…",
|
||||
"alt_text_modal.done": "Klear",
|
||||
"announcement.announcement": "Oankundiging",
|
||||
"annual_report.summary.archetype.booster": "De cool-hunter",
|
||||
"annual_report.summary.archetype.lurker": "De lurker",
|
||||
"annual_report.summary.archetype.oracle": "It orakel",
|
||||
"annual_report.summary.archetype.pollster": "De opinypeiler",
|
||||
"annual_report.summary.archetype.replier": "De sosjale flinter",
|
||||
"annual_report.summary.followers.followers": "folgers",
|
||||
"annual_report.summary.followers.total": "totaal {count}",
|
||||
"annual_report.summary.here_it_is": "Jo jieroersjoch foar {year}:",
|
||||
"annual_report.summary.highlighted_post.by_favourites": "berjocht mei de measte favoriten",
|
||||
"annual_report.summary.highlighted_post.by_reblogs": "berjocht mei de measte boosts",
|
||||
"annual_report.summary.highlighted_post.by_replies": "berjocht mei de measte reaksjes",
|
||||
"annual_report.summary.highlighted_post.possessive": "{name}’s",
|
||||
"annual_report.summary.most_used_app.most_used_app": "meast brûkte app",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "meast brûkte hashtag",
|
||||
"annual_report.summary.most_used_hashtag.none": "Gjin",
|
||||
"annual_report.summary.new_posts.new_posts": "nije berjochten",
|
||||
"annual_report.summary.percentile.text": "<topLabel>Hjirmei hearre jo ta de top</topLabel><percentage></percentage><bottomLabel> fan {domain}.</bottomLabel>",
|
||||
"annual_report.summary.percentile.we_wont_tell_bernie": "Wy sille Bernie neat fertelle.",
|
||||
"annual_report.summary.thanks": "Tank dat jo part binne fan Mastodon!",
|
||||
"attachments_list.unprocessed": "(net ferwurke)",
|
||||
"audio.hide": "Audio ferstopje",
|
||||
"block_modal.remote_users_caveat": "Wy freegje de server {domain} om jo beslút te respektearjen. It neilibben hjirfan is echter net garandearre, omdat guon servers blokkaden oars ynterpretearje kinne. Iepenbiere berjochten binne mooglik noch hieltyd sichtber foar net-oanmelde brûkers.",
|
||||
|
@ -117,6 +136,7 @@
|
|||
"bundle_column_error.routing.body": "De opfrege side kin net fûn wurde. Binne jo wis dat de URL yn de adresbalke goed is?",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "Slute",
|
||||
"bundle_modal_error.message": "Der gie wat mis by it laden fan dit skerm.",
|
||||
"bundle_modal_error.retry": "Opnij probearje",
|
||||
"closed_registrations.other_server_instructions": "Omdat Mastodon desintralisearre is, kinne jo in account meitsje op in oare server en noch hieltyd ynteraksje hawwe mei dizze.",
|
||||
"closed_registrations_modal.description": "It oanmeitsjen fan in account op {domain} is op dit stuit net mooglik, mar hâld asjebleaft yn gedachten dat jo gjin account spesifyk op {domain} nedich hawwe om Mastodon te brûken.",
|
||||
|
@ -127,13 +147,16 @@
|
|||
"column.blocks": "Blokkearre brûkers",
|
||||
"column.bookmarks": "Blêdwizers",
|
||||
"column.community": "Lokale tiidline",
|
||||
"column.create_list": "List oanmeitsje",
|
||||
"column.direct": "Priveefermeldingen",
|
||||
"column.directory": "Profilen trochsykje",
|
||||
"column.domain_blocks": "Blokkearre domeinen",
|
||||
"column.edit_list": "List bewurkje",
|
||||
"column.favourites": "Favoriten",
|
||||
"column.firehose": "Live feeds",
|
||||
"column.follow_requests": "Folchfersiken",
|
||||
"column.home": "Startside",
|
||||
"column.list_members": "Listleden beheare",
|
||||
"column.lists": "Listen",
|
||||
"column.mutes": "Negearre brûkers",
|
||||
"column.notifications": "Meldingen",
|
||||
|
@ -146,6 +169,7 @@
|
|||
"column_header.pin": "Fêstsette",
|
||||
"column_header.show_settings": "Ynstellingen toane",
|
||||
"column_header.unpin": "Losmeitsje",
|
||||
"column_search.cancel": "Annulearje",
|
||||
"column_subheading.settings": "Ynstellingen",
|
||||
"community.column_settings.local_only": "Allinnich lokaal",
|
||||
"community.column_settings.media_only": "Allinnich media",
|
||||
|
@ -188,6 +212,9 @@
|
|||
"confirmations.edit.confirm": "Bewurkje",
|
||||
"confirmations.edit.message": "Troch no te bewurkjen sil it berjocht dat jo no oan it skriuwen binne oerskreaun wurde. Wolle jo trochgean?",
|
||||
"confirmations.edit.title": "Berjocht oerskriuwe?",
|
||||
"confirmations.follow_to_list.confirm": "Folgje en tafoegje oan de list",
|
||||
"confirmations.follow_to_list.message": "Jo moatte {name} folgje om se ta te foegjen oan in list.",
|
||||
"confirmations.follow_to_list.title": "Brûker folgje?",
|
||||
"confirmations.logout.confirm": "Ofmelde",
|
||||
"confirmations.logout.message": "Binne jo wis dat jo ôfmelde wolle?",
|
||||
"confirmations.logout.title": "Ofmelde?",
|
||||
|
@ -219,6 +246,7 @@
|
|||
"disabled_account_banner.text": "Jo account {disabledAccount} is op dit stuit útskeakele.",
|
||||
"dismissable_banner.community_timeline": "Dit binne de meast resinte iepenbiere berjochten fan accounts op {domain}.",
|
||||
"dismissable_banner.dismiss": "Slute",
|
||||
"dismissable_banner.explore_links": "Dizze nijsartikelen wurde hjoed de dei it meast dield op de fediverse. Nijere artikelen dy’t troch mear ferskate minsken pleatst binne, wurde heger rangskikt.",
|
||||
"domain_block_modal.block": "Server blokkearje",
|
||||
"domain_block_modal.block_account_instead": "Yn stee hjirfan {name} blokkearje",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Minsken op dizze server kinne ynteraksje hawwe mei jo âlde berjochten.",
|
||||
|
@ -381,6 +409,9 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Meldingen negearje fan minsken dy’t jo net folgje?",
|
||||
"ignore_notifications_modal.not_following_title": "Meldingen negearje fan minsken dy’t josels net folgje?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Meldingen negearje fan net frege priveeberjochten?",
|
||||
"info_button.label": "Help",
|
||||
"interaction_modal.go": "Gean",
|
||||
"interaction_modal.no_account_yet": "Hawwe jo noch gjin account?",
|
||||
"interaction_modal.on_another_server": "Op een oare server",
|
||||
"interaction_modal.on_this_server": "Op dizze server",
|
||||
"interaction_modal.title.favourite": "Berjocht fan {name} as favoryt markearje",
|
||||
|
@ -388,6 +419,7 @@
|
|||
"interaction_modal.title.reblog": "Berjocht fan {name} booste",
|
||||
"interaction_modal.title.reply": "Op it berjocht fan {name} reagearje",
|
||||
"interaction_modal.title.vote": "Stimme yn {name}’s peiling",
|
||||
"interaction_modal.username_prompt": "Byg. {example}",
|
||||
"intervals.full.days": "{number, plural, one {# dei} other {# dagen}} lyn",
|
||||
"intervals.full.hours": "{number, plural, one {# oere} other {# oeren}} lyn",
|
||||
"intervals.full.minutes": "{number, plural, one {# minút} other {# minuten}} lyn",
|
||||
|
@ -423,6 +455,7 @@
|
|||
"keyboard_shortcuts.toggle_hidden": "Tekst efter CW-fjild ferstopje/toane",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Media ferstopje/toane",
|
||||
"keyboard_shortcuts.toot": "Nij berjocht skriuwe",
|
||||
"keyboard_shortcuts.translate": "om in berjocht oer te setten",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "Nei boppe yn list ferpleatse",
|
||||
"lightbox.close": "Slute",
|
||||
|
@ -435,11 +468,32 @@
|
|||
"link_preview.author": "Troch {name}",
|
||||
"link_preview.more_from_author": "Mear fan {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}",
|
||||
"lists.add_member": "Tafoegje",
|
||||
"lists.add_to_list": "Oan list tafoegje",
|
||||
"lists.add_to_lists": "{name} oan listen tafoegje",
|
||||
"lists.create": "Oanmeitsje",
|
||||
"lists.create_a_list_to_organize": "Meitsje in nije list oan om jo starttiidline te organisearjen",
|
||||
"lists.create_list": "List oanmeitsje",
|
||||
"lists.delete": "List fuortsmite",
|
||||
"lists.done": "Klear",
|
||||
"lists.edit": "List bewurkje",
|
||||
"lists.exclusive": "Leden op jo Startside ferstopje",
|
||||
"lists.exclusive_hint": "As ien op dizze list stiet, ferstopje dizze persoan dan op jo starttiidline om foar te kommen dat harren berjochten twa kear toand wurde.",
|
||||
"lists.find_users_to_add": "Fyn brûkers om ta te foegjen",
|
||||
"lists.list_members": "Listleden",
|
||||
"lists.list_members_count": "{count, plural, one{# lid} other{# leden}}",
|
||||
"lists.list_name": "Listnamme",
|
||||
"lists.new_list_name": "Nije listnamme",
|
||||
"lists.no_lists_yet": "Noch gjin listen.",
|
||||
"lists.no_members_yet": "Noch gjin leden.",
|
||||
"lists.no_results_found": "Gjin resultaten fûn.",
|
||||
"lists.remove_member": "Fuortsmite",
|
||||
"lists.replies_policy.followed": "Elke folge brûker",
|
||||
"lists.replies_policy.list": "Leden fan de list",
|
||||
"lists.replies_policy.none": "Net ien",
|
||||
"lists.save": "Bewarje",
|
||||
"lists.search": "Sykje",
|
||||
"lists.show_replies_to": "Foegje antwurden fan listleden ta oan",
|
||||
"load_pending": "{count, plural, one {# nij item} other {# nije items}}",
|
||||
"loading_indicator.label": "Lade…",
|
||||
"media_gallery.hide": "Ferstopje",
|
||||
|
@ -488,8 +542,12 @@
|
|||
"notification.admin.report_statuses_other": "{name} hat {target} rapportearre",
|
||||
"notification.admin.sign_up": "{name} hat harren registrearre",
|
||||
"notification.admin.sign_up.name_and_others": "{name} en {count, plural, one {# oar} other {# oaren}} hawwe harren registrearre",
|
||||
"notification.annual_report.message": "Jo {year} #Wrapstodon stiet klear! Lit de hichtepunten en memorabele mominten fan jo jier sjen op Mastodon!",
|
||||
"notification.annual_report.view": "#Wrapstodon besjen",
|
||||
"notification.favourite": "{name} hat jo berjocht as favoryt markearre",
|
||||
"notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# oar} other {# oaren}}</a> hawwe jo berjocht as favoryt markearre",
|
||||
"notification.favourite_pm": "{name} hat jo priveeberjocht as favoryt markearre",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} en <a>{count, plural, one {# oar} other {# oaren}}</a> hawwe jo priveeberjocht as favoryt markearre",
|
||||
"notification.follow": "{name} folget dy",
|
||||
"notification.follow.name_and_others": "{name} en <a>{count, plural, one {# oar persoan} other {# oare persoanen}}</a> folgje jo no",
|
||||
"notification.follow_request": "{name} hat dy in folchfersyk stjoerd",
|
||||
|
@ -594,7 +652,11 @@
|
|||
"notifications_permission_banner.enable": "Desktopmeldingen ynskeakelje",
|
||||
"notifications_permission_banner.how_to_control": "Om meldingen te ûntfangen wannear’t Mastodon net iepen stiet. Jo kinne krekt bepale hokker soarte fan ynteraksjes wol of gjin desktopmeldingen jouwe fia de boppesteande {icon} knop.",
|
||||
"notifications_permission_banner.title": "Mis neat",
|
||||
"onboarding.follows.back": "Tebek",
|
||||
"onboarding.follows.done": "Klear",
|
||||
"onboarding.follows.empty": "Spitigernôch kinne op dit stuit gjin resultaten toand wurde. Jo kinne probearje te sykjen of te blêdzjen troch de ferkenningsside om minsken te finen dy’t jo folgje kinne, of probearje it letter opnij.",
|
||||
"onboarding.follows.search": "Sykje",
|
||||
"onboarding.follows.title": "Folgje minsken om te begjinnen",
|
||||
"onboarding.profile.discoverable": "Meitsje myn profyl te finen",
|
||||
"onboarding.profile.discoverable_hint": "Wannear’t jo akkoard gean mei it te finen wêzen op Mastodon, ferskine jo berjochten yn sykresultaten en kinne se trending wurde, en jo profyl kin oan oare minsken oanrekommandearre wurde wannear’t se fergelykbere ynteressen hawwe.",
|
||||
"onboarding.profile.display_name": "Werjeftenamme",
|
||||
|
@ -632,6 +694,8 @@
|
|||
"privacy_policy.title": "Privacybelied",
|
||||
"recommended": "Oanrekommandearre",
|
||||
"refresh": "Ferfarskje",
|
||||
"regeneration_indicator.please_stand_by": "In amerijke.",
|
||||
"regeneration_indicator.preparing_your_home_feed": "Tarieden fan jo starttiidline…",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "{number, plural, one {# dei} other {# dagen}} lyn",
|
||||
"relative_time.full.hours": "{number, plural, one {# oere} other {# oeren}} lyn",
|
||||
|
@ -715,8 +779,11 @@
|
|||
"search_results.accounts": "Profilen",
|
||||
"search_results.all": "Alles",
|
||||
"search_results.hashtags": "Hashtags",
|
||||
"search_results.no_results": "Gjin resultaten.",
|
||||
"search_results.no_search_yet": "Probearje te sykjen nei berjochten, profilen of hashtags.",
|
||||
"search_results.see_all": "Alles besjen",
|
||||
"search_results.statuses": "Berjochten",
|
||||
"search_results.title": "Sykje nei ‘{q}’",
|
||||
"server_banner.about_active_users": "Oantal brûkers yn de ôfrûne 30 dagen (MAU)",
|
||||
"server_banner.active_users": "warbere brûkers",
|
||||
"server_banner.administered_by": "Beheard troch:",
|
||||
|
@ -768,6 +835,7 @@
|
|||
"status.reblogs.empty": "Net ien hat dit berjocht noch boost. Wannear’t ien dit docht, falt dat hjir te sjen.",
|
||||
"status.redraft": "Fuortsmite en opnij opstelle",
|
||||
"status.remove_bookmark": "Blêdwizer fuortsmite",
|
||||
"status.remove_favourite": "Ut favoriten fuortsmite",
|
||||
"status.replied_in_thread": "Antwurde yn petear",
|
||||
"status.replied_to": "Antwurde op {name}",
|
||||
"status.reply": "Beäntwurdzje",
|
||||
|
@ -789,6 +857,7 @@
|
|||
"subscribed_languages.target": "Toande talen foar {target} wizigje",
|
||||
"tabs_bar.home": "Startside",
|
||||
"tabs_bar.notifications": "Meldingen",
|
||||
"terms_of_service.title": "Gebrûksbetingsten",
|
||||
"time_remaining.days": "{number, plural, one {# dei} other {# dagen}} te gean",
|
||||
"time_remaining.hours": "{number, plural, one {# oere} other {# oeren}} te gean",
|
||||
"time_remaining.minutes": "{number, plural, one {# minút} other {# minuten}} te gean",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Pechar sesión",
|
||||
"confirmations.logout.message": "Desexas pechar a sesión?",
|
||||
"confirmations.logout.title": "Pechar sesión?",
|
||||
"confirmations.missing_alt_text.confirm": "Engadir texto descritivo",
|
||||
"confirmations.missing_alt_text.message": "A publicación contén multimedia sen un texto que o describa. Ao engadir a descrición fas o contido accesible para máis persoas.",
|
||||
"confirmations.missing_alt_text.secondary": "Publicar igualmente",
|
||||
"confirmations.missing_alt_text.title": "Engadir texto descritivo?",
|
||||
"confirmations.mute.confirm": "Acalar",
|
||||
"confirmations.redraft.confirm": "Eliminar e reescribir",
|
||||
"confirmations.redraft.message": "Tes a certeza de querer eliminar esta publicación e reescribila? Perderás as promocións e favorecementos, e as respostas á publicación orixinal ficarán orfas.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignorar notificacións de persoas que non te seguen?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorar notificacións de persoas que non segues?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorar notificacións de Mencións Privadas non solicitadas?",
|
||||
"info_button.label": "Axuda",
|
||||
"info_button.what_is_alt_text": "<h1>Que é o Texto Alternativo?</h1> <p>O Text Alt proporciona a descrición das imaxes para as persoas con deficiencias visuais, conexións a internet de baixa calidade ou para engadir contexto ás mesmas.</p> <p>Podes mellorar a accesibilidade e a comprensión da publicación ao escribir un texto alternativo claro, conciso e obxectivo.</p> <ul> <li>Identifica os elementos importantes</li> <li>Inclúe o texto que apareza nas imaxes</li> <li>Utiliza sintaxe estándar nas frases</li> <li>Evita información redundante</li> <li>Céntrate nos elementos principais cando sexan imaxes complexas (como diagramas ou mapas)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Para continuar, debes favorecer desde a túa conta.",
|
||||
"interaction_modal.action.follow": "Para continuar, debes facer seguimento desde a túa conta.",
|
||||
"interaction_modal.action.reblog": "Para continuar, debes promover desde a túa conta.",
|
||||
|
|
|
@ -218,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "התנתקות",
|
||||
"confirmations.logout.message": "האם אתם בטוחים שאתם רוצים להתנתק?",
|
||||
"confirmations.logout.title": "להתנתק?",
|
||||
"confirmations.missing_alt_text.confirm": "הוספת מלל חלופי",
|
||||
"confirmations.missing_alt_text.message": "ההודעה שלך כוללת קבצים גרפיים ללא תיאור (מלל חלופי). הוספת תיאורים עוזרת להנגיש את התוכן ליותר אנשים.",
|
||||
"confirmations.missing_alt_text.secondary": "לפרסם בכל זאת",
|
||||
"confirmations.missing_alt_text.title": "להוסיף מלל חלופי?",
|
||||
"confirmations.mute.confirm": "להשתיק",
|
||||
"confirmations.redraft.confirm": "מחיקה ועריכה מחדש",
|
||||
"confirmations.redraft.message": "למחוק ולהתחיל טיוטה חדשה? חיבובים והדהודים יאבדו, ותגובות להודעה המקורית ישארו יתומות.",
|
||||
|
@ -414,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "להתעלם מהתראות מא.נשים שאינם עוקביך?",
|
||||
"ignore_notifications_modal.not_following_title": "להתעלם מהתראות מא.נשים שאינם נעקביך?",
|
||||
"ignore_notifications_modal.private_mentions_title": "להתעלם מהתראות מאיזכורים פרטיים?",
|
||||
"info_button.label": "עזרה",
|
||||
"info_button.what_is_alt_text": "<h1>מהו כיתוב חלופי?</h1> <p>כיתוב חלופי משמש תיאור מילולי של תמונות לסובלים ממגבלות ראיה, חיבורי רשת איטיים, או אלו הצריכים הקשר יותר מפורט לתוכן המולטימדיה המצורף.</p> <p>ניתן לשפר את הנגישות והבנת התוכן לכולם ע\"י כתיבת תיאור ברור, תמציתי ונטול פניות.</p> <ul> <li>כיסוי היסודות החשובים</li> <li>סיכום המלל שבתמונות</li> <li>שימוש במבנה משפטים רגיל</li> <li>יש להמנע מחזרה על מידע</li> <li>אם העזרים הויזואליים הם דיאגרמות או מפות, התמקדו במגמות וממצאים מרכזיים.</li> </ul>",
|
||||
"interaction_modal.action.favourite": "כדי להמשיך, עליך לחבב מחשבונך.",
|
||||
"interaction_modal.action.follow": "כדי להמשיך, עליך לעקוב מחשבונך.",
|
||||
"interaction_modal.action.reblog": "כדי להמשיך, עליך להדהד מחשבונך.",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Váratlan hiba történt.",
|
||||
"alert.unexpected.title": "Hoppá!",
|
||||
"alt_text_badge.title": "Helyettesítő szöveg",
|
||||
"alt_text_modal.add_alt_text": "Helyettesítő szöveg hozzáadása",
|
||||
"alt_text_modal.add_text_from_image": "Szöveg hozzáadása a képből",
|
||||
"alt_text_modal.cancel": "Mégse",
|
||||
"alt_text_modal.change_thumbnail": "Bélyegkép megváltoztatása",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Írd le a hallássérültek számára…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Írd le a látássérültek számára…",
|
||||
"alt_text_modal.done": "Kész",
|
||||
"announcement.announcement": "Közlemény",
|
||||
"annual_report.summary.archetype.booster": "A cool-vadász",
|
||||
"annual_report.summary.archetype.lurker": "A settenkedő",
|
||||
|
@ -211,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Kijelentkezés",
|
||||
"confirmations.logout.message": "Biztos, hogy kijelentkezel?",
|
||||
"confirmations.logout.title": "Kijelentkezel?",
|
||||
"confirmations.missing_alt_text.confirm": "Helyettesítő szöveg hozzáadása",
|
||||
"confirmations.missing_alt_text.message": "A bejegyzés helyettesítő szöveg nélküli médiát tartalmaz. A leírások hozzáadása segít a tartalom akadálymentesebbé tételében.",
|
||||
"confirmations.missing_alt_text.secondary": "Közzététel mindenképpen",
|
||||
"confirmations.missing_alt_text.title": "Helyettesítő szöveg hozzáadása?",
|
||||
"confirmations.mute.confirm": "Némítás",
|
||||
"confirmations.redraft.confirm": "Törlés és újraírás",
|
||||
"confirmations.redraft.message": "Biztos, hogy ezt a bejegyzést szeretnéd törölni és újraírni? Minden megtolást és kedvencnek jelölést elvesztesz, az eredetire adott válaszok pedig elárvulnak.",
|
||||
|
@ -407,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Nem követőktől érkező értesítések figyelmen kívül hagyása?",
|
||||
"ignore_notifications_modal.not_following_title": "Nem követettektől érkező értesítések figyelmen kívül hagyása?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Figyelmen kívül hagyod a kéretlen privát említéseket?",
|
||||
"info_button.label": "Súgó",
|
||||
"info_button.what_is_alt_text": "<h1>Mi az alternatív szöveg?</h1> <p>Az alternatív szöveg képleírást biztosít a látássérültek, az alacsony sávszélességű kapcsolatokkal rendelkezők, illetve a bővebb kontextust keresők számára.</p> <p>Az egyértelmű, tömör és objektív alternatív szöveg megírásával mindenki számára akadálymentesebb és könnyebben érthető lesz.</p> <ul> <li>Rögzítsd a fontos elemeket.</li> <li>Foglald össze szövegesen a képeket.</li> <li>Használj szabályos mondatszerkezetet.</li> <li>Kerüld a felesleges információkat.</li> <li>Összetett vizuális ábrákon (például diagramokon vagy térképeken) összpontosíts a trendekre és a legfontosabb megállapításokra.</li> </ul>",
|
||||
"interaction_modal.action.favourite": "A folytatáshoz a fiókodból kell kedvencnek jelölnöd.",
|
||||
"interaction_modal.action.follow": "A folytatáshoz a fiókodból kell követned.",
|
||||
"interaction_modal.action.reblog": "A folytatáshoz a fiókodból kell megosztanod.",
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
"alt_text_modal.add_text_from_image": "Adder texto ab imagine",
|
||||
"alt_text_modal.cancel": "Cancellar",
|
||||
"alt_text_modal.change_thumbnail": "Cambiar le miniatura",
|
||||
"alt_text_modal.done": "Preste",
|
||||
"announcement.announcement": "Annuncio",
|
||||
"annual_report.summary.archetype.booster": "Le impulsator",
|
||||
"annual_report.summary.archetype.lurker": "Le lector",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "Upp kom óvænt villa.",
|
||||
"alert.unexpected.title": "Úbbs!",
|
||||
"alt_text_badge.title": "Hjálpartexti mynda",
|
||||
"alt_text_modal.add_alt_text": "Bæta við hjálpartexta",
|
||||
"alt_text_modal.add_text_from_image": "Bæta við texta úr mynd",
|
||||
"alt_text_modal.cancel": "Hætta við",
|
||||
"alt_text_modal.change_thumbnail": "Skipta um smámynd",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Lýstu þessu fyrir fólk með skerta heyrn…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Lýstu þessu fyrir fólk með skerta sjón…",
|
||||
"alt_text_modal.done": "Lokið",
|
||||
"announcement.announcement": "Auglýsing",
|
||||
"annual_report.summary.archetype.booster": "Svali gaurinn",
|
||||
"annual_report.summary.archetype.lurker": "Lurkurinn",
|
||||
|
@ -211,6 +218,10 @@
|
|||
"confirmations.logout.confirm": "Skrá út",
|
||||
"confirmations.logout.message": "Ertu viss um að þú viljir skrá þig út?",
|
||||
"confirmations.logout.title": "Skrá út?",
|
||||
"confirmations.missing_alt_text.confirm": "Bæta við hjálpartexta",
|
||||
"confirmations.missing_alt_text.message": "Færslan þín inniheldur myndefni án ALT-hjálpartexta. Ef þú bætir við lýsingu á myndefninu gerir það efnið þitt aðgengilegt fyrir fleira fólk.",
|
||||
"confirmations.missing_alt_text.secondary": "Birta samt",
|
||||
"confirmations.missing_alt_text.title": "Bæta við hjálpartexta?",
|
||||
"confirmations.mute.confirm": "Þagga",
|
||||
"confirmations.redraft.confirm": "Eyða og endurvinna drög",
|
||||
"confirmations.redraft.message": "Ertu viss um að þú viljir eyða þessari færslu og enduvinna drögin? Eftirlæti og endurbirtingar munu glatast og svör við upprunalegu færslunni munu verða munaðarlaus.",
|
||||
|
@ -407,6 +418,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Hunsa tilkynningar frá fólki sem fylgist ekki með þér?",
|
||||
"ignore_notifications_modal.not_following_title": "Hunsa tilkynningar frá fólki sem þú fylgist ekki með?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Hunsa tilkynningar frá óumbeðnum tilvísunum í einkaspjalli?",
|
||||
"info_button.label": "Hjálp",
|
||||
"info_button.what_is_alt_text": "<h1>Hvað er alt-texti?</h1> <p>Hjálpartexti eða ALT-myndatexti inniheldur lýsingu á myndefni fyrir fólk með ýmsar gerðir sjónskerðingar, fyrir tengingar með litla bandbreidd, eða til að gefa nánara samhengi fyrir myndefni.</p><p>Þú getur með þessu bætt almennt aðgengi og aukið skilning á efni sem þú birtir með því að skrifa skýran, skorinortan og hlutlægan alt-texta til vara.</p><ul><li>Lýstu mikilvægum atriðum</li>\\n<li>Hafðu yfirlit með þeim texta sem sést í myndum</li><li>Notaðu eðlilega setningaskipan</li><li>Forðastu óþarfar upplýsingar</li><li>Leggðu áherslu á aðalatriði í flóknu myndefni (eins og línuritum eða landakortum)</li></ul>",
|
||||
"interaction_modal.action.favourite": "Til að halda áfram þarftu að setja eitthvað í eftirlæti, verandi inni á aðgangnum þínum.",
|
||||
"interaction_modal.action.follow": "Til að halda áfram þarftu að fylgjast með einhverjum, verandi inni á aðgangnum þínum.",
|
||||
"interaction_modal.action.reblog": "Til að halda áfram þarftu að endurbirta frá einhverjum, verandi inni á aðgangnum þínum.",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"about.blocks": "Server moderati",
|
||||
"about.contact": "Contatto:",
|
||||
"about.contact": "Contatti:",
|
||||
"about.disclaimer": "Mastodon è un software libero e open-source e un marchio di Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo non disponibile",
|
||||
"about.domain_blocks.preamble": "Mastodon, generalmente, ti consente di visualizzare i contenuti e interagire con gli utenti da qualsiasi altro server nel fediverso. Queste sono le eccezioni che sono state fatte su questo particolare server.",
|
||||
|
@ -39,7 +39,7 @@
|
|||
"account.following_counter": "{count, plural, one {{counter} segui} other {{counter} segui}}",
|
||||
"account.follows.empty": "Questo utente non segue ancora nessuno.",
|
||||
"account.go_to_profile": "Vai al profilo",
|
||||
"account.hide_reblogs": "Nascondi potenziamenti da @{name}",
|
||||
"account.hide_reblogs": "Nascondi condivisioni da @{name}",
|
||||
"account.in_memoriam": "In memoria.",
|
||||
"account.joined_short": "Iscritto",
|
||||
"account.languages": "Modifica le lingue d'iscrizione",
|
||||
|
@ -61,7 +61,7 @@
|
|||
"account.requested": "In attesa d'approvazione. Clicca per annullare la richiesta di seguire",
|
||||
"account.requested_follow": "{name} ha richiesto di seguirti",
|
||||
"account.share": "Condividi il profilo di @{name}",
|
||||
"account.show_reblogs": "Mostra potenziamenti da @{name}",
|
||||
"account.show_reblogs": "Mostra condivisioni da @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} post}}",
|
||||
"account.unblock": "Sblocca @{name}",
|
||||
"account.unblock_domain": "Sblocca il dominio {domain}",
|
||||
|
@ -414,6 +414,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "Ignorare le notifiche delle persone che non ti seguono?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignorare le notifiche delle persone che non segui?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignorare le notifiche provenienti da menzioni private indesiderate?",
|
||||
"info_button.label": "Aiuto",
|
||||
"info_button.what_is_alt_text": "<h1>Cos'è il testo alternativo?</h1> <p>Il testo alternativo fornisce descrizioni delle immagini per le persone con disturbi della vista, connessioni a bassa larghezza di banda o per coloro che cercano un contesto aggiuntivo.</p> <p>È possibile migliorare l'accessibilità e la comprensione per tutti scrivendo un testo alt chiaro, conciso e obiettivo.</p> <ul> <li>Cattura elementi importanti</li> <li>Riassume il testo nelle immagini</li> <li>Usa la struttura delle frasi regolari</li> <li>Evita le informazioni ridondanti</li> <li>Concentrati sulle tendenze e i risultati chiave in immagini complesse (come diagrammi o mappe)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Per continuare, devi aggiungere ai preferiti il tuo account.",
|
||||
"interaction_modal.action.follow": "Per continuare, devi seguire dal tuo account.",
|
||||
"interaction_modal.action.reblog": "Per continuare, devi condividere dal tuo account.",
|
||||
|
@ -696,7 +698,7 @@
|
|||
"privacy.private.short": "Follower",
|
||||
"privacy.public.long": "Chiunque dentro e fuori Mastodon",
|
||||
"privacy.public.short": "Pubblico",
|
||||
"privacy.unlisted.additional": "Si comporta esattamente come pubblico, tranne per il fatto che il post non verrà visualizzato nei feed live o negli hashtag, nell'esplorazione o nella ricerca Mastodon, anche se hai attivato l'attivazione a livello di account.",
|
||||
"privacy.unlisted.additional": "",
|
||||
"privacy.unlisted.long": "Meno fanfare algoritmiche",
|
||||
"privacy.unlisted.short": "Pubblico silenzioso",
|
||||
"privacy_policy.last_updated": "Ultimo aggiornamento {date}",
|
||||
|
|
|
@ -98,6 +98,10 @@
|
|||
"alert.unexpected.message": "不明なエラーが発生しました。",
|
||||
"alert.unexpected.title": "エラー!",
|
||||
"alt_text_badge.title": "代替テキスト",
|
||||
"alt_text_modal.add_alt_text": "代替テキストを追加",
|
||||
"alt_text_modal.cancel": "キャンセル",
|
||||
"alt_text_modal.change_thumbnail": "サムネイルを変更",
|
||||
"alt_text_modal.done": "完了",
|
||||
"announcement.announcement": "お知らせ",
|
||||
"annual_report.summary.archetype.booster": "トレンドハンター",
|
||||
"annual_report.summary.archetype.lurker": "ROM専",
|
||||
|
@ -533,6 +537,8 @@
|
|||
"ignore_notifications_modal.not_followers_title": "本当に「フォローされていないアカウントからの通知」を無視するようにしますか?",
|
||||
"ignore_notifications_modal.not_following_title": "本当に「フォローしていないアカウントからの通知」を無視するようにしますか?",
|
||||
"ignore_notifications_modal.private_mentions_title": "本当に「外部からの非公開の返信」を無視するようにしますか?",
|
||||
"info_button.label": "ヘルプ",
|
||||
"info_button.what_is_alt_text": "<h1>代替テキストとは何ですか?</h1> <p>代替テキストは、視覚障害、低速ネットワーク接続の人や追加コンテンツを求める人に役立つ画像説明です。</p> <p>明確、簡潔、客観的に記述することでアクセシビリティが向上し、より多くの人に理解されるようになります。</p> <ul> <li>要点をとらえる</li> <li>画像内のテキストを要約する</li> <li>平易な文章で説明する</li> <li>情報の重複を避ける</li> <li>複雑な内容 (図や地図など) では傾向やポイントを見つける</li> </ul>",
|
||||
"interaction_modal.action.favourite": "お気に入り登録はあなたのアカウントがあるサーバーで行う必要があります。",
|
||||
"interaction_modal.action.follow": "ユーザーをフォローするには、あなたのアカウントがあるサーバーからフォローする必要があります。",
|
||||
"interaction_modal.action.reblog": "投稿をブーストするには、あなたのアカウントがあるサーバーでブーストする必要があります。",
|
||||
|
@ -1030,6 +1036,7 @@
|
|||
"status.redraft": "削除して下書きに戻す",
|
||||
"status.reference": "ひかえめな引用",
|
||||
"status.remove_bookmark": "ブックマークを削除",
|
||||
"status.remove_favourite": "お気に入りから削除",
|
||||
"status.replied_in_thread": "ほかのユーザーへ",
|
||||
"status.replied_to": "{name}さんへの返信",
|
||||
"status.reply": "返信",
|
||||
|
|
|
@ -74,7 +74,10 @@
|
|||
"alert.unexpected.message": "Yeḍra-d unezri ur netturaǧu ara.",
|
||||
"alert.unexpected.title": "Ayhuh!",
|
||||
"alt_text_badge.title": "Aḍris asegzan",
|
||||
"alt_text_modal.cancel": "Semmet",
|
||||
"alt_text_modal.done": "Immed",
|
||||
"announcement.announcement": "Ulɣu",
|
||||
"annual_report.summary.most_used_hashtag.none": "Ula yiwen",
|
||||
"audio.hide": "Ffer amesli",
|
||||
"block_modal.show_less": "Ssken-d drus",
|
||||
"block_modal.show_more": "Ssken-d ugar",
|
||||
|
@ -100,9 +103,11 @@
|
|||
"column.blocks": "Imiḍanen yettusḥebsen",
|
||||
"column.bookmarks": "Ticraḍ",
|
||||
"column.community": "Tasuddemt tadigant",
|
||||
"column.create_list": "Snulfu-d tabdart",
|
||||
"column.direct": "Tabdarin tusligin",
|
||||
"column.directory": "Inig deg imeɣna",
|
||||
"column.domain_blocks": "Taɣulin yeffren",
|
||||
"column.edit_list": "Ẓreg tabdart",
|
||||
"column.favourites": "Imenyafen",
|
||||
"column.follow_requests": "Isuturen n teḍfeṛt",
|
||||
"column.home": "Agejdan",
|
||||
|
@ -165,6 +170,7 @@
|
|||
"confirmations.unfollow.message": "Tetḥeqqeḍ belli tebɣiḍ ur teṭafaṛeḍ ara {name}?",
|
||||
"content_warning.hide": "Ffer tasuffeɣt",
|
||||
"content_warning.show": "Ssken-d akken tebɣu tili",
|
||||
"content_warning.show_more": "Sken-d ugar",
|
||||
"conversation.delete": "Kkes adiwenni",
|
||||
"conversation.mark_as_read": "Creḍ yettwaɣṛa",
|
||||
"conversation.open": "Ssken adiwenni",
|
||||
|
@ -325,11 +331,20 @@
|
|||
"link_preview.author": "S-ɣur {name}",
|
||||
"link_preview.more_from_author": "Ugar sɣur {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}}",
|
||||
"lists.add_member": "Rnu",
|
||||
"lists.add_to_list": "Rnu ɣer tebdart",
|
||||
"lists.add_to_lists": "Rnu {name} ɣer tebdarin",
|
||||
"lists.create": "Snulfu-d",
|
||||
"lists.delete": "Kkes tabdart",
|
||||
"lists.edit": "Ẓreg tabdart",
|
||||
"lists.list_name": "Isem n tebdart",
|
||||
"lists.new_list_name": "Isem n tebdart tamaynut",
|
||||
"lists.remove_member": "Kkes",
|
||||
"lists.replies_policy.followed": "Kra n useqdac i yettwaḍefren",
|
||||
"lists.replies_policy.list": "Iɛeggalen n tebdart",
|
||||
"lists.replies_policy.none": "Ula yiwen·t",
|
||||
"lists.save": "Sekles",
|
||||
"lists.search": "Nadi",
|
||||
"load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}",
|
||||
"loading_indicator.label": "Yessalay-d …",
|
||||
"media_gallery.hide": "Seggelmes",
|
||||
|
@ -399,6 +414,7 @@
|
|||
"notifications.column_settings.filter_bar.category": "Iri n usizdeg uzrib",
|
||||
"notifications.column_settings.follow": "Imeḍfaṛen imaynuten:",
|
||||
"notifications.column_settings.follow_request": "Isuturen imaynuten n teḍfeṛt:",
|
||||
"notifications.column_settings.group": "Agraw",
|
||||
"notifications.column_settings.mention": "Abdar:",
|
||||
"notifications.column_settings.poll": "Igemmaḍ n usenqed:",
|
||||
"notifications.column_settings.push": "Alɣuten yettudemmren",
|
||||
|
@ -429,6 +445,9 @@
|
|||
"notifications.policy.filter_private_mentions_title": "Abdar uslig ur yettwasferken ara",
|
||||
"notifications_permission_banner.enable": "Rmed alɣuten n tnarit",
|
||||
"notifications_permission_banner.title": "Ur zeggel acemma",
|
||||
"onboarding.follows.back": "Uɣal",
|
||||
"onboarding.follows.done": "Immed",
|
||||
"onboarding.follows.search": "Nadi",
|
||||
"onboarding.profile.display_name": "Isem ara d-yettwaskanen",
|
||||
"onboarding.profile.display_name_hint": "Isem-ik·im ummid neɣ isem-ik·im n uqeṣṣer…",
|
||||
"onboarding.profile.note": "Tameddurt",
|
||||
|
@ -524,6 +543,7 @@
|
|||
"search_results.accounts": "Imeɣna",
|
||||
"search_results.all": "Akk",
|
||||
"search_results.hashtags": "Ihacṭagen",
|
||||
"search_results.no_results": "Ulac igemmaḍ.",
|
||||
"search_results.see_all": "Wali-ten akk",
|
||||
"search_results.statuses": "Tisuffaɣ",
|
||||
"server_banner.active_users": "iseqdacen urmiden",
|
||||
|
|
|
@ -86,6 +86,13 @@
|
|||
"alert.unexpected.message": "예상하지 못한 에러가 발생했습니다.",
|
||||
"alert.unexpected.title": "앗!",
|
||||
"alt_text_badge.title": "대체 문구",
|
||||
"alt_text_modal.add_alt_text": "대체 텍스트 추가",
|
||||
"alt_text_modal.add_text_from_image": "이미지에서 텍스트 추가",
|
||||
"alt_text_modal.cancel": "취소",
|
||||
"alt_text_modal.change_thumbnail": "썸네일 변경",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "청력 장애가 있는 사람들을 위한 설명을 작성하세요…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "시각 장애가 있는 사람들을 위한 설명을 작성하세요…",
|
||||
"alt_text_modal.done": "완료",
|
||||
"announcement.announcement": "공지사항",
|
||||
"annual_report.summary.archetype.booster": "연쇄부스트마",
|
||||
"annual_report.summary.archetype.lurker": "은둔자",
|
||||
|
@ -407,6 +414,14 @@
|
|||
"ignore_notifications_modal.not_followers_title": "나를 팔로우하지 않는 사람들의 알림을 무시할까요?",
|
||||
"ignore_notifications_modal.not_following_title": "내가 팔로우하지 않는 사람들의 알림을 무시할까요?",
|
||||
"ignore_notifications_modal.private_mentions_title": "요청하지 않은 개인 멘션 알림을 무시할까요?",
|
||||
"info_button.label": "도움말",
|
||||
"interaction_modal.action.favourite": "계속하려면 내 계정으로 즐겨찾기해야 합니다.",
|
||||
"interaction_modal.action.follow": "계속하려면 내 계정으로 팔로우해야 합니다.",
|
||||
"interaction_modal.action.reblog": "계속하려면 내 계정으로 리블로그해야 합니다.",
|
||||
"interaction_modal.action.reply": "계속하려면 내 계정으로 답장해야 합니다.",
|
||||
"interaction_modal.action.vote": "계속하려면 내 계정으로 투표해야 합니다.",
|
||||
"interaction_modal.go": "이동",
|
||||
"interaction_modal.no_account_yet": "아직 계정이 없나요?",
|
||||
"interaction_modal.on_another_server": "다른 서버에",
|
||||
"interaction_modal.on_this_server": "이 서버에서",
|
||||
"interaction_modal.title.favourite": "{name} 님의 게시물을 좋아하기",
|
||||
|
@ -830,6 +845,7 @@
|
|||
"status.reblogs.empty": "아직 아무도 이 게시물을 부스트하지 않았습니다. 부스트 한 사람들이 여기에 표시 됩니다.",
|
||||
"status.redraft": "지우고 다시 쓰기",
|
||||
"status.remove_bookmark": "북마크 삭제",
|
||||
"status.remove_favourite": "즐겨찾기에서 제거",
|
||||
"status.replied_in_thread": "글타래에 답장",
|
||||
"status.replied_to": "{name} 님에게",
|
||||
"status.reply": "답장",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue