Merge pull request #2955 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to 5b291fcbe41564264954618fb1f4086a3be1c600
This commit is contained in:
Claire 2025-01-28 21:22:02 +01:00 committed by GitHub
commit 6be5a8da68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
227 changed files with 2962 additions and 1212 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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)
@ -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)

View File

@ -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

View File

@ -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

View File

@ -158,7 +158,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

View File

@ -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

View File

@ -41,9 +41,8 @@ module Admin
end
def resource_params
params.require(:user).permit(
:unconfirmed_email
)
params
.expect(user: [:unconfirmed_email])
end
end
end

View File

@ -47,7 +47,8 @@ module Admin
private
def resource_params
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
params
.expect(custom_emoji: [:shortcode, :image, :visible_in_picker])
end
def filtered_custom_emojis
@ -77,7 +78,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

View File

@ -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

View File

@ -25,7 +25,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
@ -114,7 +116,12 @@ 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_reports, :private_comment, :public_comment, :obfuscate])
params
.expect(
form_domain_block_batch: [
domain_blocks_attributes: [[:enabled, :domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate]],
]
)
end
def action_from_button

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -39,7 +39,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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,7 +28,8 @@ module Admin
end
def resource_params
params.require(:user).permit(:role_id)
params
.expect(user: [:role_id])
end
end
end

View File

@ -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

View File

@ -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

View File

@ -73,7 +73,7 @@ class Auth::SessionsController < Devise::SessionsController
end
def user_params
params.require(:user).permit(:email, :password, :otp_attempt, credential: {})
params.expect(user: [:email, :password, :otp_attempt, credential: {}])
end
def after_sign_in_path_for(resource)

View File

@ -24,6 +24,6 @@ module Admin::ExportControllerConcern
end
def import_params
params.require(:admin_import).permit(:data)
params.expect(admin_import: [:data])
end
end

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -19,6 +19,6 @@ class Settings::Preferences::BaseController < Settings::BaseController
end
def user_params
params.require(:user).permit(:locale, :time_zone, chosen_languages: [], settings_attributes: UserSettings.keys)
params.expect(user: [:locale, :time_zone, chosen_languages: [], settings_attributes: UserSettings.keys])
end
end

View File

@ -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

View File

@ -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

View File

