Merge commit '8a498f4e65ecf27cc2c992d02b47f890dafef20b' into glitch-soc/merge-upstream

This commit is contained in:
Claire 2024-05-01 14:50:16 +02:00
commit 3f61981f5d
80 changed files with 630 additions and 463 deletions

2
.nvmrc
View file

@ -1 +1 @@
20.11
20.12

View file

@ -1,152 +0,0 @@
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import api from '../api';
import { compareId } from '../compare_id';
export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
export const MARKERS_FETCH_FAIL = 'MARKERS_FETCH_FAIL';
export const MARKERS_SUBMIT_SUCCESS = 'MARKERS_SUBMIT_SUCCESS';
export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
const accessToken = getState().getIn(['meta', 'access_token'], '');
const params = _buildParams(getState());
if (Object.keys(params).length === 0 || accessToken === '') {
return;
}
// The Fetch API allows us to perform requests that will be carried out
// after the page closes. But that only works if the `keepalive` attribute
// is supported.
if (window.fetch && 'keepalive' in new Request('')) {
fetch('/api/v1/markers', {
keepalive: true,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify(params),
});
return;
} else if (navigator && navigator.sendBeacon) {
// Failing that, we can use sendBeacon, but we have to encode the data as
// FormData for DoorKeeper to recognize the token.
const formData = new FormData();
formData.append('bearer_token', accessToken);
for (const [id, value] of Object.entries(params)) {
formData.append(`${id}[last_read_id]`, value.last_read_id);
}
if (navigator.sendBeacon('/api/v1/markers', formData)) {
return;
}
}
// If neither Fetch nor sendBeacon worked, try to perform a synchronous
// request.
try {
const client = new XMLHttpRequest();
client.open('POST', '/api/v1/markers', false);
client.setRequestHeader('Content-Type', 'application/json');
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
client.send(JSON.stringify(params));
} catch (e) {
// Do not make the BeforeUnload handler error out
}
};
const _buildParams = (state) => {
const params = {};
const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null);
const lastNotificationId = state.getIn(['notifications', 'lastReadId']);
if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {
params.home = {
last_read_id: lastHomeId,
};
}
if (lastNotificationId && compareId(lastNotificationId, state.getIn(['markers', 'notifications'])) > 0) {
params.notifications = {
last_read_id: lastNotificationId,
};
}
return params;
};
const debouncedSubmitMarkers = debounce((dispatch, getState) => {
const accessToken = getState().getIn(['meta', 'access_token'], '');
const params = _buildParams(getState());
if (Object.keys(params).length === 0 || accessToken === '') {
return;
}
api(getState).post('/api/v1/markers', params).then(() => {
dispatch(submitMarkersSuccess(params));
}).catch(() => {});
}, 300000, { leading: true, trailing: true });
export function submitMarkersSuccess({ home, notifications }) {
return {
type: MARKERS_SUBMIT_SUCCESS,
home: (home || {}).last_read_id,
notifications: (notifications || {}).last_read_id,
};
}
export function submitMarkers(params = {}) {
const result = (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState);
if (params.immediate === true) {
debouncedSubmitMarkers.flush();
}
return result;
}
export const fetchMarkers = () => (dispatch, getState) => {
const params = { timeline: ['notifications'] };
dispatch(fetchMarkersRequest());
api(getState).get('/api/v1/markers', { params }).then(response => {
dispatch(fetchMarkersSuccess(response.data));
}).catch(error => {
dispatch(fetchMarkersFail(error));
});
};
export function fetchMarkersRequest() {
return {
type: MARKERS_FETCH_REQUEST,
skipLoading: true,
};
}
export function fetchMarkersSuccess(markers) {
return {
type: MARKERS_FETCH_SUCCESS,
markers,
skipLoading: true,
};
}
export function fetchMarkersFail(error) {
return {
type: MARKERS_FETCH_FAIL,
error,
skipLoading: true,
skipAlert: true,
};
}

View file

@ -0,0 +1,165 @@
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import type { MarkerJSON } from 'mastodon/api_types/markers';
import type { RootState } from 'mastodon/store';
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
import api, { authorizationTokenFromState } from '../api';
import { compareId } from '../compare_id';
export const synchronouslySubmitMarkers = createAppAsyncThunk(
'markers/submit',
async (_args, { getState }) => {
const accessToken = authorizationTokenFromState(getState);
const params = buildPostMarkersParams(getState());
if (Object.keys(params).length === 0 || !accessToken) {
return;
}
// The Fetch API allows us to perform requests that will be carried out
// after the page closes. But that only works if the `keepalive` attribute
// is supported.
if ('fetch' in window && 'keepalive' in new Request('')) {
await fetch('/api/v1/markers', {
keepalive: true,
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(params),
});
return;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
} else if ('navigator' && 'sendBeacon' in navigator) {
// Failing that, we can use sendBeacon, but we have to encode the data as
// FormData for DoorKeeper to recognize the token.
const formData = new FormData();
formData.append('bearer_token', accessToken);
for (const [id, value] of Object.entries(params)) {
if (value.last_read_id)
formData.append(`${id}[last_read_id]`, value.last_read_id);
}
if (navigator.sendBeacon('/api/v1/markers', formData)) {
return;
}
}
// If neither Fetch nor sendBeacon worked, try to perform a synchronous
// request.
try {
const client = new XMLHttpRequest();
client.open('POST', '/api/v1/markers', false);
client.setRequestHeader('Content-Type', 'application/json');
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
client.send(JSON.stringify(params));
} catch (e) {
// Do not make the BeforeUnload handler error out
}
},
);
interface MarkerParam {
last_read_id?: string;
}
function getLastHomeId(state: RootState): string | undefined {
/* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
return (
state
// @ts-expect-error state.timelines is not yet typed
.getIn(['timelines', 'home', 'items'], ImmutableList())
// @ts-expect-error state.timelines is not yet typed
.find((item) => item !== null)
);
}
function getLastNotificationId(state: RootState): string | undefined {
// @ts-expect-error state.notifications is not yet typed
return state.getIn(['notifications', 'lastReadId']);
}
const buildPostMarkersParams = (state: RootState) => {
const params = {} as { home?: MarkerParam; notifications?: MarkerParam };
const lastHomeId = getLastHomeId(state);
const lastNotificationId = getLastNotificationId(state);
if (lastHomeId && compareId(lastHomeId, state.markers.home) > 0) {
params.home = {
last_read_id: lastHomeId,
};
}
if (
lastNotificationId &&
compareId(lastNotificationId, state.markers.notifications) > 0
) {
params.notifications = {
last_read_id: lastNotificationId,
};
}
return params;
};
export const submitMarkersAction = createAppAsyncThunk<{
home: string | undefined;
notifications: string | undefined;
}>('markers/submitAction', async (_args, { getState }) => {
const accessToken = authorizationTokenFromState(getState);
const params = buildPostMarkersParams(getState());
if (Object.keys(params).length === 0 || accessToken === '') {
return { home: undefined, notifications: undefined };
}
await api(getState).post<MarkerJSON>('/api/v1/markers', params);
return {
home: params.home?.last_read_id,
notifications: params.notifications?.last_read_id,
};
});
const debouncedSubmitMarkers = debounce(
(dispatch) => {
dispatch(submitMarkersAction());
},
300000,
{
leading: true,
trailing: true,
},
);
export const submitMarkers = createAppAsyncThunk(
'markers/submit',
(params: { immediate?: boolean }, { dispatch }) => {
debouncedSubmitMarkers(dispatch);
if (params.immediate) {
debouncedSubmitMarkers.flush();
}
},
);
export const fetchMarkers = createAppAsyncThunk(
'markers/fetch',
async (_args, { getState }) => {
const response = await api(getState).get<Record<string, MarkerJSON>>(
`/api/v1/markers`,
{ params: { timeline: ['notifications'] } },
);
return { markers: response.data };
},
);

View file

@ -1,46 +0,0 @@
// @ts-check
export const PICTURE_IN_PICTURE_DEPLOY = 'PICTURE_IN_PICTURE_DEPLOY';
export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
/**
* @typedef MediaProps
* @property {string} src
* @property {boolean} muted
* @property {number} volume
* @property {number} currentTime
* @property {string} poster
* @property {string} backgroundColor
* @property {string} foregroundColor
* @property {string} accentColor
*/
/**
* @param {string} statusId
* @param {string} accountId
* @param {string} playerType
* @param {MediaProps} props
* @returns {object}
*/
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
// @ts-expect-error
return (dispatch, getState) => {
// Do not open a player for a toot that does not exist
if (getState().hasIn(['statuses', statusId])) {
dispatch({
type: PICTURE_IN_PICTURE_DEPLOY,
statusId,
accountId,
playerType,
props,
});
}
};
};
/*
* @return {object}
*/
export const removePictureInPicture = () => ({
type: PICTURE_IN_PICTURE_REMOVE,
});

View file

@ -0,0 +1,31 @@
import { createAction } from '@reduxjs/toolkit';
import type { PIPMediaProps } from 'mastodon/reducers/picture_in_picture';
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
interface DeployParams {
statusId: string;
accountId: string;
playerType: 'audio' | 'video';
props: PIPMediaProps;
}
export const removePictureInPicture = createAction('pip/remove');
export const deployPictureInPictureAction =
createAction<DeployParams>('pip/deploy');
export const deployPictureInPicture = createAppAsyncThunk(
'pip/deploy',
(args: DeployParams, { dispatch, getState }) => {
const { statusId } = args;
// Do not open a player for a toot that does not exist
// @ts-expect-error state.statuses is not yet typed
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
if (getState().hasIn(['statuses', statusId])) {
dispatch(deployPictureInPictureAction(args));
}
},
);

