Merge commit 'c87b05282909383353a9561e97b9f18a2db0766d' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2025-11-14 18:19:00 +01:00
83 changed files with 238 additions and 299 deletions

View File

@@ -1,7 +1,7 @@
import { createRoot } from 'react-dom/client';
import Rails from '@rails/ujs';
import { decode, ValidationError } from 'blurhash';
import { on } from 'delegated-events';
import ready from '../mastodon/ready';
@@ -24,10 +24,9 @@ const setAnnouncementEndsAttributes = (target: HTMLInputElement) => {
}
};
Rails.delegate(
document,
'input[type="datetime-local"]#announcement_starts_at',
on(
'change',
'input[type="datetime-local"]#announcement_starts_at',
({ target }) => {
if (target instanceof HTMLInputElement)
setAnnouncementEndsAttributes(target);
@@ -63,7 +62,7 @@ const hideSelectAll = () => {
if (hiddenField) hiddenField.value = '0';
};
Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
on('change', '#batch_checkbox_all', ({ target }) => {
if (!(target instanceof HTMLInputElement)) return;
const selectAllMatchingElement = document.querySelector(
@@ -85,7 +84,7 @@ Rails.delegate(document, '#batch_checkbox_all', 'change', ({ target }) => {
}
});
Rails.delegate(document, '.batch-table__select-all button', 'click', () => {
on('click', '.batch-table__select-all button', () => {
const hiddenField = document.querySelector<HTMLInputElement>(
'#select_all_matching',
);
@@ -113,7 +112,7 @@ Rails.delegate(document, '.batch-table__select-all button', 'click', () => {
}
});
Rails.delegate(document, batchCheckboxClassName, 'change', () => {
on('change', batchCheckboxClassName, () => {
const checkAllElement = document.querySelector<HTMLInputElement>(
'input#batch_checkbox_all',
);
@@ -140,14 +139,9 @@ Rails.delegate(document, batchCheckboxClassName, 'change', () => {
}
});
Rails.delegate(
document,
'.filter-subset--with-select select',
'change',
({ target }) => {
if (target instanceof HTMLSelectElement) target.form?.submit();
},
);
on('change', '.filter-subset--with-select select', ({ target }) => {
if (target instanceof HTMLSelectElement) target.form?.submit();
});
const onDomainBlockSeverityChange = (target: HTMLSelectElement) => {
const rejectMediaDiv = document.querySelector(
@@ -168,11 +162,11 @@ const onDomainBlockSeverityChange = (target: HTMLSelectElement) => {
}
};
Rails.delegate(document, '#domain_block_severity', 'change', ({ target }) => {
on('change', '#domain_block_severity', ({ target }) => {
if (target instanceof HTMLSelectElement) onDomainBlockSeverityChange(target);
});
const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => {
function onEnableBootstrapTimelineAccountsChange(target: HTMLInputElement) {
const bootstrapTimelineAccountsField =
document.querySelector<HTMLInputElement>(
'#form_admin_settings_bootstrap_timeline_accounts',
@@ -194,12 +188,11 @@ const onEnableBootstrapTimelineAccountsChange = (target: HTMLInputElement) => {
);
}
}
};
}
Rails.delegate(
document,
'#form_admin_settings_enable_bootstrap_timeline_accounts',
on(
'change',
'#form_admin_settings_enable_bootstrap_timeline_accounts',
({ target }) => {
if (target instanceof HTMLInputElement)
onEnableBootstrapTimelineAccountsChange(target);
@@ -239,11 +232,11 @@ const onChangeRegistrationMode = (target: HTMLSelectElement) => {
});
};
const convertUTCDateTimeToLocal = (value: string) => {
function convertUTCDateTimeToLocal(value: string) {
const date = new Date(value + 'Z');
const twoChars = (x: number) => x.toString().padStart(2, '0');
return `${date.getFullYear()}-${twoChars(date.getMonth() + 1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`;
};
}
function convertLocalDatetimeToUTC(value: string) {
const date = new Date(value);
@@ -251,14 +244,9 @@ function convertLocalDatetimeToUTC(value: string) {
return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
}
Rails.delegate(
document,
'#form_admin_settings_registrations_mode',
'change',
({ target }) => {
if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target);
},
);
on('change', '#form_admin_settings_registrations_mode', ({ target }) => {
if (target instanceof HTMLSelectElement) onChangeRegistrationMode(target);
});
async function mountReactComponent(element: Element) {
const componentName = element.getAttribute('data-admin-component');
@@ -305,7 +293,7 @@ ready(() => {
if (registrationMode) onChangeRegistrationMode(registrationMode);
const checkAllElement = document.querySelector<HTMLInputElement>(
'input#batch_checkbox_all',
'#batch_checkbox_all',
);
if (checkAllElement) {
const allCheckboxes = Array.from(
@@ -318,7 +306,7 @@ ready(() => {
}
document
.querySelector('a#add-instance-button')
.querySelector<HTMLAnchorElement>('a#add-instance-button')
?.addEventListener('click', (e) => {
const domain = document.querySelector<HTMLInputElement>(
'input[type="text"]#by_domain',
@@ -342,7 +330,7 @@ ready(() => {
}
});
Rails.delegate(document, 'form', 'submit', ({ target }) => {
on('submit', 'form', ({ target }) => {
if (target instanceof HTMLFormElement)
target
.querySelectorAll<HTMLInputElement>('input[type="datetime-local"]')

View File

@@ -4,8 +4,8 @@ import { IntlMessageFormat } from 'intl-messageformat';
import type { MessageDescriptor, PrimitiveType } from 'react-intl';
import { defineMessages } from 'react-intl';
import Rails from '@rails/ujs';
import axios from 'axios';
import { on } from 'delegated-events';
import { throttle } from 'lodash';
import { timeAgoString } from '../mastodon/components/relative_timestamp';
@@ -175,10 +175,9 @@ function loaded() {
});
}
Rails.delegate(
document,
'input#user_account_attributes_username',
on(
'input',
'input#user_account_attributes_username',
throttle(
({ target }) => {
if (!(target instanceof HTMLInputElement)) return;
@@ -202,60 +201,47 @@ function loaded() {
),
);
Rails.delegate(
document,
'#user_password,#user_password_confirmation',
'input',
() => {
const password = document.querySelector<HTMLInputElement>(
'input#user_password',
);
const confirmation = document.querySelector<HTMLInputElement>(
'input#user_password_confirmation',
);
if (!confirmation || !password) return;
on('input', '#user_password,#user_password_confirmation', () => {
const password = document.querySelector<HTMLInputElement>(
'input#user_password',
);
const confirmation = document.querySelector<HTMLInputElement>(
'input#user_password_confirmation',
);
if (!confirmation || !password) return;
if (
confirmation.value &&
confirmation.value.length > password.maxLength
) {
confirmation.setCustomValidity(
formatMessage(messages.passwordExceedsLength),
);
} else if (password.value && password.value !== confirmation.value) {
confirmation.setCustomValidity(
formatMessage(messages.passwordDoesNotMatch),
);
} else {
confirmation.setCustomValidity('');
}
},
);
if (confirmation.value && confirmation.value.length > password.maxLength) {
confirmation.setCustomValidity(
formatMessage(messages.passwordExceedsLength),
);
} else if (password.value && password.value !== confirmation.value) {
confirmation.setCustomValidity(
formatMessage(messages.passwordDoesNotMatch),
);
} else {
confirmation.setCustomValidity('');
}
});
}
Rails.delegate(
document,
'#edit_profile input[type=file]',
'change',
({ target }) => {
if (!(target instanceof HTMLInputElement)) return;
on('change', '#edit_profile input[type=file]', ({ target }) => {
if (!(target instanceof HTMLInputElement)) return;
const avatar = document.querySelector<HTMLImageElement>(
`img#${target.id}-preview`,
);
const avatar = document.querySelector<HTMLImageElement>(
`img#${target.id}-preview`,
);
if (!avatar) return;
if (!avatar) return;
let file: File | undefined;
if (target.files) file = target.files[0];
let file: File | undefined;
if (target.files) file = target.files[0];
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
if (url) avatar.src = url;
},
);
if (url) avatar.src = url;
});
Rails.delegate(document, '.input-copy input', 'click', ({ target }) => {
on('click', '.input-copy input', ({ target }) => {
if (!(target instanceof HTMLInputElement)) return;
target.focus();
@@ -263,7 +249,7 @@ Rails.delegate(document, '.input-copy input', 'click', ({ target }) => {
target.setSelectionRange(0, target.value.length);
});
Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
on('click', '.input-copy button', ({ target }) => {
if (!(target instanceof HTMLButtonElement)) return;
const input = target.parentNode?.querySelector<HTMLInputElement>(
@@ -312,22 +298,22 @@ const toggleSidebar = () => {
sidebar.classList.toggle('visible');
};
Rails.delegate(document, '.sidebar__toggle__icon', 'click', () => {
on('click', '.sidebar__toggle__icon', () => {
toggleSidebar();
});
Rails.delegate(document, '.sidebar__toggle__icon', 'keydown', (e) => {
on('keydown', '.sidebar__toggle__icon', (e) => {
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
toggleSidebar();
}
});
Rails.delegate(document, 'img.custom-emoji', 'mouseover', ({ target }) => {
on('mouseover', 'img.custom-emoji', ({ target }) => {
if (target instanceof HTMLImageElement && target.dataset.original)
target.src = target.dataset.original;
});
Rails.delegate(document, 'img.custom-emoji', 'mouseout', ({ target }) => {
on('mouseout', 'img.custom-emoji', ({ target }) => {
if (target instanceof HTMLImageElement && target.dataset.static)
target.src = target.dataset.static;
});
@@ -376,22 +362,17 @@ const setInputHint = (
}
};
Rails.delegate(
document,
'#account_statuses_cleanup_policy_enabled',
'change',
({ target }) => {
if (!(target instanceof HTMLInputElement) || !target.form) return;
on('change', '#account_statuses_cleanup_policy_enabled', ({ target }) => {
if (!(target instanceof HTMLInputElement) || !target.form) return;
target.form
.querySelectorAll<
HTMLInputElement | HTMLSelectElement
>('input:not([type=hidden], #account_statuses_cleanup_policy_enabled), select')
.forEach((input) => {
setInputDisabled(input, !target.checked);
});
},
);
target.form
.querySelectorAll<
HTMLInputElement | HTMLSelectElement
>('input:not([type=hidden], #account_statuses_cleanup_policy_enabled), select')
.forEach((input) => {
setInputDisabled(input, !target.checked);
});
});
const updateDefaultQuotePrivacyFromPrivacy = (
privacySelect: EventTarget | null,
@@ -414,18 +395,13 @@ const updateDefaultQuotePrivacyFromPrivacy = (
}
};
Rails.delegate(
document,
'#user_settings_attributes_default_privacy',
'change',
({ target }) => {
updateDefaultQuotePrivacyFromPrivacy(target);
},
);
on('change', '#user_settings_attributes_default_privacy', ({ target }) => {
updateDefaultQuotePrivacyFromPrivacy(target);
});
// Empty the honeypot fields in JS in case something like an extension
// automatically filled them.
Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => {
on('submit', '#registration_new_user,#new_user', () => {
[
'user_website',
'user_confirm_password',
@@ -439,7 +415,7 @@ Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => {
});
});
Rails.delegate(document, '.rules-list button', 'click', ({ target }) => {
on('click', '.rules-list button', ({ target }) => {
if (!(target instanceof HTMLElement)) {
return;
}

View File

@@ -675,8 +675,8 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
dispatch(useEmoji(suggestion));
} else if (suggestion.type === 'hashtag') {
completion = suggestion.name.slice(token.length - 1);
startPosition = position + token.length;
completion = token + suggestion.name.slice(token.length - 1);
startPosition = position - 1;
} else if (suggestion.type === 'account') {
completion = `@${getState().getIn(['accounts', suggestion.id, 'acct'])}`;
startPosition = position - 1;

View File

@@ -112,7 +112,7 @@ export function normalizeStatus(status, normalOldStatus, { bogusQuotePolicy = fa
}
if (normalOldStatus) {
normalStatus.quote_approval ||= normalOldStatus.quote_approval;
normalStatus.quote_approval ||= normalOldStatus.get('quote_approval');
const list = normalOldStatus.get('media_attachments');
if (normalStatus.media_attachments && list) {

View File

@@ -50,6 +50,7 @@ const AutosuggestTextarea = forwardRef(({
onKeyUp,
onKeyDown,
onPaste,
onDrop,
onFocus,
autoFocus = true,
lang,
@@ -153,6 +154,12 @@ const AutosuggestTextarea = forwardRef(({
onPaste(e);
}, [onPaste]);
const handleDrop = useCallback((e) => {
if (onDrop) {
onDrop(e);
}
}, [onDrop]);
// Show the suggestions again whenever they change and the textarea is focused
useEffect(() => {
if (suggestions.size > 0 && textareaRef.current === document.activeElement) {
@@ -204,6 +211,7 @@ const AutosuggestTextarea = forwardRef(({
onFocus={handleFocus}
onBlur={handleBlur}
onPaste={handlePaste}
onDrop={handleDrop}
dir='auto'
aria-autocomplete='list'
aria-label={placeholder}
@@ -235,6 +243,7 @@ AutosuggestTextarea.propTypes = {
onKeyUp: PropTypes.func,
onKeyDown: PropTypes.func,
onPaste: PropTypes.func.isRequired,
onDrop: PropTypes.func,
onFocus:PropTypes.func,
autoFocus: PropTypes.bool,
lang: PropTypes.string,

View File

@@ -27,12 +27,14 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
}) => {
// Handle hashtags
if (
text.startsWith('#') ||
prevText?.endsWith('#') ||
text.startsWith('') ||
prevText?.endsWith('')
(text.startsWith('#') ||
prevText?.endsWith('#') ||
text.startsWith('') ||
prevText?.endsWith('')) &&
!text.includes('%')
) {
const hashtag = text.slice(1).trim();
return (
<Link
className={classNames('mention hashtag', className)}
@@ -69,7 +71,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
return (
<a
{...props}
href={href}
href={encodeURI(href)}
title={href}
className={classNames('unhandled-link', className)}
target='_blank'

View File

@@ -64,6 +64,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionSelected: PropTypes.func.isRequired,
onChangeSpoilerText: PropTypes.func.isRequired,
onPaste: PropTypes.func.isRequired,
onDrop: PropTypes.func.isRequired,
onPickEmoji: PropTypes.func.isRequired,
autoFocus: PropTypes.bool,
withoutNavigation: PropTypes.bool,
@@ -102,6 +103,7 @@ class ComposeForm extends ImmutablePureComponent {
handleKeyDownPost = (e) => {
if (e.key.toLowerCase() === 'enter' && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
e.preventDefault();
}
this.blurOnEscape(e);
};
@@ -248,7 +250,7 @@ class ComposeForm extends ImmutablePureComponent {
};
render () {
const { intl, onPaste, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props;
const { intl, onPaste, onDrop, autoFocus, withoutNavigation, maxChars, isSubmitting } = this.props;
const { highlighted } = this.state;
return (
@@ -304,6 +306,7 @@ class ComposeForm extends ImmutablePureComponent {
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
onPaste={onPaste}
onDrop={onDrop}
autoFocus={autoFocus}
lang={this.props.lang}
className='compose-form__input'

View File

@@ -18,6 +18,23 @@ import ComposeForm from '../components/compose_form';
const urlLikeRegex = /^https?:\/\/[^\s]+\/[^\s]+$/i;
const processPasteOrDrop = (transfer, e, dispatch) => {
if (transfer && transfer.files.length === 1) {
dispatch(uploadCompose(transfer.files));
e.preventDefault();
} else if (transfer && transfer.files.length === 0) {
const data = transfer.getData('text/plain');
if (!data.match(urlLikeRegex)) return;
try {
const url = new URL(data);
dispatch(pasteLinkCompose({ url }));
} catch {
return;
}
}
};
const mapStateToProps = state => ({
text: state.getIn(['compose', 'text']),
suggestions: state.getIn(['compose', 'suggestions']),
@@ -85,20 +102,11 @@ const mapDispatchToProps = (dispatch, props) => ({
},
onPaste (e) {
if (e.clipboardData && e.clipboardData.files.length === 1) {
dispatch(uploadCompose(e.clipboardData.files));
e.preventDefault();
} else if (e.clipboardData && e.clipboardData.files.length === 0) {
const data = e.clipboardData.getData('text/plain');
if (!data.match(urlLikeRegex)) return;
processPasteOrDrop(e.clipboardData, e, dispatch);
},
try {
const url = new URL(data);
dispatch(pasteLinkCompose({ url }));
} catch {
return;
}
}
onDrop (e) {
processPasteOrDrop(e.dataTransfer, e, dispatch);
},
onPickEmoji (position, data, needsSpace) {

View File

@@ -157,6 +157,8 @@
"bundle_modal_error.close": "Tanca",
"bundle_modal_error.message": "S'ha produït un error en carregar aquesta pantalla.",
"bundle_modal_error.retry": "Torna-ho a provar",
"carousel.current": "<sr>Diapositiva</sr> {current, number} / {max, number}",
"carousel.slide": "Diapositiva {current, number} de {max, number}",
"closed_registrations.other_server_instructions": "Com que Mastodon és descentralitzat, pots crear un compte en un altre servidor i continuar interactuant amb aquest.",
"closed_registrations_modal.description": "No es pot crear un compte a {domain} ara mateix, però tingues en compte que no necessites específicament un compte a {domain} per a usar Mastodon.",
"closed_registrations_modal.find_another_server": "Troba un altre servidor",
@@ -173,6 +175,8 @@
"column.edit_list": "Edita la llista",
"column.favourites": "Favorits",
"column.firehose": "Tuts en directe",
"column.firehose_local": "Canal en directe per a aquest servidor",
"column.firehose_singular": "Canal en directe",
"column.follow_requests": "Peticions de seguir-te",
"column.home": "Inici",
"column.list_members": "Gestiona els membres de la llista",
@@ -245,8 +249,13 @@
"confirmations.missing_alt_text.secondary": "Publica-la igualment",
"confirmations.missing_alt_text.title": "Hi voleu afegir text alternatiu?",
"confirmations.mute.confirm": "Silencia",
"confirmations.private_quote_notify.cancel": "Torna a l'edició",
"confirmations.private_quote_notify.message": "La persona que citeu i altres mencionades rebran una notificació i podran veure la vostra publicació, encara que no us segueixen.",
"confirmations.private_quote_notify.title": "Voleu compartir amb seguidors i usuaris mencionats?",
"confirmations.quiet_post_quote_info.dismiss": "No m'ho tornis a recordar",
"confirmations.quiet_post_quote_info.got_it": "Entesos",
"confirmations.quiet_post_quote_info.message": "Quan citeu una publicació pública en mode silenciós, la vostra publicació s'amagarà de les línies de temps de tendències.",
"confirmations.quiet_post_quote_info.title": "Citació d'una publicació pública en mode silenciós",
"confirmations.redraft.confirm": "Esborra i reescriu",
"confirmations.redraft.message": "Segur que vols eliminar aquest tut i tornar a escriure'l? Es perdran tots els impulsos i els favorits, i les respostes al tut original quedaran aïllades.",
"confirmations.redraft.title": "Esborrar i reescriure la publicació?",
@@ -332,6 +341,7 @@
"empty_column.bookmarked_statuses": "Encara no has marcat cap tut. Quan en marquis un, apareixerà aquí.",
"empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per posar-ho tot en marxa!",
"empty_column.direct": "Encara no tens mencions privades. Quan n'enviïs o en rebis una, et sortirà aquí.",
"empty_column.disabled_feed": "Aquest canal ha estat desactivat per l'administració del vostre servidor.",
"empty_column.domain_blocks": "Encara no hi ha dominis blocats.",
"empty_column.explore_statuses": "No hi ha res en tendència ara mateix. Revisa-ho més tard!",
"empty_column.favourited_statuses": "Encara no has afavorit cap tut. Quan ho facis, apareixerà aquí.",
@@ -357,6 +367,7 @@
"explore.trending_statuses": "Tuts",
"explore.trending_tags": "Etiquetes",
"featured_carousel.header": "{count, plural, one {Publicació fixada} other {Publicacions fixades}}",
"featured_carousel.slide": "Publicació {current, number} de {max, number}",
"filter_modal.added.context_mismatch_explanation": "Aquesta categoria de filtre no s'aplica al context en què has accedit a aquest tut. Si també vols que el tut es filtri en aquest context, hauràs d'editar el filtre.",
"filter_modal.added.context_mismatch_title": "El context no coincideix!",
"filter_modal.added.expired_explanation": "La categoria d'aquest filtre ha caducat, necessitaràs canviar la seva data de caducitat per a aplicar-la.",
@@ -456,6 +467,7 @@
"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",
"info_button.what_is_alt_text": "<h1>Què és el text alternatiu?</h1> <p>El text alternatiu proporciona descripcions d'imatges per a persones amb discapacitat visual, connexions de poca amplada de banda o aquelles que busquen un context addicional.</p> <p>Podeu millorar l'accessibilitat i la comprensió per a tothom escrivint un text alternatiu clar, concís i objectiu.</p> <ul> <li>Descriviu els elements importants</li> <li>Utilitzeu frases senzilles</li> <li>Resumiu el text en imatges</li> <li>Eviteu la informació redundant</li> <li>Centreu-vos en les tendències i els aspectes clau dels elements visuals complexos (com ara diagrames o mapes)</li> </ul>",
"interaction_modal.action": "Per a interactuar amb la publicació de {name} cal que inicieu la sessió en el servidor que feu servir.",
"interaction_modal.go": "Endavant",
"interaction_modal.no_account_yet": "Encara no teniu cap compte?",
@@ -1000,10 +1012,14 @@
"video.volume_down": "Abaixa el volum",
"video.volume_up": "Apuja el volum",
"visibility_modal.button_title": "Establiu la visibilitat",
"visibility_modal.direct_quote_warning.text": "Si deseu la configuració actual, la cita incrustada es convertirà en un enllaç.",
"visibility_modal.direct_quote_warning.title": "Les cites no es poden incrustar a les mencions privades",
"visibility_modal.header": "Visibilitat i interacció",
"visibility_modal.helper.direct_quoting": "No es poden citar mencions privades fetes a Mastondon.",
"visibility_modal.helper.privacy_editing": "La visibilitat no es pot canviar després de publicar una publicació.",
"visibility_modal.helper.privacy_private_self_quote": "Les autocites de publicacions privades no es poden fer públiques.",
"visibility_modal.helper.private_quoting": "No es poden citar publicacions fetes a Mastodon només per a seguidors.",
"visibility_modal.helper.unlisted_quoting": "Quan la gent et citi les seves publicacions estaran amagades de les línies de temps de tendències.",
"visibility_modal.helper.unlisted_quoting": "Quan la gent us citi, les seves publicacions quedaran amagades de les línies de temps de tendències.",
"visibility_modal.instructions": "Controleu qui pot interactuar amb aquesta publicació. També podeu aplicar la configuració a totes les publicacions futures navegant a <link>Preferències > Valors per defecte de publicació</link>.",
"visibility_modal.privacy_label": "Visibilitat",
"visibility_modal.quote_followers": "Només seguidors",

View File

@@ -110,7 +110,7 @@
"alt_text_modal.cancel": "Abbrechen",
"alt_text_modal.change_thumbnail": "Vorschaubild ändern",
"alt_text_modal.describe_for_people_with_hearing_impairments": "Beschreibe den Inhalt für Menschen mit Schwerhörigkeit …",
"alt_text_modal.describe_for_people_with_visual_impairments": "Beschreibe den Inhalt für Menschen mit Sehschwäche …",
"alt_text_modal.describe_for_people_with_visual_impairments": "Beschreibe den Inhalt für Menschen, die blind oder sehbehindert sind …",
"alt_text_modal.done": "Fertig",
"announcement.announcement": "Ankündigung",
"annual_report.summary.archetype.booster": "Trendjäger*in",
@@ -245,7 +245,7 @@
"confirmations.logout.message": "Möchtest du dich wirklich abmelden?",
"confirmations.logout.title": "Abmelden?",
"confirmations.missing_alt_text.confirm": "Bildbeschreibung hinzufügen",
"confirmations.missing_alt_text.message": "Dein Beitrag enthält Medien ohne Bildbeschreibung. Mit Alt-Texten erreichst du auch Menschen mit einer Sehschwäche.",
"confirmations.missing_alt_text.message": "Dein Beitrag enthält Medien ohne Bildbeschreibung. Mit ALT-Texten erreichst Du auch Menschen, die blind oder sehbehindert sind.",
"confirmations.missing_alt_text.secondary": "Trotzdem veröffentlichen",
"confirmations.missing_alt_text.title": "Bildbeschreibung hinzufügen?",
"confirmations.mute.confirm": "Stummschalten",
@@ -470,7 +470,7 @@
"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>",
"info_button.what_is_alt_text": "<h1>Was ist Alt-Text?</h1> <p>Der Alt-Text bietet Bildbeschreibungen für blinde und sehbehinderte Menschen, aber auch für alle mit einer schlechten Internetverbindung und wer einen zusätzlichen Kontext möchten.</p> <p>Du kannst die Barrierefreiheit und Verständlichkeit für alle verbessern, indem du eine klare, genaue und objektive Bildbeschreibung erstellst.</p> <ul> <li>Erfasse wichtige Elemente</li> <li>Fasse Texte bildlich zusammen</li> <li>Verwende einen korrekten Satzbau</li> <li>Vermeide unwichtige und überflüssige Informationen</li> <li>Konzentriere dich bei komplexen Darstellungen (z. B. bei Diagrammen oder Karten) auf Veränderungen und Schlüsselwörter</li> </ul>",
"interaction_modal.action": "Melde dich auf deinem Mastodon-Server an, damit du mit dem Beitrag von {name} interagieren kannst.",
"interaction_modal.go": "Los",
"interaction_modal.no_account_yet": "Du hast noch kein Konto?",

View File

@@ -1029,7 +1029,7 @@
"visibility_modal.helper.unlisted_quoting": "Cuando otras cuentas te citen, sus publicaciones también se ocultarán de las líneas temporales de tendencias.",
"visibility_modal.instructions": "Controlá quién puede interactuar con este mensaje. También podés aplicar los ajustes para todos los mensajes futuros accediendo a <link>«Configuración» > «Configuración predeterminada de mensajes»</link>.",
"visibility_modal.privacy_label": "Visibilidad",
"visibility_modal.quote_followers": "Solo para seguidores",
"visibility_modal.quote_followers": "Solo seguidores",
"visibility_modal.quote_label": "Quién puede citar",
"visibility_modal.quote_nobody": "Solo yo",
"visibility_modal.quote_public": "Cualquier cuenta",

View File

@@ -903,6 +903,7 @@
"status.edited_x_times": "Curtha in eagar {count, plural, one {{count} uair amháin} two {{count} uair} few {{count} uair} many {{count} uair} other {{count} uair}}",
"status.embed": "Faigh cód leabú",
"status.favourite": "Is fearr leat",
"status.favourites_count": "{count, plural,\n one {{counter} cheanán}\n two {{counter} cheanáin}\n few {{counter} ceanáin}\n many {{counter} ceanán}\n other {{counter} ceanáin}\n}",
"status.filter": "Déan scagadh ar an bpostáil seo",
"status.history.created": "Chruthaigh {name} {date}",
"status.history.edited": "Curtha in eagar ag {name} in {date}",
@@ -937,12 +938,14 @@
"status.quotes.empty": "Níl an post seo luaite ag aon duine go fóill. Nuair a dhéanann duine é, taispeánfar anseo é.",
"status.quotes.local_other_disclaimer": "Ní thaispeánfar sleachta ar dhiúltaigh an t-údar dóibh.",
"status.quotes.remote_other_disclaimer": "Níl ráthaíocht ann go dtaispeánfar anseo ach sleachta ó {domain}. Ní thaispeánfar sleachta ar dhiúltaigh an t-údar dóibh.",
"status.quotes_count": "{count, plural,\n one {{counter} athfhriotal}\n two {{counter} athfhriotail}\n few {{counter} athfhriotail}\n many {{counter} athfhriotal}\n other {{counter} athfhriotail}\n}",
"status.read_more": "Léan a thuilleadh",
"status.reblog": "Treisiú",
"status.reblog_or_quote": "Borradh nó luachan",
"status.reblog_private": "Roinn arís le do leanúna",
"status.reblogged_by": "Mhol {name}",
"status.reblogs.empty": "Níor mhol éinne an phostáil seo fós. Nuair a mholfaidh duine éigin í, taispeánfar anseo é sin.",
"status.reblogs_count": "{count, plural,\n one {{counter} athfhriotal}\n two {{counter} athfhriotail}\n few {{counter} athfhriotail}\n many {{counter} athfhriotal}\n other {{counter} athfhriotail}\n}",
"status.redraft": "Scrios ⁊ athdhréachtaigh",
"status.remove_bookmark": "Bain leabharmharc",
"status.remove_favourite": "Bain ó cheanáin",

View File

@@ -903,6 +903,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
"status.embed": "O código a incluír",
"status.favourite": "Favorecer",
"status.favourites_count": "{count, plural, one {{counter} favorecemento} other {{counter} favorecementos}}",
"status.filter": "Filtrar esta publicación",
"status.history.created": "{name} creouno o {date}",
"status.history.edited": "{name} editouno o {date}",
@@ -937,12 +938,14 @@
"status.quotes.empty": "Aínda ninguén citou esta publicación. Cando alguén o faga aparecerá aquí.",
"status.quotes.local_other_disclaimer": "Non se mostrarán as citas rexeitadas pola autora.",
"status.quotes.remote_other_disclaimer": "Só se garante que se mostren as citas do dominio {domain}. Non se mostrarán as citas rexeitadas pola persoa autora.",
"status.quotes_count": "{count, plural, one {{counter} cita} other {{counter} citas}}",
"status.read_more": "Ler máis",
"status.reblog": "Promover",
"status.reblog_or_quote": "Promover ou citar",
"status.reblog_private": "Volver a compartir coas túas seguidoras",
"status.reblogged_by": "{name} promoveu",
"status.reblogs.empty": "Aínda ninguén promoveu esta publicación. Cando alguén o faga, amosarase aquí.",
"status.reblogs_count": "{count, plural, one {{counter} promoción} other {{counter} promocións}}",
"status.redraft": "Eliminar e reescribir",
"status.remove_bookmark": "Eliminar marcador",
"status.remove_favourite": "Retirar das favoritas",

View File

@@ -880,6 +880,7 @@
"status.quote_error.filtered": "あなたのフィルター設定によって非表示になっています",
"status.quote_error.pending_approval": "承認待ちの投稿",
"status.quote_noun": "引用",
"status.quote_policy_change": "引用できるユーザーの変更",
"status.quote_post_author": "{name} の投稿を引用",
"status.quote_private": "非公開の投稿は引用できません",
"status.quotes.local_other_disclaimer": "投稿者が拒否した引用は表示されません。",

View File

@@ -252,6 +252,7 @@
"confirmations.private_quote_notify.cancel": "편집으로 돌아가기",
"confirmations.private_quote_notify.confirm": "게시",
"confirmations.private_quote_notify.do_not_show_again": "이 메시지를 다시 표시하지 않음",
"confirmations.private_quote_notify.message": "인용하려는 사람과 멘션된 사람들은 나를 팔로우하지 않더라도 게시물에 대한 알림을 받으며 내용을 볼 수 있습니다.",
"confirmations.quiet_post_quote_info.dismiss": "다시 보지 않기",
"confirmations.quiet_post_quote_info.got_it": "알겠습니다",
"confirmations.quiet_post_quote_info.message": "조용한 공개 게시물을 인용하면 그 게시물은 유행 타임라인에서 나타나지 않을 것입니다.",

View File

@@ -639,7 +639,7 @@
"privacy.private.long": "Tikai Tavi sekotāji",
"privacy.private.short": "Sekotāji",
"privacy.public.long": "Jebkurš Mastodon un ārpus tā",
"privacy.public.short": "Redzams visiem",
"privacy.public.short": "Publisks",
"privacy.quote.anyone": "{visibility}, jebkurš var citēt",
"privacy.quote.disabled": "{visibility}, aizliegta citēšana",
"privacy.quote.limited": "{visibility}, ierobežota citēšana",

View File

@@ -4,7 +4,7 @@
"about.default_locale": "Padrão",
"about.disclaimer": "Mastodon é um software de código aberto e livre, e uma marca registrada de Mastodon gGmbH.",
"about.domain_blocks.no_reason_available": "Razão não disponível",
"about.domain_blocks.preamble": "O Mastodon geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outro servidor no fediverso. Estas são as exceções deste servidor em específico.",
"about.domain_blocks.preamble": "O \"Mastodon\" geralmente permite que você veja o conteúdo e interaja com usuários de qualquer outro servidor no \"fediverso\". Estas são as exceções deste servidor em específico.",
"about.domain_blocks.silenced.explanation": "Você geralmente não verá perfis e conteúdo deste servidor, a menos que você o procure explicitamente ou opte por seguir.",
"about.domain_blocks.silenced.title": "Limitado",
"about.domain_blocks.suspended.explanation": "Nenhum dado desse servidor será processado, armazenado ou trocado, impossibilitando qualquer interação ou comunicação com os usuários deste servidor.",
@@ -36,7 +36,7 @@
"account.familiar_followers_two": "Seguido por {name1} e {name2}",
"account.featured": "Em destaque",
"account.featured.accounts": "Perfis",
"account.featured.hashtags": "Hashtags",
"account.featured.hashtags": "\"Hashtags\"",
"account.featured_tags.last_status_at": "Última publicação em {date}",
"account.featured_tags.last_status_never": "Sem publicações",
"account.follow": "Seguir",
@@ -49,7 +49,7 @@
"account.followers": "Seguidores",
"account.followers.empty": "Nada aqui.",
"account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}",
"account.followers_you_know_counter": "{counter} que você sabe",
"account.followers_you_know_counter": "{counter} que você conhece",
"account.following": "Seguindo",
"account.following_counter": "{count, plural, one {{counter} seguindo} other {{counter} seguindo}}",
"account.follows.empty": "Nada aqui.",
@@ -903,6 +903,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} hora} other {{count} vezes}}",
"status.embed": "Obter código de incorporação",
"status.favourite": "Favorita",
"status.favourites_count": "{count, plural, one {{counter} favorito} other {{counter} favoritos}}",
"status.filter": "Filtrar esta publicação",
"status.history.created": "{name} criou {date}",
"status.history.edited": "{name} editou {date}",
@@ -937,12 +938,14 @@
"status.quotes.empty": "Ninguém citou essa publicação até agora. Quando alguém citar aparecerá aqui.",
"status.quotes.local_other_disclaimer": "Citações rejeitadas pelo autor não serão exibidas.",
"status.quotes.remote_other_disclaimer": "Apenas citações do {domain} têm a garantia de serem exibidas aqui. Citações rejeitadas pelo autor não serão exibidas.",
"status.quotes_count": "{count, plural, one {{counter} mencionar} other {{counter} menções}}",
"status.read_more": "Ler mais",
"status.reblog": "Dar boost",
"status.reblog_or_quote": "Acelerar ou citar",
"status.reblog_private": "Compartilhar novamente com seus seguidores",
"status.reblogged_by": "{name} deu boost",
"status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.",
"status.reblogs_count": "{count, plural, one {{counter} impulsionar} other {{counter} impulsionados}}",
"status.redraft": "Excluir e rascunhar",
"status.remove_bookmark": "Remover do Salvos",
"status.remove_favourite": "Remover dos favoritos",