@ -147,7 +147,6 @@ class Item extends PureComponent {
srcSet={srcSet}
sizes={sizes}
alt={description}
title={description}
lang={lang}
style={{ objectPosition: letterbox ? null : `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
@ -169,7 +168,6 @@ class Item extends PureComponent {
<video
className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
aria-label={description}
title={description}
lang={lang}
role='application'
src={attachment.get('url')}

View File

@ -96,7 +96,6 @@ export const MediaItem: React.FC<{
<img
src={previewUrl || avatarUrl}
alt={description}
title={description}
lang={lang}
onLoad={handleImageLoad}
/>
@ -116,7 +115,6 @@ export const MediaItem: React.FC<{
<img
src={previewUrl}
alt={description}
title={description}
lang={lang}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={handleImageLoad}
@ -134,7 +132,6 @@ export const MediaItem: React.FC<{
<video
className='media-gallery__item-gifv-thumbnail'
aria-label={description}
title={description}
lang={lang}
src={fullUrl}
onMouseEnter={handleMouseEnter}

View File

@ -0,0 +1,81 @@
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 QuestionMarkIcon from '@/material-icons/400-24px/question_mark.svg?react';
import { Icon } from 'flavours/glitch/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]);
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
{...props}
className='dialog-modal__popout prose dropdown-animation'
id={accessibilityId}
>
<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>
</>
);
};

View File

@ -36,6 +36,8 @@ import type { MediaAttachment } from 'flavours/glitch/models/media_attachment';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
import { assetHost } from 'flavours/glitch/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>

View File

@ -14,7 +14,6 @@ import AutosuggestInput from '../../../components/autosuggest_input';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { Button } from '../../../components/button';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container';
import PollButtonContainer from '../containers/poll_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
@ -26,6 +25,7 @@ import { CharacterCounter } from './character_counter';
import { ContentTypeButton } from './content_type_button';
import { EditIndicator } from './edit_indicator';
import { FederationButton } from './federation_button';
import { LanguageDropdown } from './language_dropdown';
import { NavigationBar } from './navigation_bar';
import { PollForm } from "./poll_form";
import { ReplyIndicator } from './reply_indicator';

View File

@ -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 'flavours/glitch/components/icon';
import { languages as preloadedLanguages } from 'flavours/glitch/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);

View File

@ -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 'flavours/glitch/actions/compose';
import { Icon } from 'flavours/glitch/components/icon';
import { languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import type { RootState } from 'flavours/glitch/store';
import { useAppSelector, useAppDispatch } from 'flavours/glitch/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>
);
};

View File

@ -1,23 +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';
import { urlRegex } from '../util/url_regex';
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
@ -72,47 +56,21 @@ const ISO_639_MAP = {
vie: 'vi', // Vietnamese
};
const debouncedLande = debounce((text) => {
const guessLanguage = (text) => {
text = text
.replace(urlRegex, '')
.replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '');
if (text.length <= 20)
return undefined;
return lande(text);
}, 500, { trailing: true });
const detectedLanguage = createSelector([
state => state.getIn(['compose', 'text']),
], text => {
if (text.length > 20) {
const guesses = debouncedLande(text);
if (!guesses)
return '';
const [lang, confidence] = guesses[0];
if (confidence > 0.8) {
const [lang, confidence] = lande(text)[0];
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, { leading: true, trailing: true });

View File

@ -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>

View File

@ -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 ? (

View File

@ -50,6 +50,34 @@
}
}
.help-button {
background: $ui-button-background-color;
border: 0;
color: $ui-button-color;
border-radius: 20px;
cursor: pointer;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
&:active,
&:focus,
&:hover {
background-color: $ui-button-focus-background-color;
}
&:focus-visible {
outline: $ui-button-icon-focus-outline;
}
.icon {
width: 14px;
height: 14px;
}
}
.button {
background-color: $ui-button-background-color;
border: 10px none;
@ -6429,6 +6457,20 @@ a.status-card {
}
}
&__popout {
background: var(--dropdown-background-color);
backdrop-filter: var(--background-filter);
border: 1px solid var(--dropdown-border-color);
box-shadow: var(--dropdown-shadow);
max-width: 320px;
padding: 16px;
border-radius: 8px;
z-index: 9999 !important;
font-size: 14px;
line-height: 20px;
color: $darker-text-color;
}
.copy-paste-text {
margin-bottom: 0;
}

View File

@ -83,9 +83,12 @@ code {
&__toolbar {
margin-top: 16px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
.character-counter {
flex: 0 0 auto;
}
}
&.hidden {
@ -566,7 +569,7 @@ code {
margin-bottom: 15px;
}
button:not(.button, .link-button) {
button:not(.button, .link-button, .help-button) {
display: block;
width: 100%;
border: 0;

View File

@ -514,6 +514,15 @@
background: darken($ui-base-color, 10%);
}
.dropdown-button.warning {
border-color: #b3261e;
color: #b3261e;
&.active {
background-color: #f9dedc;
}
}
.search__popout__menu__item {
&:hover,
&:active,

View File

@ -145,7 +145,6 @@ class Item extends PureComponent {
srcSet={srcSet}
sizes={sizes}
alt={description}
title={description}
lang={lang}
style={{ objectPosition: `${x}% ${y}%` }}
onLoad={this.handleImageLoad}
@ -167,7 +166,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')}

View File

@ -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}

View File

@ -0,0 +1,81 @@
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 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]);
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
{...props}
className='dialog-modal__popout prose dropdown-animation'
id={accessibilityId}
>
<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>
</>
);
};

View File

@ -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>

View File

@ -14,7 +14,6 @@ import AutosuggestInput from '../../../components/autosuggest_input';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { Button } from '../../../components/button';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import LanguageDropdown from '../containers/language_dropdown_container';
import PollButtonContainer from '../containers/poll_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
@ -24,6 +23,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';

View File

@ -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);

View File

@ -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>
);
};

View File

@ -1,23 +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 'flavours/glitch/actions/compose';
import LanguageDropdown from '../components/language_dropdown';
import { urlRegex } from '../util/url_regex';
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
@ -72,47 +56,21 @@ const ISO_639_MAP = {
vie: 'vi', // Vietnamese
};
const debouncedLande = debounce((text) => {
const guessLanguage = (text) => {
text = text
.replace(urlRegex, '')
.replace(/(^|[^/\w])@(([a-z0-9_]+)@[a-z0-9.-]+[a-z0-9]+)/ig, '');
if (text.length <= 20)
return undefined;
return lande(text);
}, 500, { trailing: true });
const detectedLanguage = createSelector([
state => state.getIn(['compose', 'text']),
], text => {
if (text.length > 20) {
const guesses = debouncedLande(text);
if (!guesses)
return '';
const [lang, confidence] = guesses[0];
if (confidence > 0.8) {
const [lang, confidence] = lande(text)[0];
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, { leading: true, trailing: true });

View File

@ -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>

View File

@ -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 ? (

View File

@ -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 {# دقائق}} متبقية",

View File

@ -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": "Трябва да разпространите нечий блог от акаунта си, за да продължите.",

View File

@ -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",
@ -414,6 +414,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.",

View File

@ -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.",

View File

@ -414,6 +414,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.",

View File

@ -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",
@ -414,6 +414,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.",

View File

@ -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": "Απάντησε",

View File

@ -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.",

View File

@ -414,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>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.",

View File

@ -414,6 +414,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.",

View File

@ -414,6 +414,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.",

View File

@ -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",
@ -414,6 +414,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.",

View File

@ -414,6 +414,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.",

View File

@ -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",

View File

@ -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.",

View File

@ -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": "بستن ویدیو",

View File

@ -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ä",
@ -407,6 +414,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.",

View File

@ -414,6 +414,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.",

View File

@ -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.",

View File

@ -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",

View File

@ -414,6 +414,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.",

View File

@ -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": "כדי להמשיך, עליך להדהד מחשבונך.",

View File

@ -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ő",
@ -407,6 +414,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.",

View File

@ -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",

View File

@ -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",
@ -407,6 +414,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.",

View File

@ -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}",

View File

@ -86,6 +86,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専",
@ -407,6 +411,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": "投稿をブーストするには、あなたのアカウントがあるサーバーでブーストする必要があります。",
@ -836,6 +842,7 @@
"status.reblogs.empty": "まだ誰もブーストしていません。ブーストされるとここに表示されます。",
"status.redraft": "削除して下書きに戻す",
"status.remove_bookmark": "ブックマークを削除",
"status.remove_favourite": "お気に入りから削除",
"status.replied_in_thread": "ほかのユーザーへ",
"status.replied_to": "{name}さんへの返信",
"status.reply": "返信",

View File

@ -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",

View File

@ -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": "답장",

View File

@ -86,6 +86,13 @@
"alert.unexpected.message": "Įvyko netikėta klaida.",
"alert.unexpected.title": "Ups!",
"alt_text_badge.title": "Alternatyvus tekstas",
"alt_text_modal.add_alt_text": "Pridėti alternatyvųjį tekstą",
"alt_text_modal.add_text_from_image": "Pridėti tekstą iš vaizdo",
"alt_text_modal.cancel": "Atšaukti",
"alt_text_modal.change_thumbnail": "Keisti miniatiūrą",
"alt_text_modal.describe_for_people_with_hearing_impairments": "Aprašykite tai klausos negalią turintiems asmenims…",
"alt_text_modal.describe_for_people_with_visual_impairments": "Aprašykite tai regos sutrikimų turintiems asmenims…",
"alt_text_modal.done": "Atlikta",
"announcement.announcement": "Skelbimas",
"annual_report.summary.archetype.booster": "Šaunus medžiotojas",
"annual_report.summary.archetype.lurker": "Stebėtojas",
@ -287,7 +294,7 @@
"empty_column.community": "Vietinė laiko skalė yra tuščia. Parašyk ką nors viešai, kad pradėtum sąveikauti.",
"empty_column.direct": "Dar neturi jokių privačių paminėjimų. Kai išsiųsi arba gausi vieną iš jų, jis bus rodomas čia.",
"empty_column.domain_blocks": "Kol kas nėra užblokuotų serverių.",
"empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrink vėliau!",
"empty_column.explore_statuses": "Šiuo metu niekas nėra tendencinga. Patikrinkite vėliau!",
"empty_column.favourited_statuses": "Dar neturi mėgstamų įrašų. Kai vieną iš jų pamėgsi, jis bus rodomas čia.",
"empty_column.favourites": "Šio įrašo dar niekas nepamėgo. Kai kas nors tai padarys, jie bus rodomi čia.",
"empty_column.follow_requests": "Dar neturi jokių sekimo prašymų. Kai gausi tokį prašymą, jis bus rodomas čia.",
@ -403,6 +410,8 @@
"ignore_notifications_modal.not_followers_title": "Ignoruoti pranešimus iš žmonių, kurie tave neseka?",
"ignore_notifications_modal.not_following_title": "Ignoruoti pranešimus iš žmonių, kuriuos neseki?",
"ignore_notifications_modal.private_mentions_title": "Ignoruoti pranešimus iš neprašytų privačių paminėjimų?",
"info_button.label": "Žinynas",
"info_button.what_is_alt_text": "<h1>Kas yra alternatyvusis tekstas?</h1> <p>Alternatyvusis tekstas pateikia vaizdų aprašymus asmenims su regos sutrikimais, turintiems mažo pralaidumo ryšį arba ieškantiems papildomo konteksto.</p> <p>Galite pagerinti prieinamumą ir suprantamumą visiems, jei parašysite aiškų, glaustą ir objektyvų alternatyvųjį tekstą.</p> <ul> <li>Užfiksuokite svarbiausius elementus.</li> <li>Apibendrinkite tekstą vaizduose.</li> <li>Naudokite įprasta sakinio struktūrą.</li> <li>Venkite nereikalingos informacijos.</li> <li>Sutelkite dėmesį į tendencijas ir pagrindines išvadas sudėtinguose vaizdiniuose (tokiuose kaip diagramos ar žemėlapiai).</li> </ul>",
"interaction_modal.action.favourite": "Kad tęstumėte, turite pamėgti iš savo paskyros.",
"interaction_modal.action.follow": "Kad tęstumėte, turite sekti iš savo paskyros.",
"interaction_modal.action.reblog": "Kad tęstumėte, turite pasidalinti iš savo paskyros.",
@ -707,7 +716,7 @@
"report.categories.violation": "Turinys pažeidžia vieną ar daugiau serverio taisyklių",
"report.category.subtitle": "Pasirink geriausią atitikmenį.",
"report.category.title": "Papasakok mums, kas vyksta su šiuo {type}",
"report.category.title_account": "profilis",
"report.category.title_account": "profiliu",
"report.category.title_status": "įrašas",
"report.close": "Atlikta",
"report.comment.title": "Ar yra dar kas nors, ką, tavo manymu, turėtume žinoti?",

View File

@ -414,6 +414,8 @@
"ignore_notifications_modal.not_followers_title": "Meldingen negeren van mensen die jou niet volgen?",
"ignore_notifications_modal.not_following_title": "Meldingen negeren van mensen die je niet volgt?",
"ignore_notifications_modal.private_mentions_title": "Meldingen negeren van ongevraagde privéberichten?",
"info_button.label": "Help",
"info_button.what_is_alt_text": "<h1>Wat is alt-tekst?</h1> <p>Alt-tekst biedt beschrijvingen van afbeeldingen voor mensen met een visuele beperking, voor verbindingen met lage internetsnelheid of mensen die op zoek zijn naar extra context.</p> <p>Je kunt de toegankelijkheid en de begrijpelijkheid voor iedereen verbeteren door heldere, beknopte en objectieve alt-teksten te schrijven.</p> <ul> <li>Leg belangrijke elementen vast</li> <li>Tekst in afbeeldingen samenvatten</li> <li>Een eenvoudige zinsbouw gebruiken</li> <li>Overbodige informatie vermijden</li> <li>Voor complexe diagrammen of kaarten alleen op trends en belangrijke bevindingen focussen</li> </ul>",
"interaction_modal.action.favourite": "Om verder te gaan, moet je vanaf je eigen account als favoriet markeren.",
"interaction_modal.action.follow": "Om verder te gaan, moet je vanaf je eigen account volgen.",
"interaction_modal.action.reblog": "Om verder te gaan, moet je vanaf je eigen account boosten.",

View File

@ -86,6 +86,13 @@
"alert.unexpected.message": "Wystąpił nieoczekiwany błąd.",
"alert.unexpected.title": "Ups!",
"alt_text_badge.title": "Tekst alternatywny",
"alt_text_modal.add_alt_text": "Dodaj tekst alternatywny",
"alt_text_modal.add_text_from_image": "Dodaj tekst z obrazu",
"alt_text_modal.cancel": "Anuluj",
"alt_text_modal.change_thumbnail": "Zmień miniaturę",
"alt_text_modal.describe_for_people_with_hearing_impairments": "Opisz to dla osób z wadą słuchu…",
"alt_text_modal.describe_for_people_with_visual_impairments": "Opisz to dla osób z wadą wzroku…",
"alt_text_modal.done": "Gotowe",
"announcement.announcement": "Ogłoszenie",
"annual_report.summary.archetype.booster": "Łowca treści",
"annual_report.summary.archetype.lurker": "Czyhający",
@ -103,6 +110,7 @@
"annual_report.summary.most_used_hashtag.most_used_hashtag": "najczęściej używany hashtag",
"annual_report.summary.most_used_hashtag.none": "Brak",
"annual_report.summary.new_posts.new_posts": "nowe wpisy",
"annual_report.summary.percentile.text": "<topLabel>Plasuje Cię w czołówce</topLabel><percentage></percentage><bottomLabel> użytkowników {domain}.</bottomLabel>",
"annual_report.summary.percentile.we_wont_tell_bernie": "Nie powiemy Berniemu.",
"annual_report.summary.thanks": "Dziękujemy, że jesteś częścią Mastodona!",
"attachments_list.unprocessed": "(nieprzetworzone)",
@ -408,6 +416,10 @@
"ignore_notifications_modal.private_mentions_title": "Ignoruj powiadomienia o nieproszonych wzmiankach prywatnych?",
"interaction_modal.action.favourite": "Aby kontynuować, musisz dodać do ulubionych na swoim koncie.",
"interaction_modal.action.follow": "Aby kontynuować, musisz obserwować ze swojego konta.",
"interaction_modal.action.reblog": "Aby kontynuować, musisz podać dalej ze swojego konta.",
"interaction_modal.action.reply": "Aby kontynuować, musisz odpowiedzieć ze swojego konta.",
"interaction_modal.action.vote": "Aby kontynuować, musisz zagłosować ze swojego konta.",
"interaction_modal.go": "Dalej",
"interaction_modal.no_account_yet": "Nie masz jeszcze konta?",
"interaction_modal.on_another_server": "Na innym serwerze",
"interaction_modal.on_this_server": "Na tym serwerze",
@ -452,6 +464,7 @@
"keyboard_shortcuts.toggle_hidden": "aby wyświetlić lub ukryć wpis spod CW",
"keyboard_shortcuts.toggle_sensitivity": "Pokaż/ukryj multimedia",
"keyboard_shortcuts.toot": "Stwórz nowy post",
"keyboard_shortcuts.translate": "Aby przetłumaczyć post",
"keyboard_shortcuts.unfocus": "aby opuścić pole wyszukiwania/pisania",
"keyboard_shortcuts.up": "aby przejść na górę listy",
"lightbox.close": "Zamknij",
@ -690,6 +703,8 @@
"privacy_policy.title": "Polityka prywatności",
"recommended": "Zalecane",
"refresh": "Odśwież",
"regeneration_indicator.please_stand_by": "Proszę czekać.",
"regeneration_indicator.preparing_your_home_feed": "Przygotowywanie Twojego kanału wiadomości...",
"relative_time.days": "{number} dni",
"relative_time.full.days": "{number, plural, one {# dzień} few {# dni} many {# dni} other {# dni}} temu",
"relative_time.full.hours": "{number, plural, one {# godzinę} few {# godziny} many {# godzin} other {# godzin}} temu",

View File

@ -414,9 +414,11 @@
"ignore_notifications_modal.not_followers_title": "Ignorar notificações de pessoas que não te seguem?",
"ignore_notifications_modal.not_following_title": "Ignorar notificações de pessoas que não segues?",
"ignore_notifications_modal.private_mentions_title": "Ignorar notificações de menções privadas não solicitadas?",
"info_button.label": "Ajuda",
"info_button.what_is_alt_text": "<h1>O que é texto alternativo?</h1> <p>O texto alternativo fornece descrições de imagens para pessoas com deficiências visuais, conexões de baixa largura de banda ou pessoas que procuram um contexto adicional.</p> <p>Podes melhorar a acessibilidade e a compreensão para todos escrevendo um texto alternativo claro, conciso e objetivo.</p> <ul> <li>Capta elementos importantes</li> <li>Resume o texto que aparece nas imagens</li> <li>Usa uma estrutura de frase regular</li> <li>Evita informações redundantes</li> <li>Centra-te nas tendências e nas principais conclusões em imagens complexas (como diagramas ou mapas)</li> </ul>",
"interaction_modal.action.favourite": "Para continuar, tens de adicionar um favorito na tua conta.",
"interaction_modal.action.follow": "Para continuar, tens de seguir alguém na tua conta.",
"interaction_modal.action.reblog": "Para continuar, tens de fazer uma republicação na tua conta.",
"interaction_modal.action.reblog": "Para continuar, tens de impulsionar desde a tua conta.",
"interaction_modal.action.reply": "Para continuar, tens de fazer uma resposta na tua conta.",
"interaction_modal.action.vote": "Para continuar é necessário votar a partir da tua conta.",
"interaction_modal.go": "Ir",

View File

@ -409,6 +409,8 @@
"ignore_notifications_modal.not_followers_title": "Të shpërfillen njoftime nga persona që sju ndjekin?",
"ignore_notifications_modal.not_following_title": "Të shpërfillen njoftime nga persona që si ndiqni?",
"ignore_notifications_modal.private_mentions_title": "Të shpërfillen njoftime nga Përmendje Private të pakërkuara?",
"info_button.label": "Ndihmë",
"info_button.what_is_alt_text": "<h1>Çështë teksti alternativ?</h1> <p>Teksti alternativ jep përshkrime figurash për persona me mangësi në të parët, lidhje me gjerësi bande të ulët, ose për ata që duan kontekst shtesë.</p> <p>Mund të përmirësoni përdorimin nga persona me aftësi të kufizuara dhe kuptimin për këto, duke shkruar tekst alternativ të qartë, konciz dhe objektiv.</p> <ul> <li>Rrokni elementët e rëndësishëm</li> <li>Përmblidhni tekst në figura</li> <li>Përdorni strukturë të rregullt fjalish</li> <li>Shmangni përsëritje informacioni</li> <li>Në aspekte pamore të ndërlikuara (fjala vjen, diagrame ose harta) përqendrohuni te prirje dhe gjetje gjërash kyçe</li> </ul>",
"interaction_modal.action.favourite": "Që të vazhdoni, lypset ti vini shenjë si i parapëlqyer që nga llogaria juaj.",
"interaction_modal.action.follow": "Që të vazhdoni, lypset ta ndiqni që nga llogaria juaj.",
"interaction_modal.action.reblog": "Që të vazhdoni, lypset ta riblogoni që nga llogaria juaj.",

View File

@ -523,7 +523,7 @@
"navigation_bar.favourites": "Favoriter",
"navigation_bar.filters": "Tystade ord",
"navigation_bar.follow_requests": "Följförfrågningar",
"navigation_bar.followed_tags": "Utvalda hashtags",
"navigation_bar.followed_tags": "Följda hashtaggar",
"navigation_bar.follows_and_followers": "Följer och följare",
"navigation_bar.lists": "Listor",
"navigation_bar.logout": "Logga ut",

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