View file

@ -29,9 +29,14 @@ const setCSRFHeader = () => {
void ready(setCSRFHeader);
export const authorizationTokenFromState = (getState?: GetState) => {
return (
getState && (getState().meta.get('access_token', '') as string | false)
);
};
const authorizationHeaderFromState = (getState?: GetState) => {
const accessToken =
getState && (getState().meta.get('access_token', '') as string);
const accessToken = authorizationTokenFromState(getState);
if (!accessToken) {
return {};

View file

@ -0,0 +1,7 @@
// See app/serializers/rest/account_serializer.rb
export interface MarkerJSON {
last_read_id: string;
version: string;
updated_at: string;
}

View file

@ -1,26 +1,26 @@
import type { PropsWithChildren } from 'react';
import { useCallback } from 'react';
import classNames from 'classnames';
interface BaseProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
interface BaseProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
block?: boolean;
secondary?: boolean;
text?: JSX.Element;
}
interface PropsWithChildren extends BaseProps {
text?: never;
interface PropsChildren extends PropsWithChildren<BaseProps> {
text?: undefined;
}
interface PropsWithText extends BaseProps {
text: JSX.Element;
children: never;
text: JSX.Element | string;
children?: undefined;
}
type Props = PropsWithText | PropsWithChildren;
type Props = PropsWithText | PropsChildren;
export const Button: React.FC<Props> = ({
text,
type = 'button',
onClick,
disabled,
@ -28,6 +28,7 @@ export const Button: React.FC<Props> = ({
secondary,
className,
title,
text,
children,
...props
}) => {

View file

@ -191,7 +191,7 @@ const timeRemainingString = (
interface Props {
intl: IntlShape;
timestamp: string;
year: number;
year?: number;
futureDate?: boolean;
short?: boolean;
}
@ -203,11 +203,6 @@ class RelativeTimestamp extends Component<Props, States> {
now: Date.now(),
};
static defaultProps = {
year: new Date().getFullYear(),
short: true,
};
_timer: number | undefined;
shouldComponentUpdate(nextProps: Props, nextState: States) {
@ -257,7 +252,13 @@ class RelativeTimestamp extends Component<Props, States> {
}
render() {
const { timestamp, intl, year, futureDate, short } = this.props;
const {
timestamp,
intl,
futureDate,
year = new Date().getFullYear(),
short = true,
} = this.props;
const timeGiven = timestamp.includes('T');
const date = new Date(timestamp);

View file

@ -81,7 +81,7 @@ class StatusActionBar extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.map,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,

View file

@ -262,7 +262,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
},
deployPictureInPicture (status, type, mediaProps) {
dispatch(deployPictureInPicture(status.get('id'), status.getIn(['account', 'id']), type, mediaProps));
dispatch(deployPictureInPicture({statusId: status.get('id'), accountId: status.getIn(['account', 'id']), playerType: type, props: mediaProps}));
},
onInteractionModal (type, status) {

View file

@ -82,7 +82,7 @@ class GettingStarted extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.map,
myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool,
fetchFollowRequests: PropTypes.func.isRequired,
unreadFollowRequests: PropTypes.number,

View file

@ -210,4 +210,4 @@ class Footer extends ImmutablePureComponent {
}
export default withRouter(connect(makeMapStateToProps)(injectIntl(Footer)));
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));

View file

@ -1,51 +0,0 @@
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { Avatar } from 'mastodon/components/avatar';
import { DisplayName } from 'mastodon/components/display_name';
import { IconButton } from 'mastodon/components/icon_button';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
});
const mapStateToProps = (state, { accountId }) => ({
account: state.getIn(['accounts', accountId]),
});
class Header extends ImmutablePureComponent {
static propTypes = {
accountId: PropTypes.string.isRequired,
statusId: PropTypes.string.isRequired,
account: ImmutablePropTypes.record.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
render () {
const { account, statusId, onClose, intl } = this.props;
return (
<div className='picture-in-picture__header'>
<Link to={`/@${account.get('acct')}/${statusId}`} className='picture-in-picture__header__account'>
<Avatar account={account} size={36} />
<DisplayName account={account} />
</Link>
<IconButton icon='times' iconComponent={CloseIcon} onClick={onClose} title={intl.formatMessage(messages.close)} />
</div>
);
}
}
export default connect(mapStateToProps)(injectIntl(Header));

View file

@ -0,0 +1,46 @@
import { defineMessages, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { Avatar } from 'mastodon/components/avatar';
import { DisplayName } from 'mastodon/components/display_name';
import { IconButton } from 'mastodon/components/icon_button';
import { useAppSelector } from 'mastodon/store';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
});
interface Props {
accountId: string;
statusId: string;
onClose: () => void;
}
export const Header: React.FC<Props> = ({ accountId, statusId, onClose }) => {
const account = useAppSelector((state) => state.accounts.get(accountId));
const intl = useIntl();
if (!account) return null;
return (
<div className='picture-in-picture__header'>
<Link
to={`/@${account.get('acct')}/${statusId}`}
className='picture-in-picture__header__account'
>
<Avatar account={account} size={36} />
<DisplayName account={account} />
</Link>
<IconButton
icon='times'
iconComponent={CloseIcon}
onClick={onClose}
title={intl.formatMessage(messages.close)}
/>
</div>
);
};

View file

@ -1,89 +0,0 @@
import PropTypes from 'prop-types';
import { Component } from 'react';
import { connect } from 'react-redux';
import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
import Audio from 'mastodon/features/audio';
import Video from 'mastodon/features/video';
import Footer from './components/footer';
import Header from './components/header';
const mapStateToProps = state => ({
...state.get('picture_in_picture'),
});
class PictureInPicture extends Component {
static propTypes = {
statusId: PropTypes.string,
accountId: PropTypes.string,
type: PropTypes.string,
src: PropTypes.string,
muted: PropTypes.bool,
volume: PropTypes.number,
currentTime: PropTypes.number,
poster: PropTypes.string,
backgroundColor: PropTypes.string,
foregroundColor: PropTypes.string,
accentColor: PropTypes.string,
dispatch: PropTypes.func.isRequired,
};
handleClose = () => {
const { dispatch } = this.props;
dispatch(removePictureInPicture());
};
render () {
const { type, src, currentTime, accountId, statusId } = this.props;
if (!currentTime) {
return null;
}
let player;
if (type === 'video') {
player = (
<Video
src={src}
currentTime={this.props.currentTime}
volume={this.props.volume}
muted={this.props.muted}
autoPlay
inline
alwaysVisible
/>
);
} else if (type === 'audio') {
player = (
<Audio
src={src}
currentTime={this.props.currentTime}
volume={this.props.volume}
muted={this.props.muted}
poster={this.props.poster}
backgroundColor={this.props.backgroundColor}
foregroundColor={this.props.foregroundColor}
accentColor={this.props.accentColor}
autoPlay
/>
);
}
return (
<div className='picture-in-picture'>
<Header accountId={accountId} statusId={statusId} onClose={this.handleClose} />
{player}
<Footer statusId={statusId} />
</div>
);
}
}
export default connect(mapStateToProps)(PictureInPicture);

View file

@ -0,0 +1,79 @@
import { useCallback } from 'react';
import { removePictureInPicture } from 'mastodon/actions/picture_in_picture';
import Audio from 'mastodon/features/audio';
import Video from 'mastodon/features/video';
import { useAppDispatch, useAppSelector } from 'mastodon/store/typed_functions';
import Footer from './components/footer';
import { Header } from './components/header';
export const PictureInPicture: React.FC = () => {
const dispatch = useAppDispatch();
const handleClose = useCallback(() => {
dispatch(removePictureInPicture());
}, [dispatch]);
const pipState = useAppSelector((s) => s.picture_in_picture);
if (pipState.type === null) {
return null;
}
const {
type,
src,
currentTime,
accountId,
statusId,
volume,
muted,
poster,
backgroundColor,
foregroundColor,
accentColor,
} = pipState;
let player;
switch (type) {
case 'video':
player = (
<Video
src={src}
currentTime={currentTime}
volume={volume}
muted={muted}
autoPlay
inline
alwaysVisible
/>
);
break;
case 'audio':
player = (
<Audio
src={src}
currentTime={currentTime}
volume={volume}
muted={muted}
poster={poster}
backgroundColor={backgroundColor}
foregroundColor={foregroundColor}
accentColor={accentColor}
autoPlay
/>
);
}
return (
<div className='picture-in-picture'>
<Header accountId={accountId} statusId={statusId} onClose={handleClose} />
{player}
<Footer statusId={statusId} />
</div>
);
};

View file

@ -74,7 +74,7 @@ class ActionBar extends PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.map,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,

View file

@ -14,7 +14,7 @@ import { HotKeys } from 'react-hotkeys';
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import PictureInPicture from 'mastodon/features/picture_in_picture';
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';

View file

@ -471,6 +471,8 @@
"notification.own_poll": "Анкетата ви приключи",
"notification.poll": "Анкета, в която гласувахте, приключи",
"notification.reblog": "{name} подсили ваша публикация",
"notification.relationships_severance_event": "Изгуби се връзката с {name}",
"notification.relationships_severance_event.learn_more": "Научете повече",
"notification.status": "{name} току-що публикува",
"notification.update": "{name} промени публикация",
"notification_requests.accept": "Приемам",

View file

@ -472,7 +472,7 @@
"notification.own_poll": "La teva enquesta ha finalitzat",
"notification.poll": "Ha finalitzat una enquesta en què has votat",
"notification.reblog": "{name} t'ha impulsat",
"notification.relationships_severance_event": "Connexions perdudes amb {name}",
"notification.relationships_severance_event": "S'han perdut les connexions amb {name}",
"notification.relationships_severance_event.account_suspension": "Un administrador de {from} ha suspès {target}; això vol dir que ja no en podreu rebre actualitzacions o interactuar-hi.",
"notification.relationships_severance_event.domain_block": "Un administrador de {from} ha blocat {target}, incloent-hi {followersCount} dels vostres seguidors i {followingCount, plural, one {# compte} other {# comptes}} que seguiu.",
"notification.relationships_severance_event.learn_more": "Per a saber-ne més",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "Filtrar esta publicación",
"filter_modal.title.status": "Filtrar una publicación",
"filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer",
"filtered_notifications_banner.private_mentions": "{count, plural, one {mención privada} other {menciones privadas}}",
"filtered_notifications_banner.title": "Notificaciones filtradas",
"firehose.all": "Todas",
"firehose.local": "Este servidor",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "Filtrar esta publicación",
"filter_modal.title.status": "Filtrar una publicación",
"filtered_notifications_banner.pending_requests": "Notificaciones de {count, plural, =0 {nadie} one {una persona} other {# personas}} que podrías conocer",
"filtered_notifications_banner.private_mentions": "{count, plural, one {mención privada} other {menciones privadas}}",
"filtered_notifications_banner.title": "Notificaciones filtradas",
"firehose.all": "Todas",
"firehose.local": "Este servidor",

View file

@ -472,7 +472,11 @@
"notification.own_poll": "Äänestyksesi on päättynyt",
"notification.poll": "Kysely, johon osallistuit, on päättynyt",
"notification.reblog": "{name} tehosti julkaisuasi",
"notification.relationships_severance_event": "Menetettiin yhteydet palvelimeen {name}",
"notification.relationships_severance_event.account_suspension": "Palvelimen {from} ylläpitäjä on jäädyttänyt verkkotunnuksen {target}, minkä takia et voi enää vastaanottaa heidän päivityksiään tai olla vuorovaikutuksessa heidän kanssaan.",
"notification.relationships_severance_event.domain_block": "Palvelimen {from} ylläpitäjä on estänyt verkkotunnuksen {target}, mukaan lukien {followersCount} seuraajistasi ja {followingCount, plural, one {# seuratuistasi} other {# seuratuistasi}}.",
"notification.relationships_severance_event.learn_more": "Lue lisää",
"notification.relationships_severance_event.user_domain_block": "Olet estänyt verkkotunnuksen {target}, mikä poisti {followersCount} seuraajistasi ja {followingCount, plural, one {# seuratuistasi} other {# seuratuistasi}}.",
"notification.status": "{name} julkaisi juuri",
"notification.update": "{name} muokkasi julkaisua",
"notification_requests.accept": "Hyväksy",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "Filtrar esta publicación",
"filter_modal.title.status": "Filtrar unha publicación",
"filtered_notifications_banner.pending_requests": "Notificacións de {count, plural, =0 {ninguén} one {unha persoa} other {# persoas}} que poderías coñecer",
"filtered_notifications_banner.private_mentions": "{count, plural, one {mención privada} other {mencións privadas}}",
"filtered_notifications_banner.title": "Notificacións filtradas",
"firehose.all": "Todo",
"firehose.local": "Este servidor",
@ -471,6 +472,11 @@
"notification.own_poll": "A túa enquisa rematou",
"notification.poll": "Rematou a enquisa na que votaches",
"notification.reblog": "{name} compartiu a túa publicación",
"notification.relationships_severance_event": "Perdeuse a conexión con {name}",
"notification.relationships_severance_event.account_suspension": "A administración de {from} suspendeu a {target}, o que significa que xa non vas recibir actualizacións de esa conta ou interactuar con ela.",
"notification.relationships_severance_event.domain_block": "A administración de {from} bloqueou a {target}, que inclúe a {followersCount} das túas seguidoras e a {followingCount, plural, one {# conta} other {# contas}} que sigues.",
"notification.relationships_severance_event.learn_more": "Saber máis",
"notification.relationships_severance_event.user_domain_block": "Bloqueaches a {target}, eliminando a {followersCount} das túas seguidoras e a {followingCount, plural, one {# conta} other {# contas}} que sigues.",
"notification.status": "{name} publicou",
"notification.update": "{name} editou unha publicación",
"notification_requests.accept": "Aceptar",

View file

@ -124,7 +124,7 @@
"column.domain_blocks": "Letiltott domainek",
"column.favourites": "Kedvencek",
"column.firehose": "Hírfolyamok",
"column.follow_requests": "Követési kérelmek",
"column.follow_requests": "Követési kérések",
"column.home": "Kezdőlap",
"column.lists": "Listák",
"column.mutes": "Némított felhasználók",
@ -133,8 +133,8 @@
"column.public": "Föderációs idővonal",
"column_back_button.label": "Vissza",
"column_header.hide_settings": "Beállítások elrejtése",
"column_header.moveLeft_settings": "Oszlop elmozdítása balra",
"column_header.moveRight_settings": "Oszlop elmozdítása jobbra",
"column_header.moveLeft_settings": "Oszlop áthelyezése balra",
"column_header.moveRight_settings": "Oszlop áthelyezése jobbra",
"column_header.pin": "Kitűzés",
"column_header.show_settings": "Beállítások megjelenítése",
"column_header.unpin": "Kitűzés eltávolítása",
@ -143,7 +143,7 @@
"community.column_settings.media_only": "Csak média",
"community.column_settings.remote_only": "Csak távoli",
"compose.language.change": "Nyelv megváltoztatása",
"compose.language.search": "Nyelv keresése...",
"compose.language.search": "Nyelvek keresése…",
"compose.published.body": "A bejegyzés publikálásra került.",
"compose.published.open": "Megnyitás",
"compose.saved.body": "A bejegyzés mentve.",
@ -473,8 +473,8 @@
"notification.poll": "Egy szavazás, melyben részt vettél, véget ért",
"notification.reblog": "{name} megtolta a bejegyzésedet",
"notification.relationships_severance_event": "Elvesztek a kapcsolatok vele: {name}",
"notification.relationships_severance_event.account_suspension": "Egy admin a {from} kiszolgálóról felfüggesztette {target} fiókot, ami azt jelenti, hogy mostantól nem tudsz vele interaktálni vagy tőle értesítéseket kapni.",
"notification.relationships_severance_event.domain_block": "Egy admin a {from} kiszolgálón letiltotta {target} domaint, beleértve {followersCount} követődet és {followingCount, plural, one {#} other {#}} általad követett személyt.",
"notification.relationships_severance_event.account_suspension": "Egy admin a(z) {from} kiszolgálóról felfüggesztette {target} fiókját, ami azt jelenti, hogy mostantól nem fogsz róla értesítést kapni, és nem fogsz tudni vele kapcsolatba lépni.",
"notification.relationships_severance_event.domain_block": "Egy admin a(z) {from} kiszolgálón letiltotta {target} domaint, beleértve {followersCount} követőt és {followingCount, plural, one {#} other {#}} követett fiókot.",
"notification.relationships_severance_event.learn_more": "További információk",
"notification.relationships_severance_event.user_domain_block": "Letiltottad a(z) {target} domaint, ezzel eltávolítva {followersCount} követőt és {followingCount, plural, one {#} other {#}} követett fiókot.",
"notification.status": "{name} bejegyzést tett közzé",
@ -492,7 +492,7 @@
"notifications.column_settings.filter_bar.advanced": "Minden kategória megjelenítése",
"notifications.column_settings.filter_bar.category": "Gyorsszűrő sáv",
"notifications.column_settings.follow": "Új követők:",
"notifications.column_settings.follow_request": "Új követési kérelmek:",
"notifications.column_settings.follow_request": "Új követési kérések:",
"notifications.column_settings.mention": "Megemlítések:",
"notifications.column_settings.poll": "Szavazási eredmények:",
"notifications.column_settings.push": "Leküldéses értesítések",
@ -552,14 +552,14 @@
"onboarding.share.next_steps": "Lehetséges következő lépések:",
"onboarding.share.title": "Profil megosztása",
"onboarding.start.lead": "Az új Mastodon-fiók használatra kész. Így hozhatod ki belőle a legtöbbet:",
"onboarding.start.skip": "Szeretnél előreugrani?",
"onboarding.start.skip": "Nincs szükséged segítségre a kezdéshez?",
"onboarding.start.title": "Ez sikerült!",
"onboarding.steps.follow_people.body": "A Mastodon az érdekes emberek követéséről szól.",
"onboarding.steps.follow_people.title": "{count, plural, one {egy ember} other {# ember}} követése",
"onboarding.steps.publish_status.body": "Üdvözöljük a világot.",
"onboarding.steps.follow_people.title": "Szabd személyre a kezdőlapodat",
"onboarding.steps.publish_status.body": "Köszöntsd a világot szöveggel, fotókkal, videókkal vagy szavazásokkal {emoji}",
"onboarding.steps.publish_status.title": "Az első bejegyzés létrehozása",
"onboarding.steps.setup_profile.body": "Mások nagyobb valószínűséggel lépnek kapcsolatba veled egy kitöltött profil esetén.",
"onboarding.steps.setup_profile.title": "Profilod testreszabása",
"onboarding.steps.setup_profile.body": "Növeld az interakciók számát a profilod részletesebb kitöltésével.",
"onboarding.steps.setup_profile.title": "Szabd személyre a profilodat",
"onboarding.steps.share_profile.body": "Tudasd az ismerőseiddel, hogyan találhatnak meg a Mastodonon",
"onboarding.steps.share_profile.title": "Oszd meg a Mastodon profilodat",
"onboarding.tips.2fa": "<strong>Tudtad?</strong> A fiókod biztonságossá teheted, ha a fiók beállításaiban beállítod a kétlépcsős hitelesítést. Bármilyen választott TOTP alkalmazással működik, nincs szükség telefonszámra!",
@ -787,9 +787,9 @@
"upload_modal.hint": "Kattints vagy húzd a kört az előnézetben arra a fókuszpontra, mely minden bélyegképen látható kell, hogy legyen.",
"upload_modal.preparing_ocr": "OCR előkészítése…",
"upload_modal.preview_label": "Előnézet ({ratio})",
"upload_progress.label": "Feltöltés...",
"upload_progress.label": "Feltöltés",
"upload_progress.processing": "Feldolgozás…",
"username.taken": "Ez a felhasználónév foglalt. Válassz másikat",
"username.taken": "Ez a felhasználónév foglalt. Válassz másikat.",
"video.close": "Videó bezárása",
"video.download": "Fájl letöltése",
"video.exit_fullscreen": "Kilépés teljes képernyőből",
@ -799,5 +799,5 @@
"video.mute": "Hang némítása",
"video.pause": "Szünet",
"video.play": "Lejátszás",
"video.unmute": "Hang némításának vége"
"video.unmute": "Hang némításának feloldása"
}

View file

@ -473,7 +473,10 @@
"notification.poll": "Könnun sem þú tókst þátt í er lokið",
"notification.reblog": "{name} endurbirti færsluna þína",
"notification.relationships_severance_event": "Missti tengingar við {name}",
"notification.relationships_severance_event.account_suspension": "Stjórnandi á {from} hefur fryst {target}, sem þýðir að þú færð ekki lengur skilaboð frá viðkomandi né átt í samskiptum við viðkomandi.",
"notification.relationships_severance_event.domain_block": "Stjórnandi á {from} hefur lokað á {target} og þar með {followersCount} fylgjendur þína auk {followingCount, plural, one {# aðgangs} other {# aðganga}} sem þú fylgist með.",
"notification.relationships_severance_event.learn_more": "Kanna nánar",
"notification.relationships_severance_event.user_domain_block": "Þú hefur lokað á {target} og þar með fjarlægt {followersCount} fylgjendur þína auk {followingCount, plural, one {# aðgangs} other {# aðganga}} sem þú fylgist með.",
"notification.status": "{name} sendi inn rétt í þessu",
"notification.update": "{name} breytti færslu",
"notification_requests.accept": "Samþykkja",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "Filtra questo post",
"filter_modal.title.status": "Filtra un post",
"filtered_notifications_banner.pending_requests": "Notifiche da {count, plural, =0 {nessuno} one {una persona} other {# persone}} che potresti conoscere",
"filtered_notifications_banner.private_mentions": "{count, plural,one {menzione privata} other {menzioni private}}",
"filtered_notifications_banner.title": "Notifiche filtrate",
"firehose.all": "Tutto",
"firehose.local": "Questo server",
@ -473,7 +474,9 @@
"notification.reblog": "{name} ha rebloggato il tuo post",
"notification.relationships_severance_event": "Connessioni perse con {name}",
"notification.relationships_severance_event.account_suspension": "Un amministratore da {from} ha sospeso {target}, il che significa che non puoi più ricevere aggiornamenti da loro o interagire con loro.",
"notification.relationships_severance_event.domain_block": "Un amministratore da {from} ha bloccato {target}, inclusi {followersCount} dei tuoi seguaci e {followingCount, plural, one {# account} other {# account}} che segui.",
"notification.relationships_severance_event.learn_more": "Scopri di più",
"notification.relationships_severance_event.user_domain_block": "Tu hai bloccato {target}, rimuovendo {followersCount} dei tuoi seguaci e {followingCount, plural, one {# account} other {# account}} che segui.",
"notification.status": "{name} ha appena pubblicato un post",
"notification.update": "{name} ha modificato un post",
"notification_requests.accept": "Accetta",

View file

@ -177,6 +177,7 @@
"confirmations.delete_list.message": "本当にこのリストを完全に削除しますか?",
"confirmations.discard_edit_media.confirm": "破棄",
"confirmations.discard_edit_media.message": "メディアの説明またはプレビューに保存されていない変更があります。それでも破棄しますか?",
"confirmations.domain_block.confirm": "サーバーをブロック",
"confirmations.domain_block.message": "本当に{domain}全体を非表示にしますか? 多くの場合は個別にブロックやミュートするだけで充分であり、また好ましいです。公開タイムラインにそのドメインのコンテンツが表示されなくなり、通知も届かなくなります。そのドメインのフォロワーはアンフォローされます。",
"confirmations.edit.confirm": "編集",
"confirmations.edit.message": "今編集すると現在作成中のメッセージが上書きされます。本当に実行しますか?",

View file

@ -221,6 +221,7 @@
"filter_modal.select_filter.prompt_new": "Taggayt tamaynutt : {name}",
"filter_modal.select_filter.search": "Nadi neɣ snulfu-d",
"filter_modal.select_filter.title": "Sizdeg tassufeɣt-a",
"filter_modal.title.status": "Sizdeg tassufeɣt",
"firehose.all": "Akk",
"firehose.local": "Deg uqeddac-ayi",
"firehose.remote": "Iqeddacen nniḍen",
@ -335,6 +336,7 @@
"mute_modal.show_options": "Sken-d tinefrunin",
"mute_modal.title": "Sgugem aseqdac?",
"navigation_bar.about": "Ɣef",
"navigation_bar.advanced_interface": "Ldi deg ugrudem n web leqqayen",
"navigation_bar.blocks": "Iseqdacen yettusḥebsen",
"navigation_bar.bookmarks": "Ticraḍ",
"navigation_bar.community_timeline": "Tasuddemt tadigant",
@ -364,6 +366,7 @@
"notification.own_poll": "Tafrant-ik·im tfuk",
"notification.poll": "Tfukk tefrant ideg tettekkaḍ",
"notification.reblog": "{name} yebḍa tajewwiqt-ik i tikelt-nniḍen",
"notification.relationships_severance_event.learn_more": "Issin ugar",
"notification.status": "{name} akken i d-yessufeɣ",
"notification_requests.accept": "Qbel",
"notification_requests.dismiss": "Agi",
@ -372,6 +375,8 @@
"notifications.clear_confirmation": "Tebɣiḍ s tidet ad tekkseḍ akk tilɣa-inek·em i lebda?",
"notifications.column_settings.alert": "Tilɣa n tnarit",
"notifications.column_settings.favourite": "Imenyafen:",
"notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin",
"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.mention": "Abdar:",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "이 게시물을 필터",
"filter_modal.title.status": "게시물 필터",
"filtered_notifications_banner.pending_requests": "알 수도 있는 {count, plural, =0 {0 명} one {한 명} other {# 명}}의 사람들로부터의 알림",
"filtered_notifications_banner.private_mentions": "{count, plural, other {개인적인 멘션}}",
"filtered_notifications_banner.title": "걸러진 알림",
"firehose.all": "모두",
"firehose.local": "이 서버",
@ -471,6 +472,11 @@
"notification.own_poll": "설문을 마침",
"notification.poll": "참여한 설문이 종료됨",
"notification.reblog": "{name} 님이 부스트했습니다",
"notification.relationships_severance_event": "{name} 님과의 연결이 끊어졌습니다",
"notification.relationships_severance_event.account_suspension": "{from}의 관리자가 {target}를 정지시켰기 때문에 그들과 더이상 상호작용 할 수 없고 정보를 받아볼 수 없습니다.",
"notification.relationships_severance_event.domain_block": "{from}의 관리자가 {target}를 차단하였고 여기에는 나의 {followersCount} 명의 팔로워와 {followingCount, plural, other {#}} 명의 팔로우가 포함되었습니다.",
"notification.relationships_severance_event.learn_more": "더 알아보기",
"notification.relationships_severance_event.user_domain_block": "내가 {target}를 차단하여 {followersCount} 명의 팔로워와 {followingCount, plural, other {#}} 명의 팔로우가 제거되었습니다.",
"notification.status": "{name} 님이 방금 게시물을 올렸습니다",
"notification.update": "{name} 님이 게시물을 수정했습니다",
"notification_requests.accept": "수락",
@ -484,6 +490,7 @@
"notifications.column_settings.alert": "데스크탑 알림",
"notifications.column_settings.favourite": "좋아요:",
"notifications.column_settings.filter_bar.advanced": "모든 범주 표시",
"notifications.column_settings.filter_bar.category": "빠른 필터 막대",
"notifications.column_settings.follow": "새 팔로워:",
"notifications.column_settings.follow_request": "새 팔로우 요청:",
"notifications.column_settings.mention": "멘션:",

View file

@ -89,8 +89,12 @@
"announcement.announcement": "Comunicados",
"attachments_list.unprocessed": "(não processado)",
"audio.hide": "Ocultar áudio",
"block_modal.remote_users_caveat": "Pediremos ao servidor {domínio} que respeite sua decisão. No entanto, a conformidade não é garantida pois alguns servidores podem lidar com os blocos de maneira diferente. As postagens públicas ainda podem estar visíveis para usuários não logados.",
"block_modal.show_less": "Mostrar menos",
"block_modal.show_more": "Mostrar mais",
"block_modal.they_cant_mention": "Eles não podem mencionar ou seguir você.",
"block_modal.they_cant_see_posts": "Eles não podem ver suas postagens e você não verá as deles.",
"block_modal.they_will_know": "Eles podem ver que estão bloqueados.",
"block_modal.title": "Bloquear usuário?",
"block_modal.you_wont_see_mentions": "Você não verá publicações que os mencionem.",
"boost_modal.combo": "Pressione {combo} para pular isso na próxima vez",
@ -173,6 +177,7 @@
"confirmations.delete_list.message": "Você tem certeza de que deseja excluir esta lista?",
"confirmations.discard_edit_media.confirm": "Descartar",
"confirmations.discard_edit_media.message": "Há mudanças não salvas na descrição ou pré-visualização da mídia. Descartar assim mesmo?",
"confirmations.domain_block.confirm": "Servidor de blocos",
"confirmations.domain_block.message": "Você tem certeza de que deseja bloquear tudo de {domain}? Você não verá mais o conteúdo desta instância em nenhuma linha do tempo pública ou nas suas notificações. Seus seguidores desta instância serão removidos.",
"confirmations.edit.confirm": "Editar",
"confirmations.edit.message": "Editar agora irá substituir a mensagem que está sendo criando. Tem certeza de que deseja continuar?",
@ -204,8 +209,27 @@
"dismissable_banner.explore_statuses": "Estas são postagens de toda a rede social que estão ganhando tração hoje. Postagens mais recentes com mais impulsos e favoritos têm classificações mais altas.",
"dismissable_banner.explore_tags": "Estas hashtags estão ganhando popularidade no momento entre as pessoas deste e de outros servidores da rede descentralizada.",
"dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas na rede social que pessoas em {domain} seguem.",
"domain_block_modal.block": "Servidor de blocos.",
"domain_block_modal.block_account_instead": "Bloco @(nome)",
"domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.",
"domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.",
"domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.",
"domain_block_modal.title": "Dominio do bloco",
"domain_block_modal.you_will_lose_followers": "Todos os seus seguidores deste servidor serão removidos.",
"domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.",
"domain_pill.activitypub_lets_connect": "Ele permite que você se conecte e interaja com pessoas não apenas no Mastodon, mas também em diferentes aplicativos sociais.",
"domain_pill.activitypub_like_language": "ActivityPub é como a linguagem que o Mastodon fala com outras redes sociais.",
"domain_pill.server": "Servidor",
"domain_pill.their_handle": "Seu identificador:",
"domain_pill.their_server": "Sua casa digital, onde ficam todas as suas postagens.",
"domain_pill.their_username": "Seu identificador exclusivo em seu servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.",
"domain_pill.username": "Nome de usuário",
"domain_pill.whats_in_a_handle": "O que há em uma alça?",
"domain_pill.who_they_are": "Como os identificadores indicam quem alguém é e onde está, você pode interagir com pessoas na web social de <button>plataformas alimentadas pelo ActivityPub</button>.",
"domain_pill.who_you_are": "Como seu identificador indica quem você é e onde está, as pessoas podem interagir com você nas redes sociais das <button>plataformas alimentadas pelo ActivityPub</button>.",
"domain_pill.your_handle": "Seu identificador:",
"domain_pill.your_server": "Sua casa digital, onde ficam todas as suas postagens. Não gosta deste? Transfira servidores a qualquer momento e traga seus seguidores também.",
"domain_pill.your_username": "Seu identificador exclusivo neste servidor. É possível encontrar usuários com o mesmo nome de usuário em servidores diferentes.",
"embed.instructions": "Incorpore este toot no seu site ao copiar o código abaixo.",
"embed.preview": "Aqui está como vai ficar:",
"emoji_button.activity": "Atividade",
@ -273,6 +297,8 @@
"filter_modal.select_filter.subtitle": "Use uma categoria existente ou crie uma nova",
"filter_modal.select_filter.title": "Filtrar esta publicação",
"filter_modal.title.status": "Filtrar uma publicação",
"filtered_notifications_banner.pending_requests": "Notificações de {count, plural, =0 {no one} one {one person} other {# people}} que você talvez conheça",
"filtered_notifications_banner.private_mentions": "{count, plural, one {private mention} other {private mentions}}",
"filtered_notifications_banner.title": "Notificações filtradas",
"firehose.all": "Tudo",
"firehose.local": "Este servidor",
@ -402,7 +428,9 @@
"loading_indicator.label": "Carregando…",
"media_gallery.toggle_visible": "{number, plural, one {Ocultar mídia} other {Ocultar mídias}}",
"moved_to_account_banner.text": "Sua conta {disabledAccount} está desativada porque você a moveu para {movedToAccount}.",
"mute_modal.hide_from_notifications": "Ocultar das notificações",
"mute_modal.hide_options": "Ocultar opções",
"mute_modal.indefinite": "Até que eu os ative",
"mute_modal.show_options": "Mostrar opções",
"mute_modal.they_can_mention_and_follow": "Eles podem mencionar e seguir você, mas você não os verá.",
"mute_modal.they_wont_know": "Eles não saberão que foram silenciados.",
@ -444,6 +472,11 @@
"notification.own_poll": "Sua enquete terminou",
"notification.poll": "Uma enquete que você votou terminou",
"notification.reblog": "{name} deu boost no teu toot",
"notification.relationships_severance_event": "Conexões perdidas com {name}",
"notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que você não pode mais receber atualizações deles ou interagir com eles.",
"notification.relationships_severance_event.domain_block": "An admin from {from} has blocked {target}, including {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
"notification.relationships_severance_event.learn_more": "Saber mais",
"notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
"notification.status": "{name} acabou de tootar",
"notification.update": "{name} editou uma publicação",
"notification_requests.accept": "Aceitar",
@ -456,6 +489,8 @@
"notifications.column_settings.admin.sign_up": "Novas inscrições:",
"notifications.column_settings.alert": "Notificações no computador",
"notifications.column_settings.favourite": "Favoritos:",
"notifications.column_settings.filter_bar.advanced": "Exibir todas as categorias",
"notifications.column_settings.filter_bar.category": "Barra de filtro rápido",
"notifications.column_settings.follow": "Seguidores:",
"notifications.column_settings.follow_request": "Seguidores pendentes:",
"notifications.column_settings.mention": "Menções:",
@ -481,7 +516,9 @@
"notifications.permission_denied": "Navegador não tem permissão para ativar notificações no computador.",
"notifications.permission_denied_alert": "Verifique a permissão do navegador para ativar notificações no computador.",
"notifications.permission_required": "Ativar notificações no computador exige permissão do navegador.",
"notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}",
"notifications.policy.filter_new_accounts_title": "Novas contas",
"notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}",
"notifications.policy.filter_not_followers_title": "Pessoas que não estão te seguindo",
"notifications.policy.filter_not_following_hint": "Até que você os aprove manualmente",
"notifications.policy.filter_not_following_title": "Pessoas que você não segue",
@ -569,6 +606,7 @@
"relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s",
"relative_time.today": "hoje",
"reply_indicator.attachments": "{count, plural, one {# attachment} other {# attachments}}",
"reply_indicator.cancel": "Cancelar",
"reply_indicator.poll": "Enquete",
"report.block": "Bloquear",
@ -667,6 +705,7 @@
"status.edited_x_times": "Editado {count, plural, one {{count} hora} other {{count} vezes}}",
"status.embed": "Incorporar",
"status.favourite": "Favorita",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar esta publicação",
"status.filtered": "Filtrado",
"status.hide": "Ocultar publicação",
@ -687,6 +726,7 @@
"status.reblog": "Dar boost",
"status.reblog_private": "Dar boost para o mesmo público",
"status.reblogged_by": "{name} deu boost",
"status.reblogs": "{count, plural, one {boost} other {boosts}}",
"status.reblogs.empty": "Nada aqui. Quando alguém der boost, o usuário aparecerá aqui.",
"status.redraft": "Excluir e rascunhar",
"status.remove_bookmark": "Remover do Salvos",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "Filtrar esta publicação",
"filter_modal.title.status": "Filtrar uma publicação",
"filtered_notifications_banner.pending_requests": "Notificações de {count, plural, =0 {ninguém} one {uma pessoa} other {# pessoas}} que talvez conheça",
"filtered_notifications_banner.private_mentions": "{count, plural,one {menção privada} other {menções privadas}}",
"filtered_notifications_banner.title": "Notificações filtradas",
"firehose.all": "Todas",
"firehose.local": "Este servidor",
@ -471,6 +472,11 @@
"notification.own_poll": "A sua votação terminou",
"notification.poll": "Uma votação em que participaste chegou ao fim",
"notification.reblog": "{name} reforçou a tua publicação",
"notification.relationships_severance_event": "Perdeu as ligações com {name}",
"notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que já não pode receber atualizações dele ou interagir com ele.",
"notification.relationships_severance_event.domain_block": "Um administrador de {from} bloqueou {target}, incluindo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.",
"notification.relationships_severance_event.learn_more": "Saber mais",
"notification.relationships_severance_event.user_domain_block": "Bloqueou {target}, removendo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.",
"notification.status": "{name} acabou de publicar",
"notification.update": "{name} editou uma publicação",
"notification_requests.accept": "Aceitar",

View file

@ -298,6 +298,7 @@
"filter_modal.select_filter.title": "Filtriraj to objavo",
"filter_modal.title.status": "Filtrirajte objavo",
"filtered_notifications_banner.pending_requests": "Obvestila od {count, plural, =0 {nikogar, ki bi ga} one {# človeka, ki bi ga} two {# ljudi, ki bi ju} few {# ljudi, ki bi jih} other {# ljudi, ki bi jih}} lahko poznali",
"filtered_notifications_banner.private_mentions": "{count, plural, one {zasebna omemba} two {zasebni omembi} few {zasebne omembe} other {zasebnih omemb}}",
"filtered_notifications_banner.title": "Filtrirana obvestila",
"firehose.all": "Vse",
"firehose.local": "Ta strežnik",
@ -471,6 +472,11 @@
"notification.own_poll": "Vaša anketa je zaključena",
"notification.poll": "Anketa, v kateri ste sodelovali, je zaključena",
"notification.reblog": "{name} je izpostavila/a vašo objavo",
"notification.relationships_severance_event": "Povezave z {name} prekinjene",
"notification.relationships_severance_event.account_suspension": "Skrbnik na {from} je suspendiral račun {target}, kar pomeni, da od računa ne morete več prejemati posodobitev ali imeti z njim interakcij.",
"notification.relationships_severance_event.domain_block": "Skrbnik na {from} je blokiral domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.",
"notification.relationships_severance_event.learn_more": "Več o tem",
"notification.relationships_severance_event.user_domain_block": "Blokirali ste domeno {target}, vključno z vašimi sledilci ({followersCount}) in {followingCount, plural, one {# računom, ki mu sledite} two {# računoma, ki jima sledite} few {# računi, ki jim sledite} other {# računi, ki jim sledite}}.",
"notification.status": "{name} je pravkar objavil/a",
"notification.update": "{name} je uredil(a) objavo",
"notification_requests.accept": "Sprejmi",

View file

@ -462,6 +462,7 @@
"notification.own_poll": "Ваша анкета је завршена",
"notification.poll": "Завршена је анкета у којој сте гласали",
"notification.reblog": "{name} је подржао вашу објаву",
"notification.relationships_severance_event.learn_more": "Сазнајте више",
"notification.status": "{name} је управо објавио",
"notification.update": "{name} је уредио објаву",
"notification_requests.accept": "Прихвати",
@ -474,6 +475,7 @@
"notifications.column_settings.admin.sign_up": "Нове рагистрације:",
"notifications.column_settings.alert": "Обавештења на радној површини",
"notifications.column_settings.favourite": "Омиљено:",
"notifications.column_settings.filter_bar.advanced": "Прикажи све категорије",
"notifications.column_settings.follow": "Нови пратиоци:",
"notifications.column_settings.follow_request": "Нови захтеви за праћење:",
"notifications.column_settings.mention": "Помињања:",

View file

@ -21,14 +21,14 @@ import history from './history';
import listAdder from './list_adder';
import listEditor from './list_editor';
import lists from './lists';
import markers from './markers';
import { markersReducer } from './markers';
import media_attachments from './media_attachments';
import meta from './meta';
import { modalReducer } from './modal';
import { notificationPolicyReducer } from './notification_policy';
import { notificationRequestsReducer } from './notification_requests';
import notifications from './notifications';
import picture_in_picture from './picture_in_picture';
import { pictureInPictureReducer } from './picture_in_picture';
import polls from './polls';
import push_notifications from './push_notifications';
import { relationshipsReducer } from './relationships';
@ -77,8 +77,8 @@ const reducers = {
suggestions,
polls,
trends,
markers,
picture_in_picture,
markers: markersReducer,
picture_in_picture: pictureInPictureReducer,
history,
tags,
followed_tags,

View file

@ -1,26 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import {
MARKERS_SUBMIT_SUCCESS,
} from '../actions/markers';
const initialState = ImmutableMap({
home: '0',
notifications: '0',
});
export default function markers(state = initialState, action) {
switch(action.type) {
case MARKERS_SUBMIT_SUCCESS:
if (action.home) {
state = state.set('home', action.home);
}
if (action.notifications) {
state = state.set('notifications', action.notifications);
}
return state;
default:
return state;
}
}

View file

@ -0,0 +1,18 @@
import { createReducer } from '@reduxjs/toolkit';
import { submitMarkersAction } from 'mastodon/actions/markers';
const initialState = {
home: '0',
notifications: '0',
};
export const markersReducer = createReducer(initialState, (builder) => {
builder.addCase(
submitMarkersAction.fulfilled,
(state, { payload: { home, notifications } }) => {
if (home) state.home = home;
if (notifications) state.notifications = notifications;
},
);
});

View file

@ -13,7 +13,7 @@ import {
unfocusApp,
} from '../actions/app';
import {
MARKERS_FETCH_SUCCESS,
fetchMarkers,
} from '../actions/markers';
import {
notificationsUpdate,
@ -255,8 +255,8 @@ const recountUnread = (state, last_read_id) => {
export default function notifications(state = initialState, action) {
switch(action.type) {
case MARKERS_FETCH_SUCCESS:
return action.markers.notifications ? recountUnread(state, action.markers.notifications.last_read_id) : state;
case fetchMarkers.fulfilled.type:
return action.payload.markers.notifications ? recountUnread(state, action.payload.markers.notifications.last_read_id) : state;
case NOTIFICATIONS_MOUNT:
return updateMounted(state);
case NOTIFICATIONS_UNMOUNT:

View file

@ -1,26 +0,0 @@
import { PICTURE_IN_PICTURE_DEPLOY, PICTURE_IN_PICTURE_REMOVE } from 'mastodon/actions/picture_in_picture';
import { TIMELINE_DELETE } from '../actions/timelines';
const initialState = {
statusId: null,
accountId: null,
type: null,
src: null,
muted: false,
volume: 0,
currentTime: 0,
};
export default function pictureInPicture(state = initialState, action) {
switch(action.type) {
case PICTURE_IN_PICTURE_DEPLOY:
return { statusId: action.statusId, accountId: action.accountId, type: action.playerType, ...action.props };
case PICTURE_IN_PICTURE_REMOVE:
return { ...initialState };
case TIMELINE_DELETE:
return (state.statusId === action.id) ? { ...initialState } : state;
default:
return state;
}
}

View file

@ -0,0 +1,56 @@
import type { Reducer } from '@reduxjs/toolkit';
import {
deployPictureInPictureAction,
removePictureInPicture,
} from 'mastodon/actions/picture_in_picture';
import { TIMELINE_DELETE } from '../actions/timelines';
export interface PIPMediaProps {
src: string;
muted: boolean;
volume: number;
currentTime: number;
poster: string;
backgroundColor: string;
foregroundColor: string;
accentColor: string;
}
interface PIPStateWithValue extends Partial<PIPMediaProps> {
statusId: string;
accountId: string;
type: 'audio' | 'video';
}
interface PIPStateEmpty extends Partial<PIPMediaProps> {
type: null;
}
type PIPState = PIPStateWithValue | PIPStateEmpty;
const initialState = {
type: null,
muted: false,
volume: 0,
currentTime: 0,
};
export const pictureInPictureReducer: Reducer<PIPState> = (
state = initialState,
action,
) => {
if (deployPictureInPictureAction.match(action))
return {
statusId: action.payload.statusId,
accountId: action.payload.accountId,
type: action.payload.playerType,
...action.payload.props,
};
else if (removePictureInPicture.match(action)) return initialState;
else if (action.type === TIMELINE_DELETE)
if (state.type && state.statusId === action.id) return initialState;
return state;
};

View file

@ -60,7 +60,7 @@ export const makeGetStatus = () => {
export const makeGetPictureInPicture = () => {
return createSelector([
(state, { id }) => state.get('picture_in_picture').statusId === id,
(state, { id }) => state.picture_in_picture.statusId === id,
(state) => state.getIn(['meta', 'layout']) !== 'mobile',
], (inUse, available) => ImmutableMap({
inUse: inUse && available,

View file

@ -2612,6 +2612,7 @@ a.account__display-name {
}
$ui-header-height: 55px;
$ui-header-logo-wordmark-width: 99px;
.ui__header {
display: none;
@ -2627,6 +2628,10 @@ $ui-header-height: 55px;
&__logo {
display: inline-flex;
padding: 15px;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
container: header-logo / inline-size;
.logo {
height: $ui-header-height - 30px;
@ -2637,7 +2642,7 @@ $ui-header-height: 55px;
display: none;
}
@media screen and (width >= 320px) {
@container header-logo (min-width: #{$ui-header-logo-wordmark-width}) {
.logo--wordmark {
display: block;
}
@ -2654,6 +2659,7 @@ $ui-header-height: 55px;
gap: 10px;
padding: 0 10px;
overflow: hidden;
flex-shrink: 0;
.button {
flex: 0 0 auto;
@ -5084,6 +5090,7 @@ a.status-card {
.language-dropdown__dropdown {
box-shadow: var(--dropdown-shadow);
background: var(--dropdown-background-color);
backdrop-filter: var(--background-filter);
border: 1px solid var(--dropdown-border-color);
padding: 4px;
border-radius: 4px;

View file

@ -27,6 +27,8 @@ class AccountWarning < ApplicationRecord
suspend: 4_000,
}, suffix: :action
RECENT_PERIOD = 3.months.freeze
normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true
belongs_to :account, inverse_of: :account_warnings
@ -37,7 +39,7 @@ class AccountWarning < ApplicationRecord
scope :latest, -> { order(id: :desc) }
scope :custom, -> { where.not(text: '') }
scope :recent, -> { where('account_warnings.created_at >= ?', 3.months.ago) }
scope :recent, -> { where(created_at: RECENT_PERIOD.ago..) }
def statuses
Status.with_discarded.where(id: status_ids || [])

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::MarkerSerializer < ActiveModel::Serializer
# Please update `app/javascript/mastodon/api_types/markers.ts` when making changes to the attributes
attributes :last_read_id, :version, :updated_at
def last_read_id

View file

@ -16,16 +16,14 @@
= image_tag frontend_asset_url('images/mailer-new/welcome/checkbox-off.png'), alt: '', width: 20, height: 20
%td.email-checklist-icons-step-td
- if defined?(key)
= image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40
= image_tag frontend_asset_url("images/mailer-new/welcome-icons/#{key}_step-#{checked ? 'on' : 'off'}.png"), alt: '', width: 40, height: 40
%td.email-checklist-text-td
.email-desktop-flex
/[if mso]
<table border="0" cellpadding="0" cellspacing="0" align="center" style="width:100%;" role="presentation"><tr><td vertical-align:top;">
%div
- if defined?(title)
%h3= title
- if defined?(text)
%p= text
%h3= t("user_mailer.welcome.#{key}_title")
%p= t("user_mailer.welcome.#{key}_step")
/[if mso]
</td><td style="vertical-align:top;">
%div

View file

@ -23,11 +23,11 @@
%td.email-body-huge-padding-td
%h2.email-h2= t('user_mailer.welcome.checklist_title')
%p.email-h-sub= t('user_mailer.welcome.checklist_subtitle')
= render 'application/mailer/checklist', key: 'edit_profile_step', title: t('user_mailer.welcome.edit_profile_title'), text: t('user_mailer.welcome.edit_profile_step'), checked: @has_account_fields, button_text: t('user_mailer.welcome.edit_profile_action'), button_url: web_url('start/profile')
= render 'application/mailer/checklist', key: 'follow_step', title: t('user_mailer.welcome.follow_title'), text: t('user_mailer.welcome.follow_step'), checked: @has_active_relationships, button_text: t('user_mailer.welcome.follow_action'), button_url: web_url('start/follows')
= render 'application/mailer/checklist', key: 'post_step', title: t('user_mailer.welcome.post_title'), text: t('user_mailer.welcome.post_step'), checked: @has_statuses, button_text: t('user_mailer.welcome.post_action'), button_url: web_url
= render 'application/mailer/checklist', key: 'share_step', title: t('user_mailer.welcome.share_title'), text: t('user_mailer.welcome.share_step'), checked: false, button_text: t('user_mailer.welcome.share_action'), button_url: web_url('start/share')
= render 'application/mailer/checklist', key: 'apps_step', title: t('user_mailer.welcome.apps_title'), text: t('user_mailer.welcome.apps_step'), checked: false, show_apps_buttons: true
= render 'application/mailer/checklist', key: 'edit_profile', checked: @has_account_fields, button_text: t('user_mailer.welcome.edit_profile_action'), button_url: web_url('start/profile')
= render 'application/mailer/checklist', key: 'follow', checked: @has_active_relationships, button_text: t('user_mailer.welcome.follow_action'), button_url: web_url('start/follows')
= render 'application/mailer/checklist', key: 'post', checked: @has_statuses, button_text: t('user_mailer.welcome.post_action'), button_url: web_url
= render 'application/mailer/checklist', key: 'share', checked: false, button_text: t('user_mailer.welcome.share_action'), button_url: web_url('start/share')
= render 'application/mailer/checklist', key: 'apps', checked: false, show_apps_buttons: true
%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' }
%tr
%td.email-body-columns-td

View file

@ -1768,6 +1768,7 @@ bg:
contrast: Mastodon (висок контраст)
default: Mastodon (тъмно)
mastodon-light: Mastodon (светло)
system: Самодейно (употреба на системната тема)
time:
formats:
default: "%d %b, %Y, %H:%M"

View file

@ -1768,6 +1768,7 @@ ca:
contrast: Mastodon (alt contrast)
default: Mastodon (fosc)
mastodon-light: Mastodon (clar)
system: Automàtic (utilitza el tema del sistema)
time:
formats:
default: "%b %d, %Y, %H:%M"

View file

@ -1767,6 +1767,7 @@ da:
contrast: Mastodon (høj kontrast)
default: Mastodont (mørkt)
mastodon-light: Mastodon (lyst)
system: Automatisk (benyt systemtema)
time:
formats:
default: "%d. %b %Y, %H:%M"

View file

@ -1768,6 +1768,7 @@ de:
contrast: Mastodon (Hoher Kontrast)
default: Mastodon (Dunkel)
mastodon-light: Mastodon (Hell)
system: Automatisch (mit System synchronisieren)
time:
formats:
default: "%d. %b %Y, %H:%M Uhr"

View file

@ -1768,6 +1768,7 @@ es-AR:
contrast: Alto contraste
default: Oscuro
mastodon-light: Claro
system: Automático (usar tema del sistema)
time:
formats:
default: "%Y.%b.%d, %H:%M"

View file

@ -1768,6 +1768,7 @@ es-MX:
contrast: Alto contraste
default: Mastodon
mastodon-light: Mastodon (claro)
system: Automático (usar tema del sistema)
time:
formats:
default: "%d de %b del %Y, %H:%M"

View file

@ -1768,6 +1768,7 @@ es:
contrast: Alto contraste
default: Mastodon
mastodon-light: Mastodon (claro)
system: Automático (usar tema del sistema)
time:
formats:
default: "%d de %b del %Y, %H:%M"

View file

@ -1772,6 +1772,7 @@ eu:
contrast: Mastodon (Kontraste altua)
default: Mastodon (Iluna)
mastodon-light: Mastodon (Argia)
system: Automatikoa (erabili sistemaren gaia)
time:
formats:
default: "%Y(e)ko %b %d, %H:%M"

View file

@ -1768,6 +1768,7 @@ fi:
contrast: Mastodon (Korkea kontrasti)
default: Mastodon (Tumma)
mastodon-light: Mastodon (Vaalea)
system: Automaattinen (käytä järjestelmän teemaa)
time:
formats:
default: "%d.%m.%Y klo %H.%M"

View file

@ -1768,6 +1768,7 @@ fo:
contrast: Mastodon (høgur kontrastur)
default: Mastodon (myrkt)
mastodon-light: Mastodon (ljóst)
system: Sjálvvirkandi (brúka vanligt uppsetingareyðkenni)
time:
formats:
default: "%b %d, %Y, %H:%M"

View file

@ -1768,6 +1768,7 @@ gl:
contrast: Mastodon (Alto contraste)
default: Mastodon (Escuro)
mastodon-light: Mastodon (Claro)
system: Automático (seguir ao sistema)
time:
formats:
default: "%d %b, %Y, %H:%M"

View file

@ -1832,6 +1832,7 @@ he:
contrast: מסטודון (ניגודיות גבוהה)
default: מסטודון (כהה)
mastodon-light: מסטודון (בהיר)
system: אוטומטי (לפי המערכת)
time:
formats:
default: "%d %b %Y, %H:%M"

View file

@ -57,27 +57,27 @@ hu:
deleted: Törölve
demote: Lefokozás
destroyed_msg: A %{username} fiók adatai bekerültek a végleges törlése váró sorba
disable: Kikapcsolás
disable_sign_in_token_auth: Tokenes e-mail hitelesítés letiltása
disable: Befagyasztás
disable_sign_in_token_auth: Tokenes e-mail-hitelesítés letiltása
disable_two_factor_authentication: Kétlépcsős hitelesítés kikapcsolása
disabled: Kikapcsolva
display_name: Megjelenített név
disabled: Befagyasztva
display_name: Megjelenítendő név
domain: Domain
edit: Szerkesztés
email: E-mail
email: E-mail-cím
email_status: E-mail állapot
enable: Bekapcsolás
enable_sign_in_token_auth: Tokenes e-mail hitelesítés engedélyezése
enable: Kiolvasztás
enable_sign_in_token_auth: Tokenes e-mail-hitelesítés engedélyezése
enabled: Bekapcsolva
enabled_msg: A %{username} fiók fagyasztását sikeresen visszavontuk
enabled_msg: "%{username} fiókja befagyasztása sikeresen visszavonva"
followers: Követő
follows: Követett
header: Fejléc
inbox_url: Beérkezett üzenetek URL-je
inbox_url: Beérkezett üzenetek webcíme
invite_request_text: Csatlakozás oka
invited_by: Meghívta
ip: IP
joined: Csatlakozott
ip: IP-cím
joined: Csatlakozva
location:
all: Összes
local: Helyi
@ -1768,6 +1768,7 @@ hu:
contrast: Mastodon (nagy kontrasztú)
default: Mastodon (sötét)
mastodon-light: Mastodon (világos)
system: Automatikus (rendszertéma használata)
time:
formats:
default: "%Y. %b %d., %H:%M"

View file

@ -1772,6 +1772,7 @@ is:
contrast: Mastodon (mikil birtuskil)
default: Mastodon (dökkt)
mastodon-light: Mastodon (ljóst)
system: Sjálfvirkt (nota þema kerfis)
time:
formats:
default: "%d. %b, %Y, %H:%M"

View file

@ -1770,6 +1770,7 @@ it:
contrast: Mastodon (contrasto elevato)
default: Mastodon (scuro)
mastodon-light: Mastodon (chiaro)
system: Automatico (usa il tema di sistema)
time:
formats:
default: "%d %b %Y, %H:%M"

View file

@ -391,6 +391,7 @@ kab:
invites: Iɛeṛṛuḍen
moderation: Aseɣyed
delete: Kkes
everyone: Tisirag timezwura
privileges:
administrator: Anedbal
rules:
@ -441,6 +442,10 @@ kab:
system_checks:
rules_check:
action: Sefrek ilugan n uqeddac
software_version_critical_check:
action: Wali ileqqman yellan
software_version_patch_check:
action: Wali ileqqman yellan
title: Tadbelt
trends:
allow: Sireg
@ -602,6 +607,8 @@ kab:
notifications: Ilɣa
thread: Idiwenniyen
edit:
add_keyword: Rnu awal tasarut
keywords: Awalen n tsarut
title: Ẓreg amzizdig
index:
delete: Kkes
@ -640,6 +647,7 @@ kab:
blocking: Tabdart n yimiḍanen iweḥlen
bookmarks: Ticraḍ
following: Tabdert n wid teṭṭafareḍ
lists: Tibdarin
muting: Tabdert n wid tesgugmeḍ
upload: Sali
invites:
@ -750,6 +758,7 @@ kab:
phantom_js: PhantomJS
qq: Iminig QQ
safari: Safari
unknown_browser: Iminig arussin
weibo: Weibo
current_session: Tiɣimit tamirant
date: Azemz

View file

@ -1738,6 +1738,7 @@ ko:
contrast: 마스토돈 (고대비)
default: 마스토돈 (어두움)
mastodon-light: 마스토돈 (밝음)
system: 자동 선택 (시스템 테마 이용)
time:
formats:
default: "%Y-%m-%d %H:%M"

View file

@ -1768,6 +1768,7 @@ nl:
contrast: Mastodon (hoog contrast)
default: Mastodon (donker)
mastodon-light: Mastodon (licht)
system: Automatisch (systeemthema gebruiken)
time:
formats:
default: "%d %B %Y om %H:%M"

View file

@ -1768,6 +1768,7 @@ nn:
contrast: Mastodon (Høg kontrast)
default: Mastodon (Mørkt)
mastodon-light: Mastodon (Lyst)
system: Automatisk (bruk systemdrakta)
time:
formats:
default: "%d.%b %Y, %H:%M"

View file

@ -1832,6 +1832,7 @@ pl:
contrast: Mastodon (Wysoki kontrast)
default: Mastodon (Ciemny)
mastodon-light: Mastodon (Jasny)
system: Automatyczny (odpowiadający motywowi systemu)
time:
formats:
default: "%d. %b %Y, %H:%M"

View file

@ -597,6 +597,9 @@ pt-BR:
actions_description_html: Decida que medidas tomar para resolver esta denúncia. Se você decidir punir a conta denunciada, ela receberá uma notificação por e-mail, exceto quando for selecionada a categoria <strong>spam</strong> for selecionada.
actions_description_remote_html: Decida quais medidas tomará para resolver esta denúncia. Isso só afetará como <strong>seu servidor</strong> se comunica com esta conta remota e manipula seu conteúdo.
add_to_report: Adicionar mais à denúncia
already_suspended_badges:
local: Já suspenso neste servidor
remote: Já suspenso em seu servidor
are_you_sure: Você tem certeza?
assign_to_self: Atribuir para si
assigned: Moderador responsável
@ -1652,13 +1655,20 @@ pt-BR:
import: Importar
import_and_export: Importar e exportar
migrate: Migração de conta
notifications: Notificações por e-mail
preferences: Preferências
profile: Perfil
relationships: Seguindo e seguidores
severed_relationships: Relacionamentos rompidos
statuses_cleanup: Exclusão automatizada de publicações
strikes: Avisos de moderação
two_factor_authentication: Autenticação de dois fatores
webauthn_authentication: Chaves de segurança
severed_relationships:
download: Download %{count}
event_type:
account_suspension: Suspensão da conta (%{target_name})
domain_block: Suspensão do servidor (%{target_name})
statuses:
attached:
audio:

View file

@ -1768,6 +1768,7 @@ pt-PT:
contrast: Mastodon (Elevado contraste)
default: Mastodon (Escuro)
mastodon-light: Mastodon (Claro)
system: Automático (usar tema do sistema)
time:
formats:
default: "%H:%M em %d de %b de %Y"

View file

@ -1899,6 +1899,7 @@ ru:
suspend: Учётная запись заблокирована
welcome:
explanation: Вот несколько советов для новичков
feature_action: Подробнее
subject: Добро пожаловать в Mastodon
title: Добро пожаловать на борт, %{name}!
users:

View file

@ -74,8 +74,8 @@ kab:
setting_default_language: Tutlayt n tira
setting_default_privacy: Tabaḍnit n tira
setting_display_media_default: Akk-a kan
setting_display_media_hide_all: Ffer kullec
setting_display_media_show_all: Ssken kullec
setting_display_media_hide_all: Ffer-iten akk
setting_display_media_show_all: Sken-iten-id akk
setting_hide_network: Ffer azetta-k·m
setting_theme: Asental n wesmel
setting_use_pending_items: Askar aleɣwayan
@ -115,6 +115,8 @@ kab:
text: Alugen
tag:
name: Ahacṭag
user:
time_zone: Tamnaḍt tasragant
user_role:
name: Isem
permissions_as_keys: Tisirag

View file

@ -1832,6 +1832,7 @@ sl:
contrast: Mastodon (Visok kontrast)
default: Mastodon (Temna)
mastodon-light: Mastodon (Svetla)
system: Samodejno (uporabi sistemsko temo)
time:
formats:
default: "%b %d %Y, %H:%M"

View file

@ -1762,6 +1762,7 @@ sq:
contrast: Mastodon (Me shumë kontrast)
default: Mastodon (I errët)
mastodon-light: Mastodon (I çelët)
system: E automatizuar (përdor temë sistemi)
time:
formats:
default: "%d %b, %Y, %H:%M"

View file

@ -1756,6 +1756,7 @@ sv:
contrast: Hög kontrast
default: Mastodon
mastodon-light: Mastodon (ljust)
system: Automatisk (använd systemtema)
time:
formats:
default: "%d %b %Y, %H:%M"

View file

@ -1646,6 +1646,7 @@ th:
user_domain_block: คุณได้ปิดกั้น %{target_name}
lost_followers: ผู้ติดตามที่หายไป
lost_follows: การติดตามที่หายไป
preamble: คุณอาจสูญเสียการติดตามและผู้ติดตามเมื่อคุณปิดกั้นโดเมนหรือเมื่อผู้กลั่นกรองของคุณตัดสินใจที่จะระงับเซิร์ฟเวอร์ระยะไกล เมื่อสิ่งนั้นเกิดขึ้น คุณจะสามารถดาวน์โหลดรายการความสัมพันธ์ที่ตัดขาด เพื่อตรวจสอบและอาจนำเข้าในเซิร์ฟเวอร์อื่น
purged: มีการล้างข้อมูลเกี่ยวกับเซิร์ฟเวอร์นี้โดยผู้ดูแลของเซิร์ฟเวอร์ของคุณ
type: เหตุการณ์
statuses:
@ -1735,6 +1736,7 @@ th:
contrast: Mastodon (ความคมชัดสูง)
default: Mastodon (มืด)
mastodon-light: Mastodon (สว่าง)
system: อัตโนมัติ (ใช้ชุดรูปแบบของระบบ)
time:
formats:
default: "%d %b %Y %H:%M น."

View file

@ -1768,6 +1768,7 @@ tr:
contrast: Mastodon (Yüksek karşıtlık)
default: Mastodon (Karanlık)
mastodon-light: Mastodon (Açık)
system: Otomatik (sistem temasını kullan)
time:
formats:
default: "%d %b %Y %H:%M"

View file

@ -1832,6 +1832,7 @@ uk:
contrast: Mastodon (Висока контрастність)
default: Mastodon (Темна)
mastodon-light: Mastodon (світла)
system: Автоматично (використовувати системну тему)
time:
formats:
default: "%b %d, %Y, %H:%M"

View file

@ -1736,6 +1736,7 @@ vi:
contrast: Mastodon (Tương phản)
default: Mastodon (Tối)
mastodon-light: Mastodon (Sáng)
system: Tự động (chủ đề hệ thống)
time:
formats:
default: "%-d.%m.%Y %H:%M"

View file

@ -1736,6 +1736,7 @@ zh-CN:
contrast: Mastodon高对比度
default: Mastodon暗色主题
mastodon-light: Mastodon亮色主题
system: 自动切换(使用系统主题)
time:
formats:
default: "%Y年%m月%d日 %H:%M"

View file

@ -1738,6 +1738,7 @@ zh-TW:
contrast: Mastodon高對比
default: Mastodon深色
mastodon-light: Mastodon亮色
system: 自動(使用系統佈景主題)
time:
formats:
default: "%Y 年 %b 月 %d 日 %H:%M"