mirror of
https://github.com/glitch-soc/mastodon.git
synced 2026-03-29 03:00:33 +02:00
Merge commit '4e276e4476c81482143440f1f405479e0e4be7eb' into glitch-soc/merge-upstream
This commit is contained in:
20
Gemfile.lock
20
Gemfile.lock
@@ -99,8 +99,8 @@ GEM
|
||||
ast (2.4.3)
|
||||
attr_required (1.0.2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1225.0)
|
||||
aws-sdk-core (3.243.0)
|
||||
aws-partitions (1.1227.0)
|
||||
aws-sdk-core (3.244.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@@ -108,11 +108,11 @@ GEM
|
||||
bigdecimal
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
logger
|
||||
aws-sdk-kms (1.122.0)
|
||||
aws-sdk-core (~> 3, >= 3.241.4)
|
||||
aws-sdk-kms (1.123.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.216.0)
|
||||
aws-sdk-core (~> 3, >= 3.243.0)
|
||||
aws-sdk-s3 (1.217.0)
|
||||
aws-sdk-core (~> 3, >= 3.244.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.12.1)
|
||||
@@ -304,8 +304,8 @@ GEM
|
||||
highline (3.1.2)
|
||||
reline
|
||||
hiredis (0.6.3)
|
||||
hiredis-client (0.27.0)
|
||||
redis-client (= 0.27.0)
|
||||
hiredis-client (0.28.0)
|
||||
redis-client (= 0.28.0)
|
||||
hkdf (0.3.0)
|
||||
htmlentities (4.4.2)
|
||||
http (5.3.1)
|
||||
@@ -707,7 +707,7 @@ GEM
|
||||
reline
|
||||
redcarpet (3.6.1)
|
||||
redis (4.8.1)
|
||||
redis-client (0.27.0)
|
||||
redis-client (0.28.0)
|
||||
connection_pool
|
||||
regexp_parser (2.11.3)
|
||||
reline (0.6.3)
|
||||
@@ -862,7 +862,7 @@ GEM
|
||||
unicode-display_width (>= 1.1.1, < 4)
|
||||
terrapin (1.1.1)
|
||||
climate_control
|
||||
test-prof (1.5.2)
|
||||
test-prof (1.6.0)
|
||||
thor (1.5.0)
|
||||
tilt (2.7.0)
|
||||
timeout (0.6.1)
|
||||
|
||||
@@ -158,7 +158,7 @@ export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } =
|
||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia });
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, exclude_reblogs: withReplies, tagged, max_id: maxId });
|
||||
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}:media${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40, exclude_replies: !withReplies });
|
||||
export const expandListTimeline = (id, { maxId } = {}) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId });
|
||||
export const expandLinkTimeline = (url, { maxId } = {}) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId });
|
||||
export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}) => {
|
||||
|
||||
@@ -155,8 +155,12 @@ export async function apiRequest<
|
||||
export async function apiRequestGet<ApiResponse = unknown, ApiParams = unknown>(
|
||||
url: ApiUrl,
|
||||
params?: RequestParamsOrData<ApiParams>,
|
||||
args: {
|
||||
signal?: AbortSignal;
|
||||
timeout?: number;
|
||||
} = {},
|
||||
) {
|
||||
return apiRequest<ApiResponse>('GET', url, { params });
|
||||
return apiRequest<ApiResponse>('GET', url, { params, ...args });
|
||||
}
|
||||
|
||||
export async function apiRequestPost<ApiResponse = unknown, ApiData = unknown>(
|
||||
|
||||
@@ -4,13 +4,22 @@ import type {
|
||||
ApiSearchResultsJSON,
|
||||
} from 'mastodon/api_types/search';
|
||||
|
||||
export const apiGetSearch = (params: {
|
||||
export const apiGetSearch = (
|
||||
params: {
|
||||
q: string;
|
||||
resolve?: boolean;
|
||||
type?: ApiSearchType;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
}) =>
|
||||
apiRequestGet<ApiSearchResultsJSON>('v2/search', {
|
||||
},
|
||||
options: {
|
||||
signal?: AbortSignal;
|
||||
} = {},
|
||||
) =>
|
||||
apiRequestGet<ApiSearchResultsJSON>(
|
||||
'v2/search',
|
||||
{
|
||||
...params,
|
||||
});
|
||||
},
|
||||
options,
|
||||
);
|
||||
|
||||
@@ -53,6 +53,9 @@ export interface BaseApiAccountJSON {
|
||||
id: string;
|
||||
last_status_at: string;
|
||||
locked: boolean;
|
||||
show_media: boolean;
|
||||
show_media_replies: boolean;
|
||||
show_featured: boolean;
|
||||
noindex?: boolean;
|
||||
note: string;
|
||||
roles?: ApiAccountJSON[];
|
||||
|
||||
@@ -6,7 +6,7 @@ import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import { isClientFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import {
|
||||
fetchRelationships,
|
||||
followAccount,
|
||||
@@ -171,7 +171,7 @@ export const FollowButton: React.FC<{
|
||||
'button--compact': compact,
|
||||
});
|
||||
|
||||
if (isClientFeatureEnabled('profile_editing')) {
|
||||
if (isServerFeatureEnabled('profile_redesign')) {
|
||||
return (
|
||||
<Link to='/profile/edit' className={buttonClasses}>
|
||||
{label}
|
||||
|
||||
@@ -28,7 +28,10 @@ export interface ComboboxItemState {
|
||||
isDisabled: boolean;
|
||||
}
|
||||
|
||||
interface ComboboxProps<T extends ComboboxItem> extends TextInputProps {
|
||||
interface ComboboxProps<T extends ComboboxItem> extends Omit<
|
||||
TextInputProps,
|
||||
'icon'
|
||||
> {
|
||||
/**
|
||||
* The value of the combobox's text input
|
||||
*/
|
||||
@@ -71,6 +74,18 @@ interface ComboboxProps<T extends ComboboxItem> extends TextInputProps {
|
||||
* The main selection handler, called when an option is selected or deselected.
|
||||
*/
|
||||
onSelectItem: (item: T) => void;
|
||||
/**
|
||||
* Icon to be displayed in the text input
|
||||
*/
|
||||
icon?: TextInputProps['icon'] | null;
|
||||
/**
|
||||
* Set to false to keep the menu open when an item is selected
|
||||
*/
|
||||
closeOnSelect?: boolean;
|
||||
/**
|
||||
* Prevent the menu from opening, e.g. to prevent the empty state from showing
|
||||
*/
|
||||
suppressMenu?: boolean;
|
||||
}
|
||||
|
||||
interface Props<T extends ComboboxItem>
|
||||
@@ -124,6 +139,8 @@ const ComboboxWithRef = <T extends ComboboxItem>(
|
||||
onSelectItem,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
closeOnSelect = true,
|
||||
suppressMenu = false,
|
||||
icon = SearchIcon,
|
||||
className,
|
||||
...otherProps
|
||||
@@ -148,7 +165,7 @@ const ComboboxWithRef = <T extends ComboboxItem>(
|
||||
const showStatusMessageInMenu =
|
||||
!!statusMessage && value.length > 0 && items.length === 0;
|
||||
const hasMenuContent =
|
||||
!disabled && (items.length > 0 || showStatusMessageInMenu);
|
||||
!disabled && !suppressMenu && (items.length > 0 || showStatusMessageInMenu);
|
||||
const isMenuOpen = shouldMenuOpen && hasMenuContent;
|
||||
|
||||
const openMenu = useCallback(() => {
|
||||
@@ -204,11 +221,15 @@ const ComboboxWithRef = <T extends ComboboxItem>(
|
||||
const isDisabled = getIsItemDisabled?.(item) ?? false;
|
||||
if (!isDisabled) {
|
||||
onSelectItem(item);
|
||||
|
||||
if (closeOnSelect) {
|
||||
closeMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
inputRef.current?.focus();
|
||||
},
|
||||
[getIsItemDisabled, items, onSelectItem],
|
||||
[closeMenu, closeOnSelect, getIsItemDisabled, items, onSelectItem],
|
||||
);
|
||||
|
||||
const handleSelectItem = useCallback(
|
||||
@@ -343,7 +364,7 @@ const ComboboxWithRef = <T extends ComboboxItem>(
|
||||
value={value}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
icon={icon}
|
||||
icon={icon ?? undefined}
|
||||
className={classNames(classes.input, className)}
|
||||
ref={mergeRefs}
|
||||
/>
|
||||
|
||||
@@ -1,87 +1,71 @@
|
||||
import type { ChangeEventHandler, FC } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useId, useState } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import type { ApiHashtagJSON } from '@/mastodon/api_types/tags';
|
||||
import { Combobox } from '@/mastodon/components/form_fields';
|
||||
import {
|
||||
addFeaturedTag,
|
||||
clearSearch,
|
||||
updateSearchQuery,
|
||||
} from '@/mastodon/reducers/slices/profile_edit';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import { useSearchTags } from '@/mastodon/hooks/useSearchTags';
|
||||
import type { TagSearchResult } from '@/mastodon/hooks/useSearchTags';
|
||||
import { addFeaturedTag } from '@/mastodon/reducers/slices/profile_edit';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
|
||||
import classes from '../styles.module.scss';
|
||||
|
||||
type SearchResult = Omit<ApiHashtagJSON, 'url' | 'history'> & {
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: {
|
||||
id: 'account_edit_tags.search_placeholder',
|
||||
defaultMessage: 'Enter a hashtag…',
|
||||
},
|
||||
addTag: {
|
||||
id: 'account_edit_tags.add_tag',
|
||||
defaultMessage: 'Add #{tagName}',
|
||||
},
|
||||
});
|
||||
|
||||
export const AccountEditTagSearch: FC = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [query, setQuery] = useState('');
|
||||
const {
|
||||
query,
|
||||
tags: suggestedTags,
|
||||
searchTags,
|
||||
resetSearch,
|
||||
isLoading,
|
||||
results: rawResults,
|
||||
} = useAppSelector((state) => state.profileEdit.search);
|
||||
const results = useMemo(() => {
|
||||
if (!rawResults) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const results: SearchResult[] = [...rawResults]; // Make array mutable
|
||||
const trimmedQuery = query.trim();
|
||||
if (
|
||||
trimmedQuery.length > 0 &&
|
||||
results.every(
|
||||
(result) => result.name.toLowerCase() !== trimmedQuery.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
results.push({
|
||||
id: 'new',
|
||||
name: trimmedQuery,
|
||||
label: intl.formatMessage(messages.addTag, { tagName: trimmedQuery }),
|
||||
} = useSearchTags({
|
||||
query,
|
||||
// Remove existing featured tags from suggestions
|
||||
filterResults: (tag) => !tag.featuring,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}, [intl, query, rawResults]);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleSearchChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
void dispatch(updateSearchQuery(e.target.value));
|
||||
setQuery(e.target.value);
|
||||
searchTags(e.target.value);
|
||||
},
|
||||
[dispatch],
|
||||
[searchTags],
|
||||
);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleSelect = useCallback(
|
||||
(item: SearchResult) => {
|
||||
void dispatch(clearSearch());
|
||||
(item: TagSearchResult) => {
|
||||
resetSearch();
|
||||
setQuery('');
|
||||
void dispatch(addFeaturedTag({ name: item.name }));
|
||||
},
|
||||
[dispatch],
|
||||
[dispatch, resetSearch],
|
||||
);
|
||||
|
||||
const inputId = useId();
|
||||
const inputLabel = intl.formatMessage(messages.placeholder);
|
||||
|
||||
return (
|
||||
<>
|
||||
<label htmlFor={inputId} className='sr-only'>
|
||||
{inputLabel}
|
||||
</label>
|
||||
<Combobox
|
||||
id={inputId}
|
||||
value={query}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
items={results}
|
||||
placeholder={inputLabel}
|
||||
items={suggestedTags as TagSearchResult[]}
|
||||
isLoading={isLoading}
|
||||
renderItem={renderItem}
|
||||
onSelectItem={handleSelect}
|
||||
@@ -89,7 +73,8 @@ export const AccountEditTagSearch: FC = () => {
|
||||
icon={SearchIcon}
|
||||
type='search'
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = (item: SearchResult) => item.label ?? `#${item.name}`;
|
||||
const renderItem = (item: TagSearchResult) => item.label ?? `#${item.name}`;
|
||||
|
||||
@@ -2,10 +2,12 @@ import { useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useParams } from 'react-router';
|
||||
import { useHistory } from 'react-router';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { fetchEndorsedAccounts } from 'mastodon/actions/accounts';
|
||||
import { fetchFeaturedTags } from 'mastodon/actions/featured_tags';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
@@ -35,21 +37,27 @@ import { EmptyMessage } from './components/empty_message';
|
||||
import { FeaturedTag } from './components/featured_tag';
|
||||
import type { TagMap } from './components/featured_tag';
|
||||
|
||||
interface Params {
|
||||
acct?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
multiColumn,
|
||||
}) => {
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
|
||||
const forceEmptyState = suspended || blockedBy || hidden;
|
||||
const { acct = '' } = useParams<Params>();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const history = useHistory();
|
||||
useEffect(() => {
|
||||
if (
|
||||
account &&
|
||||
!account.show_featured &&
|
||||
isServerFeatureEnabled('profile_redesign')
|
||||
) {
|
||||
history.push(`/@${account.acct}`);
|
||||
}
|
||||
}, [account, history]);
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId) {
|
||||
void dispatch(fetchFeaturedTags({ accountId }));
|
||||
@@ -166,7 +174,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
aria-posinset={index + 1}
|
||||
aria-setsize={featuredTags.size}
|
||||
>
|
||||
<FeaturedTag tag={tag} account={acct} />
|
||||
<FeaturedTag tag={tag} account={account?.acct ?? ''} />
|
||||
</Article>
|
||||
))}
|
||||
</ItemList>
|
||||
|
||||
@@ -2,10 +2,9 @@ import { useEffect, useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { List as ImmutableList, isList } from 'immutable';
|
||||
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { expandAccountMediaTimeline } from 'mastodon/actions/timelines';
|
||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||
@@ -18,38 +17,69 @@ import Column from 'mastodon/features/ui/components/column';
|
||||
import { useAccountId } from 'mastodon/hooks/useAccountId';
|
||||
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
|
||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||||
import type { RootState } from 'mastodon/store';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import {
|
||||
useAppSelector,
|
||||
useAppDispatch,
|
||||
createAppSelector,
|
||||
} from 'mastodon/store';
|
||||
|
||||
import { MediaItem } from './components/media_item';
|
||||
|
||||
const getAccountGallery = createSelector(
|
||||
const emptyList = ImmutableList<MediaAttachment>();
|
||||
|
||||
const redesignEnabled = isServerFeatureEnabled('profile_redesign');
|
||||
|
||||
const selectGalleryTimeline = createAppSelector(
|
||||
[
|
||||
(state: RootState, accountId: string) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn(
|
||||
[`account:${accountId}:media`, 'items'],
|
||||
ImmutableList(),
|
||||
) as ImmutableList<string>,
|
||||
(state: RootState) => state.statuses,
|
||||
(_state, accountId?: string | null) => accountId,
|
||||
(state) => state.timelines,
|
||||
(state) => state.accounts,
|
||||
(state) => state.statuses,
|
||||
],
|
||||
(statusIds, statuses) => {
|
||||
let items = ImmutableList<MediaAttachment>();
|
||||
(accountId, timelines, accounts, statuses) => {
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
const account = accounts.get(accountId);
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
statusIds.forEach((statusId) => {
|
||||
const status = statuses.get(statusId) as
|
||||
| ImmutableMap<string, unknown>
|
||||
| undefined;
|
||||
let items = emptyList;
|
||||
const { show_media, show_media_replies } = account;
|
||||
// If the account disabled showing media, don't display anything.
|
||||
if (!show_media && redesignEnabled) {
|
||||
return {
|
||||
items,
|
||||
hasMore: false,
|
||||
isLoading: false,
|
||||
showingReplies: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (status) {
|
||||
const showingReplies = show_media_replies && redesignEnabled;
|
||||
const timeline = timelines.get(
|
||||
`account:${accountId}:media${showingReplies ? ':with_replies' : ''}`,
|
||||
);
|
||||
const statusIds = timeline?.get('items');
|
||||
|
||||
if (isList(statusIds)) {
|
||||
for (const statusId of statusIds) {
|
||||
const status = statuses.get(statusId);
|
||||
items = items.concat(
|
||||
(
|
||||
status.get('media_attachments') as ImmutableList<MediaAttachment>
|
||||
status?.get('media_attachments') as ImmutableList<MediaAttachment>
|
||||
).map((media) => media.set('status', status)),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
return {
|
||||
items,
|
||||
hasMore: !!timeline?.get('hasMore'),
|
||||
isLoading: !!timeline?.get('isLoading'),
|
||||
showingReplies,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@@ -58,27 +88,12 @@ export const AccountGallery: React.FC<{
|
||||
}> = ({ multiColumn }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useAccountId();
|
||||
const attachments = useAppSelector((state) =>
|
||||
accountId
|
||||
? getAccountGallery(state, accountId)
|
||||
: ImmutableList<MediaAttachment>(),
|
||||
);
|
||||
const isLoading = useAppSelector((state) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn([
|
||||
`account:${accountId}:media`,
|
||||
'isLoading',
|
||||
]),
|
||||
);
|
||||
const hasMore = useAppSelector((state) =>
|
||||
(state.timelines as ImmutableMap<string, unknown>).getIn([
|
||||
`account:${accountId}:media`,
|
||||
'hasMore',
|
||||
]),
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
accountId ? state.accounts.get(accountId) : undefined,
|
||||
);
|
||||
const isAccount = !!account;
|
||||
const {
|
||||
isLoading = true,
|
||||
hasMore = false,
|
||||
items: attachments = emptyList,
|
||||
showingReplies: withReplies = false,
|
||||
} = useAppSelector((state) => selectGalleryTimeline(state, accountId)) ?? {};
|
||||
|
||||
const { suspended, blockedBy, hidden } = useAccountVisibility(accountId);
|
||||
|
||||
@@ -87,16 +102,18 @@ export const AccountGallery: React.FC<{
|
||||
| undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && isAccount) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId));
|
||||
if (accountId) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId, { withReplies }));
|
||||
}
|
||||
}, [dispatch, accountId, isAccount]);
|
||||
}, [dispatch, accountId, withReplies]);
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
if (maxId) {
|
||||
void dispatch(expandAccountMediaTimeline(accountId, { maxId }));
|
||||
void dispatch(
|
||||
expandAccountMediaTimeline(accountId, { maxId, withReplies }),
|
||||
);
|
||||
}
|
||||
}, [dispatch, accountId, maxId]);
|
||||
}, [maxId, dispatch, accountId, withReplies]);
|
||||
|
||||
const handleOpenMedia = useCallback(
|
||||
(attachment: MediaAttachment) => {
|
||||
|
||||
@@ -5,25 +5,16 @@ import { FormattedMessage } from 'react-intl';
|
||||
import type { NavLinkProps } from 'react-router-dom';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
|
||||
import { isRedesignEnabled } from '../common';
|
||||
|
||||
import classes from './redesign.module.scss';
|
||||
|
||||
export const AccountTabs: FC<{ acct: string }> = ({ acct }) => {
|
||||
if (isRedesignEnabled()) {
|
||||
return (
|
||||
<div className={classes.tabs}>
|
||||
<NavLink isActive={isActive} to={`/@${acct}`}>
|
||||
<FormattedMessage id='account.activity' defaultMessage='Activity' />
|
||||
</NavLink>
|
||||
<NavLink exact to={`/@${acct}/media`}>
|
||||
<FormattedMessage id='account.media' defaultMessage='Media' />
|
||||
</NavLink>
|
||||
<NavLink exact to={`/@${acct}/featured`}>
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
</NavLink>
|
||||
</div>
|
||||
);
|
||||
return <RedesignTabs />;
|
||||
}
|
||||
return (
|
||||
<div className='account__section-headline'>
|
||||
@@ -49,3 +40,32 @@ export const AccountTabs: FC<{ acct: string }> = ({ acct }) => {
|
||||
const isActive: Required<NavLinkProps>['isActive'] = (match, location) =>
|
||||
match?.url === location.pathname ||
|
||||
(!!match?.url && location.pathname.startsWith(`${match.url}/tagged/`));
|
||||
|
||||
const RedesignTabs: FC = () => {
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { acct, show_featured, show_media } = account;
|
||||
|
||||
return (
|
||||
<div className={classes.tabs}>
|
||||
<NavLink isActive={isActive} to={`/@${acct}`}>
|
||||
<FormattedMessage id='account.activity' defaultMessage='Activity' />
|
||||
</NavLink>
|
||||
{show_media && (
|
||||
<NavLink exact to={`/@${acct}/media`}>
|
||||
<FormattedMessage id='account.media' defaultMessage='Media' />
|
||||
</NavLink>
|
||||
)}
|
||||
{show_featured && (
|
||||
<NavLink exact to={`/@${acct}/featured`}>
|
||||
<FormattedMessage id='account.featured' defaultMessage='Featured' />
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
ItemList,
|
||||
Scrollable,
|
||||
} from 'mastodon/components/scrollable_list/components';
|
||||
import { useSearchAccounts } from 'mastodon/features/lists/use_search_accounts';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useSearchAccounts } from 'mastodon/hooks/useSearchAccounts';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import {
|
||||
addCollectionItem,
|
||||
@@ -374,6 +374,7 @@ export const CollectionAccounts: React.FC<{
|
||||
onSelectItem={
|
||||
isEditMode ? instantToggleAccountItem : toggleAccountItem
|
||||
}
|
||||
closeOnSelect={false}
|
||||
/>
|
||||
{hasMaxAccounts && (
|
||||
<FormattedMessage
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Fragment, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@@ -6,9 +6,11 @@ import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { isFulfilled } from '@reduxjs/toolkit';
|
||||
|
||||
import { languages } from '@/mastodon/initial_state';
|
||||
import {
|
||||
hasSpecialCharacters,
|
||||
inputToHashtag,
|
||||
trimHashFromStart,
|
||||
} from '@/mastodon/utils/hashtags';
|
||||
import type {
|
||||
ApiCreateCollectionPayload,
|
||||
@@ -17,12 +19,16 @@ import type {
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import {
|
||||
CheckboxField,
|
||||
ComboboxField,
|
||||
Fieldset,
|
||||
FormStack,
|
||||
RadioButtonField,
|
||||
SelectField,
|
||||
TextAreaField,
|
||||
} from 'mastodon/components/form_fields';
|
||||
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
|
||||
import { useSearchTags } from 'mastodon/hooks/useSearchTags';
|
||||
import type { TagSearchResult } from 'mastodon/hooks/useSearchTags';
|
||||
import {
|
||||
createCollection,
|
||||
updateCollection,
|
||||
@@ -34,7 +40,6 @@ import classes from './styles.module.scss';
|
||||
import { WizardStepHeader } from './wizard_step_header';
|
||||
|
||||
export const CollectionDetails: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const { id, name, description, topic, discoverable, sensitive, accountIds } =
|
||||
@@ -64,18 +69,6 @@ export const CollectionDetails: React.FC = () => {
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleTopicChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'topic',
|
||||
value: inputToHashtag(event.target.value),
|
||||
}),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleDiscoverableChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(
|
||||
@@ -156,11 +149,6 @@ export const CollectionDetails: React.FC = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const topicHasSpecialCharacters = useMemo(
|
||||
() => hasSpecialCharacters(topic),
|
||||
[topic],
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={classes.form}>
|
||||
<FormStack className={classes.formFieldStack}>
|
||||
@@ -213,39 +201,9 @@ export const CollectionDetails: React.FC = () => {
|
||||
maxLength={100}
|
||||
/>
|
||||
|
||||
<TextInputField
|
||||
required={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='collections.collection_topic'
|
||||
defaultMessage='Topic'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='collections.topic_hint'
|
||||
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
|
||||
/>
|
||||
}
|
||||
value={topic}
|
||||
onChange={handleTopicChange}
|
||||
autoCapitalize='off'
|
||||
autoCorrect='off'
|
||||
spellCheck='false'
|
||||
maxLength={40}
|
||||
status={
|
||||
topicHasSpecialCharacters
|
||||
? {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage({
|
||||
id: 'collections.topic_special_chars_hint',
|
||||
defaultMessage:
|
||||
'Special characters will be removed when saving',
|
||||
}),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TopicField />
|
||||
|
||||
<LanguageField />
|
||||
|
||||
<Fieldset
|
||||
legend={
|
||||
@@ -335,3 +293,143 @@ export const CollectionDetails: React.FC = () => {
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const TopicField: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { id, topic } = useAppSelector((state) => state.collections.editor);
|
||||
|
||||
const collection = useAppSelector((state) =>
|
||||
id ? state.collections.collections[id] : undefined,
|
||||
);
|
||||
const [isInitialValue, setIsInitialValue] = useState(
|
||||
() => trimHashFromStart(topic) === (collection?.tag?.name ?? ''),
|
||||
);
|
||||
|
||||
const { tags, isLoading, searchTags } = useSearchTags({
|
||||
query: topic,
|
||||
});
|
||||
|
||||
const handleTopicChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsInitialValue(false);
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'topic',
|
||||
value: inputToHashtag(event.target.value),
|
||||
}),
|
||||
);
|
||||
searchTags(event.target.value);
|
||||
},
|
||||
[dispatch, searchTags],
|
||||
);
|
||||
|
||||
const handleSelectTopicSuggestion = useCallback(
|
||||
(item: TagSearchResult) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'topic',
|
||||
value: inputToHashtag(item.name),
|
||||
}),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const topicHasSpecialCharacters = useMemo(
|
||||
() => hasSpecialCharacters(topic),
|
||||
[topic],
|
||||
);
|
||||
|
||||
return (
|
||||
<ComboboxField
|
||||
required={false}
|
||||
icon={null}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='collections.collection_topic'
|
||||
defaultMessage='Topic'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='collections.topic_hint'
|
||||
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
|
||||
/>
|
||||
}
|
||||
value={topic}
|
||||
items={tags}
|
||||
isLoading={isLoading}
|
||||
renderItem={renderTagItem}
|
||||
onSelectItem={handleSelectTopicSuggestion}
|
||||
onChange={handleTopicChange}
|
||||
autoCapitalize='off'
|
||||
autoCorrect='off'
|
||||
spellCheck='false'
|
||||
maxLength={40}
|
||||
status={
|
||||
topicHasSpecialCharacters
|
||||
? {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage({
|
||||
id: 'collections.topic_special_chars_hint',
|
||||
defaultMessage:
|
||||
'Special characters will be removed when saving',
|
||||
}),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
suppressMenu={isInitialValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTagItem = (item: TagSearchResult) => item.label ?? `#${item.name}`;
|
||||
|
||||
const LanguageField: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const initialLanguage = useAppSelector(
|
||||
(state) => state.compose.get('default_language') as string,
|
||||
);
|
||||
const { language } = useAppSelector((state) => state.collections.editor);
|
||||
|
||||
const selectedLanguage = language ?? initialLanguage;
|
||||
|
||||
const handleLanguageChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'language',
|
||||
value: event.target.value,
|
||||
}),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='collections.collection_language'
|
||||
defaultMessage='Language'
|
||||
/>
|
||||
}
|
||||
value={selectedLanguage}
|
||||
onChange={handleLanguageChange}
|
||||
>
|
||||
<option value=''>
|
||||
<FormattedMessage
|
||||
id='collections.collection_language_none'
|
||||
defaultMessage='None'
|
||||
tagName={Fragment}
|
||||
/>
|
||||
</option>
|
||||
{languages?.map(([code, name, localName]) => (
|
||||
<option key={code} value={code}>
|
||||
{localName} ({name})
|
||||
</option>
|
||||
))}
|
||||
</SelectField>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,8 +21,7 @@ export const LinkTimeline: React.FC<{
|
||||
const columnRef = useRef<ColumnRef>(null);
|
||||
const firstStatusId = useAppSelector((state) =>
|
||||
decodedUrl
|
||||
? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
||||
(state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string)
|
||||
? (state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string)
|
||||
: undefined,
|
||||
);
|
||||
const story = useAppSelector((state) =>
|
||||
|
||||
@@ -28,11 +28,10 @@ import { DisplayName } from 'mastodon/components/display_name';
|
||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||
import { useSearchAccounts } from 'mastodon/hooks/useSearchAccounts';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { useSearchAccounts } from './use_search_accounts';
|
||||
|
||||
export const messages = defineMessages({
|
||||
manageMembers: {
|
||||
id: 'column.list_members',
|
||||
|
||||
@@ -23,7 +23,7 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
import { checkAnnualReport } from '@/mastodon/reducers/slices/annual_report';
|
||||
import { isClientFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
||||
import { clearHeight } from '../../actions/height_cache';
|
||||
@@ -182,7 +182,7 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
}
|
||||
|
||||
const profileRedesignRoutes = [];
|
||||
if (isClientFeatureEnabled('profile_editing')) {
|
||||
if (isServerFeatureEnabled('profile_redesign')) {
|
||||
profileRedesignRoutes.push(
|
||||
<WrappedRoute key="edit" path='/profile/edit' component={AccountEdit} content={children} />,
|
||||
<WrappedRoute key="featured_tags" path='/profile/featured_tags' component={AccountEditFeaturedTags} content={children} />
|
||||
|
||||
121
app/javascript/mastodon/hooks/useSearchTags.ts
Normal file
121
app/javascript/mastodon/hooks/useSearchTags.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { apiGetSearch } from 'mastodon/api/search';
|
||||
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
|
||||
import { trimHashFromStart } from 'mastodon/utils/hashtags';
|
||||
|
||||
export type TagSearchResult = Omit<ApiHashtagJSON, 'url' | 'history'> & {
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
addTag: {
|
||||
id: 'account_edit_tags.add_tag',
|
||||
defaultMessage: 'Add #{tagName}',
|
||||
},
|
||||
});
|
||||
|
||||
const fetchSearchHashtags = ({
|
||||
q,
|
||||
limit,
|
||||
signal,
|
||||
}: {
|
||||
q: string;
|
||||
limit: number;
|
||||
signal: AbortSignal;
|
||||
}) => apiGetSearch({ q, type: 'hashtags', limit }, { signal });
|
||||
|
||||
export function useSearchTags({
|
||||
query,
|
||||
limit = 11,
|
||||
filterResults,
|
||||
}: {
|
||||
query?: string;
|
||||
limit?: number;
|
||||
filterResults?: (account: ApiHashtagJSON) => boolean;
|
||||
} = {}) {
|
||||
const intl = useIntl();
|
||||
const [fetchedTags, setFetchedTags] = useState<ApiHashtagJSON[]>([]);
|
||||
const [loadingState, setLoadingState] = useState<
|
||||
'idle' | 'loading' | 'error'
|
||||
>('idle');
|
||||
|
||||
const searchRequestRef = useRef<AbortController | null>(null);
|
||||
|
||||
const searchTags = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
if (searchRequestRef.current) {
|
||||
searchRequestRef.current.abort();
|
||||
}
|
||||
|
||||
const trimmedQuery = trimHashFromStart(value.trim());
|
||||
|
||||
if (trimmedQuery.length === 0) {
|
||||
setFetchedTags([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingState('loading');
|
||||
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
void fetchSearchHashtags({
|
||||
q: trimmedQuery,
|
||||
limit,
|
||||
signal: searchRequestRef.current.signal,
|
||||
})
|
||||
.then(({ hashtags }) => {
|
||||
const tags = filterResults
|
||||
? hashtags.filter(filterResults)
|
||||
: hashtags;
|
||||
setFetchedTags(tags);
|
||||
setLoadingState('idle');
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingState('error');
|
||||
});
|
||||
},
|
||||
500,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
const resetSearch = useCallback(() => {
|
||||
setFetchedTags([]);
|
||||
setLoadingState('idle');
|
||||
}, []);
|
||||
|
||||
// Add dedicated item for adding the current query
|
||||
const tags = useMemo(() => {
|
||||
const trimmedQuery = query ? trimHashFromStart(query.trim()) : '';
|
||||
if (!trimmedQuery || !fetchedTags.length) {
|
||||
return fetchedTags;
|
||||
}
|
||||
|
||||
const results: TagSearchResult[] = [...fetchedTags]; // Make array mutable
|
||||
if (
|
||||
trimmedQuery.length > 0 &&
|
||||
results.every(
|
||||
(result) => result.name.toLowerCase() !== trimmedQuery.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
results.push({
|
||||
id: 'new',
|
||||
name: trimmedQuery,
|
||||
label: intl.formatMessage(messages.addTag, { tagName: trimmedQuery }),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}, [fetchedTags, query, intl]);
|
||||
|
||||
return {
|
||||
tags,
|
||||
searchTags,
|
||||
resetSearch,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
}
|
||||
@@ -153,7 +153,7 @@ export const languages = initialState?.languages.map((lang) => {
|
||||
lang[0],
|
||||
displayNames?.of(lang[0].replace('zh-YUE', 'yue')) ?? lang[1],
|
||||
lang[2],
|
||||
];
|
||||
] as InitialStateLanguage;
|
||||
});
|
||||
|
||||
export function getAccessToken(): string | undefined {
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"account.edit_profile_short": "Рэдагаваць",
|
||||
"account.enable_notifications": "Апавяшчаць мяне пра допісы @{name}",
|
||||
"account.endorse": "Паказваць у профілі",
|
||||
"account.familiar_followers_many": "Мае сярод падпісчыкаў {name1}, {name2}, і {othersCount, plural, one {яшчэ # чалавека, знаёмага Вам} few {яшчэ # чалавекі, знаёмыя Вам} many {яшчэ # чалавек, знаёмых Вам} other {яшчэ # чалавекі, знаёмыя Вам}}",
|
||||
"account.familiar_followers_many": "Мае сярод падпісчыкаў {name1}, {name2}, і {othersCount, plural, one {яшчэ #-го чалавека, знаёмага Вам} few {яшчэ #-х чалавек, знаёмых Вам} many {яшчэ # людзей, знаёмых Вам} other {яшчэ # чалавек, знаёмых Вам}}",
|
||||
"account.familiar_followers_one": "Мае сярод падпісчыкаў {name1}",
|
||||
"account.familiar_followers_two": "Мае сярод падпісчыкаў {name1} і {name2}",
|
||||
"account.featured": "Рэкамендаванае",
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Перайсці да профілю",
|
||||
"account.hide_reblogs": "Схаваць пашырэнні ад @{name}",
|
||||
"account.in_memoriam": "У памяць.",
|
||||
"account.joined_long": "Далучыў(-ла)ся {date}",
|
||||
"account.joined_short": "Далучыўся",
|
||||
"account.languages": "Змяніць выбраныя мовы",
|
||||
"account.link_verified_on": "Права ўласнасці на гэтую спасылку праверана {date}",
|
||||
@@ -151,13 +150,53 @@
|
||||
"account_edit.button.edit": "Змяніць {item}",
|
||||
"account_edit.column_button": "Гатова",
|
||||
"account_edit.column_title": "Рэдагаваць профіль",
|
||||
"account_edit.custom_fields.name": "поле",
|
||||
"account_edit.custom_fields.placeholder": "Дадайце свае займеннікі, знешнія спасылкі ці нешта іншае, чым Вы хацелі б падзяліцца.",
|
||||
"account_edit.custom_fields.title": "Карыстальніцкія палі",
|
||||
"account_edit.custom_fields.reorder_button": "Змяніць парадак палёў",
|
||||
"account_edit.custom_fields.tip_content": "Вы можаце лёгка дадаць даверу да свайго ўліковага запісу Mastodon пацвярджэннем спасылак на любы з Вашых сайтаў.",
|
||||
"account_edit.custom_fields.tip_title": "Падказка: Дадаванне пацверджаных спасылак",
|
||||
"account_edit.custom_fields.title": "Адвольныя палі",
|
||||
"account_edit.custom_fields.verified_hint": "Як мне дадаць пацверджаную спасылку?",
|
||||
"account_edit.display_name.placeholder": "Вашае бачнае імя — гэта імя, якое іншыя людзі бачаць у Вашым профілі і ў стужках.",
|
||||
"account_edit.display_name.title": "Бачнае імя",
|
||||
"account_edit.featured_hashtags.item": "хэштэгі",
|
||||
"account_edit.featured_hashtags.placeholder": "Дапамажыце іншым зразумець, якія тэмы Вас цікавяць, і атрымаць доступ да іх.",
|
||||
"account_edit.featured_hashtags.title": "Выбраныя хэштэгі",
|
||||
"account_edit.field_delete_modal.confirm": "Вы ўпэўненыя, што хочаце выдаліць гэтае адвольнае поле? Гэтае дзеянне будзе незваротным.",
|
||||
"account_edit.field_delete_modal.delete_button": "Выдаліць",
|
||||
"account_edit.field_delete_modal.title": "Выдаліць адвольнае поле?",
|
||||
"account_edit.field_edit_modal.add_title": "Дадаць адвольнае поле",
|
||||
"account_edit.field_edit_modal.edit_title": "Рэдагаваць адвольнае поле",
|
||||
"account_edit.field_edit_modal.limit_header": "Перавышаная рэкамендаваная колькасць сімвалаў",
|
||||
"account_edit.field_edit_modal.limit_message": "Карыстальнікі мабільных прылад могуць не ўбачыць Вашае поле цалкам.",
|
||||
"account_edit.field_edit_modal.link_emoji_warning": "Мы раім не ўжываць адвольныя эмодзі разам з url-спасылкамі. Адвольныя палі, якія ўтрымліваюць і тое, і другое, будуць адлюстраваныя выключна як тэкст, а не спасылкі, каб не блытаць карыстальнікаў.",
|
||||
"account_edit.field_edit_modal.name_hint": "Напрыклад, \"Асабісты Сайт\"",
|
||||
"account_edit.field_edit_modal.name_label": "Назва",
|
||||
"account_edit.field_edit_modal.url_warning": "Каб дадаць спасылку, калі ласка, дадайце {protocol} у яе пачатку.",
|
||||
"account_edit.field_edit_modal.value_hint": "Напрыклад, “https://example.me”",
|
||||
"account_edit.field_edit_modal.value_label": "Значэнне",
|
||||
"account_edit.field_reorder_modal.drag_cancel": "Перасоўванне адмененае. Поле {item} было павернутае на месца.",
|
||||
"account_edit.field_reorder_modal.drag_end": "Поле {item} было павернутае на месца.",
|
||||
"account_edit.field_reorder_modal.drag_instructions": "Каб змяніць парадак адвольных палёў, націсніце прабел або ўвод. Падчас перасоўвання карыстайцеся клавішамі са стрэлкамі, каб пасунуць поле ўверх ці ўніз. Націсніце прабел ці ўвод зноў, каб замацаваць поле на новым месцы, або націсніце Esc, каб скасаваць дзеянне.",
|
||||
"account_edit.field_reorder_modal.drag_move": "Поле {item} было перасунутае.",
|
||||
"account_edit.field_reorder_modal.drag_over": "Поле {item} было перасунутае над \"{over}\".",
|
||||
"account_edit.field_reorder_modal.drag_start": "Абранае поле \"{item}\".",
|
||||
"account_edit.field_reorder_modal.handle_label": "Перасунуць поле \"{item}\"",
|
||||
"account_edit.field_reorder_modal.title": "Перасунуць палі",
|
||||
"account_edit.image_alt_modal.add_title": "Дадаць альт. тэкст",
|
||||
"account_edit.image_alt_modal.details_content": "ЯК РАБІЦЬ: <ul> <li>Апішыце, як Вы выглядаеце на відарысе</li> <li>Апісвайце ад трэцяй асобы (напрыклад, “Алесь” замест \"я”)</li> <li>Будзьце сціслымі — некалькі слоў звычайна дастаткова</li> </ul> ЯК НЕ РАБІЦЬ: <ul> <li>Пачынаць з \"Фотаздымак...\" — гэта залішняе для чытачоў</li> </ul> ПРЫКЛАД: <ul> <li>“Алесь у вышыванцы і акулярах”</li> </ul>",
|
||||
"account_edit.image_alt_modal.details_title": "Падказка: Альтэрнатыўны тэкст для фота профілю",
|
||||
"account_edit.image_alt_modal.edit_title": "Рэдагаваць альт. тэкст",
|
||||
"account_edit.image_alt_modal.text_hint": "Альтэрнатыўны тэкст дапамагае чытачам Вашага кантэнту лепш яго разумець.",
|
||||
"account_edit.image_alt_modal.text_label": "Альт. тэкст",
|
||||
"account_edit.image_delete_modal.confirm": "Вы ўпэўненыя, што хочаце выдаліць гэты відарыс? Гэтае дзеянне будзе незваротным.",
|
||||
"account_edit.image_delete_modal.delete_button": "Выдаліць",
|
||||
"account_edit.image_delete_modal.title": "Выдаліць відарыс?",
|
||||
"account_edit.image_edit.add_button": "Дадаць відарыс",
|
||||
"account_edit.image_edit.alt_add_button": "Дадаць альт. тэкст",
|
||||
"account_edit.image_edit.alt_edit_button": "Рэдагаваць альт. тэкст",
|
||||
"account_edit.image_edit.remove_button": "Прыбраць відарыс",
|
||||
"account_edit.image_edit.replace_button": "Замяніць відарыс",
|
||||
"account_edit.name_modal.add_title": "Дадаць бачнае імя",
|
||||
"account_edit.name_modal.edit_title": "Змяніць бачнае імя",
|
||||
"account_edit.profile_tab.button_label": "Змяніць",
|
||||
@@ -172,6 +211,23 @@
|
||||
"account_edit.profile_tab.subtitle": "Змяняйце на свой густ укладкі свайго профілю і тое, што яны паказваюць.",
|
||||
"account_edit.profile_tab.title": "Налады ўкладкі профілю",
|
||||
"account_edit.save": "Захаваць",
|
||||
"account_edit.upload_modal.back": "Назад",
|
||||
"account_edit.upload_modal.done": "Гатова",
|
||||
"account_edit.upload_modal.next": "Далей",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Маштаб",
|
||||
"account_edit.upload_modal.step_upload.button": "Агляд файлаў",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Перацягн. сюды, каб запамп.",
|
||||
"account_edit.upload_modal.step_upload.header": "Выбраць відарыс",
|
||||
"account_edit.upload_modal.step_upload.hint": "Фармату WEBP, PNG, GIF або JPG, да {limit} МБ.{br}Відарыс будзе сціснуты да памеру {width}x{height} пікселяў.",
|
||||
"account_edit.upload_modal.title_add": "Дадаць фота профілю",
|
||||
"account_edit.upload_modal.title_replace": "Замяніць фота профілю",
|
||||
"account_edit.verified_modal.details": "Дадайце даверу да Вашага профілю Mastodon, пацвярджэннем спасылак на ўласныя сайты. Вось як гэта працуе:",
|
||||
"account_edit.verified_modal.invisible_link.details": "Дадайце спасылку ў свой загаловак. Важнай часткай з'яўляецца rel=\"me\", яна прадухіляе выдачу сябе за іншую асобу на сайтах з карыстальніцкім кантэнтам. Вы нават можаце выкарыстоўваць тэг link у загалоўку старонкі замест {tag}, але HTML код павінен быць даступным без выканання JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Як мне зрабіць спасылку нябачнай?",
|
||||
"account_edit.verified_modal.step1.header": "Скапіруйце HTML код знізу і ўстаўце яго ў загаловак свайго сайту",
|
||||
"account_edit.verified_modal.step2.details": "Калі Вы ўжо дадалі свой сайт у якасці адвольнага поля, Вам спатрэбіцца яго выдаліць і зноў дадаць, каб запусціць верыфікацыю.",
|
||||
"account_edit.verified_modal.step2.header": "Дадаць свой сайт як адвольнае поле",
|
||||
"account_edit.verified_modal.title": "Як дадаць пацверджаную спасылку",
|
||||
"account_edit_tags.add_tag": "Дадаць #{tagName}",
|
||||
"account_edit_tags.column_title": "Змяніць выбраныя хэштэгі",
|
||||
"account_edit_tags.help_text": "Выбраныя хэштэгі дапамагаюць карыстальнікам знаходзіць Ваш профіль і ўзаемадзейнічаць з ім. Яны дзейнічаюць як фільтры пры праглядзе актыўнасці на Вашай старонцы.",
|
||||
@@ -306,10 +362,15 @@
|
||||
"collections.create_collection": "Стварыць калекцыю",
|
||||
"collections.delete_collection": "Выдаліць калекцыю",
|
||||
"collections.description_length_hint": "Максімум 100 сімвалаў",
|
||||
"collections.detail.accept_inclusion": "Добра",
|
||||
"collections.detail.accounts_heading": "Уліковыя запісы",
|
||||
"collections.detail.author_added_you": "{author} дадаў(-ла) Вас у гэтую калекцыю",
|
||||
"collections.detail.curated_by_author": "Курыруе {author}",
|
||||
"collections.detail.curated_by_you": "Курыруеце Вы",
|
||||
"collections.detail.loading": "Загружаецца калекцыя…",
|
||||
"collections.detail.other_accounts_in_collection": "Іншыя ў гэтай калекцыі:",
|
||||
"collections.detail.revoke_inclusion": "Прыбраць сябе",
|
||||
"collections.detail.sensitive_note": "У гэтай калекцыі прысутнічаюць уліковыя запісы і кантэнт, змесціва якіх можа падацца адчувальным для некаторых карыстальнікаў.",
|
||||
"collections.detail.share": "Падзяліцца гэтай калекцыяй",
|
||||
"collections.edit_details": "Рэдагаваць падрабязнасці",
|
||||
"collections.error_loading_collections": "Адбылася памылка падчас загрузкі Вашых калекцый.",
|
||||
@@ -324,10 +385,14 @@
|
||||
"collections.old_last_post_note": "Апошні допіс быў больш за тыдзень таму",
|
||||
"collections.remove_account": "Прыбраць гэты ўліковы запіс",
|
||||
"collections.report_collection": "Паскардзіцца на гэту калекцыю",
|
||||
"collections.revoke_collection_inclusion": "Прыбраць сябе з гэтай калекцыі",
|
||||
"collections.revoke_inclusion.confirmation": "Вас прыбралі з \"{collection}\"",
|
||||
"collections.revoke_inclusion.error": "Адбылася памылка, калі ласка, спрабуйце яшчэ раз пазней.",
|
||||
"collections.search_accounts_label": "Шукайце ўліковыя запісы, каб дадаць іх сюды…",
|
||||
"collections.search_accounts_max_reached": "Вы дадалі максімальную колькасць уліковых запісаў",
|
||||
"collections.sensitive": "Адчувальная",
|
||||
"collections.topic_hint": "Дадайце хэштэг, які дапаможа іншым зразумець галоўную тэму гэтай калекцыі.",
|
||||
"collections.topic_special_chars_hint": "Спецыяльныя сімвалы будуць прыбраныя пры захаванні",
|
||||
"collections.view_collection": "Глядзець калекцыю",
|
||||
"collections.view_other_collections_by_user": "Паглядзець іншыя калекцыі гэтага карыстальніка",
|
||||
"collections.visibility_public": "Публічная",
|
||||
@@ -447,6 +512,9 @@
|
||||
"confirmations.remove_from_followers.confirm": "Выдаліць падпісчыка",
|
||||
"confirmations.remove_from_followers.message": "{name} больш не будзе падпісаны(-ая) на Вас. Упэўненыя, што хочаце працягнуць?",
|
||||
"confirmations.remove_from_followers.title": "Выдаліць падпісчыка?",
|
||||
"confirmations.revoke_collection_inclusion.confirm": "Прыбраць сябе",
|
||||
"confirmations.revoke_collection_inclusion.message": "Гэтае дзеянне канчатковае, і куратар не зможа пасля зноў дадаць Вас у гэтую калекцыю.",
|
||||
"confirmations.revoke_collection_inclusion.title": "Прыбраць Вас з гэтай калекцыі?",
|
||||
"confirmations.revoke_quote.confirm": "Выдаліць допіс",
|
||||
"confirmations.revoke_quote.message": "Гэтае дзеянне немагчыма адмяніць.",
|
||||
"confirmations.revoke_quote.title": "Выдаліць допіс?",
|
||||
@@ -769,6 +837,7 @@
|
||||
"navigation_bar.automated_deletion": "Аўтаматычнае выдаленне допісаў",
|
||||
"navigation_bar.blocks": "Заблакіраваныя карыстальнікі",
|
||||
"navigation_bar.bookmarks": "Закладкі",
|
||||
"navigation_bar.collections": "Калекцыі",
|
||||
"navigation_bar.direct": "Прыватныя згадванні",
|
||||
"navigation_bar.domain_blocks": "Заблакіраваныя дамены",
|
||||
"navigation_bar.favourites": "Упадабанае",
|
||||
@@ -916,12 +985,14 @@
|
||||
"notifications_permission_banner.title": "Не прапусціце нічога",
|
||||
"onboarding.follows.back": "Назад",
|
||||
"onboarding.follows.empty": "На жаль, зараз немагчыма паказаць вынікі. Вы можаце паспрабаваць выкарыстоўваць пошук і праглядзець старонку агляду, каб знайсці людзей, на якіх можна падпісацца, або паўтарыць спробу пазней.",
|
||||
"onboarding.follows.next": "Далей: Наладзьце свой профіль",
|
||||
"onboarding.follows.search": "Пошук",
|
||||
"onboarding.follows.title": "Падпішыцеся на некага, каб пачаць",
|
||||
"onboarding.profile.discoverable": "Зрабіць мой профіль бачным",
|
||||
"onboarding.profile.discoverable_hint": "Калі Вы звяртаецеся да адкрытасці на Mastodon, Вашы допісы могуць з'яўляцца ў выніках пошуку і трэндах, а Ваш профіль можа быць прапанаваны людзям з такімі ж інтарэсамі.",
|
||||
"onboarding.profile.display_name": "Бачнае імя",
|
||||
"onboarding.profile.display_name_hint": "Ваша поўнае імя або ваш псеўданім…",
|
||||
"onboarding.profile.finish": "Гатова",
|
||||
"onboarding.profile.note": "Біяграфія",
|
||||
"onboarding.profile.note_hint": "Вы можаце @згадваць іншых людзей або выкарыстоўваць #хэштэгі…",
|
||||
"onboarding.profile.title": "Налады профілю",
|
||||
|
||||
@@ -70,7 +70,6 @@
|
||||
"account.go_to_profile": "Vés al perfil",
|
||||
"account.hide_reblogs": "Amaga els impulsos de @{name}",
|
||||
"account.in_memoriam": "En Memòria.",
|
||||
"account.joined_long": "Membre des de {date}",
|
||||
"account.joined_short": "S'hi va unir",
|
||||
"account.languages": "Canvia les llengües subscrites",
|
||||
"account.link_verified_on": "La propietat d'aquest enllaç es va verificar el dia {date}",
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"account.go_to_profile": "Přejít na profil",
|
||||
"account.hide_reblogs": "Skrýt boosty od @{name}",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
"account.joined_long": "Přidali se {date}",
|
||||
"account.joined_short": "Připojen/a",
|
||||
"account.languages": "Změnit odebírané jazyky",
|
||||
"account.link_verified_on": "Vlastnictví tohoto odkazu bylo zkontrolováno {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Mynd i'r proffil",
|
||||
"account.hide_reblogs": "Cuddio hybiau gan @{name}",
|
||||
"account.in_memoriam": "Er Cof.",
|
||||
"account.joined_long": "Ymunodd ar {date}",
|
||||
"account.joined_short": "Ymunodd",
|
||||
"account.languages": "Newid ieithoedd wedi tanysgrifio iddyn nhw",
|
||||
"account.link_verified_on": "Gwiriwyd perchnogaeth y ddolen yma ar {date}",
|
||||
@@ -183,6 +182,15 @@
|
||||
"account_edit.field_reorder_modal.drag_start": "Wedi codi maes \"{item}\".",
|
||||
"account_edit.field_reorder_modal.handle_label": "Llusgo'r maes \"{item}\"",
|
||||
"account_edit.field_reorder_modal.title": "Aildrefnu meysydd",
|
||||
"account_edit.image_alt_modal.add_title": "Ychwanegu testun amgen",
|
||||
"account_edit.image_alt_modal.details_content": "GWNEUD:<ul><li> Disgrifio'ch hun fel yn eich llun</li><li> Defnyddio iaith trydydd person (e.e. “Siôn” yn lle “fi”)</li><li> Bod yn gryno – mae ychydig o eiriau’n aml yn ddigon</li></ul> PEIDIO:<ul><li> Dechrau gyda “Llun o” – mae’n ddiangen ar gyfer darllenwyr sgrin</li></ul> ENGHRAIFFT:<ul><li> “Dyma Siôn yn gwisgo crys gwyrdd a sbectol”</li></ul>",
|
||||
"account_edit.image_alt_modal.details_title": "Awgrymiadau: Testun amgen ar gyfer lluniau proffil",
|
||||
"account_edit.image_alt_modal.edit_title": "Golygu testun amgen",
|
||||
"account_edit.image_alt_modal.text_hint": "Mae testun amgen yn helpu defnyddwyr darllenwyr sgrin i ddeall eich cynnwys.",
|
||||
"account_edit.image_alt_modal.text_label": "Testun amgen",
|
||||
"account_edit.image_delete_modal.confirm": "Ydych chi'n siŵr eich bod chi eisiau dileu'r ddelwedd hon? Does dim modd dadwneud hynny.",
|
||||
"account_edit.image_delete_modal.delete_button": "Dileu",
|
||||
"account_edit.image_delete_modal.title": "Dileu delwedd?",
|
||||
"account_edit.image_edit.add_button": "Ychwanegu Delwedd",
|
||||
"account_edit.image_edit.alt_add_button": "Ychwanegu testun amgen",
|
||||
"account_edit.image_edit.alt_edit_button": "Golygu testun amgen",
|
||||
@@ -202,6 +210,16 @@
|
||||
"account_edit.profile_tab.subtitle": "Cyfaddaswch y tabiau ar eich proffil a'r hyn maen nhw'n ei ddangos.",
|
||||
"account_edit.profile_tab.title": "Gosodiadau tab proffil",
|
||||
"account_edit.save": "Cadw",
|
||||
"account_edit.upload_modal.back": "Nôl",
|
||||
"account_edit.upload_modal.done": "Gorffen",
|
||||
"account_edit.upload_modal.next": "Nesaf",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Chwyddo",
|
||||
"account_edit.upload_modal.step_upload.button": "Pori ffeiliau",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Gollwng i lwytho i fyny",
|
||||
"account_edit.upload_modal.step_upload.header": "Dewiswch ddelwedd",
|
||||
"account_edit.upload_modal.step_upload.hint": "Fformat WEBP, PNG, GIF neu JPG, hyd at {limit}MB.{br}Bydd y ddelwedd yn cael ei haddasu i {width}x{height}px.",
|
||||
"account_edit.upload_modal.title_add": "Ychwanegu llun proffil",
|
||||
"account_edit.upload_modal.title_replace": "Amnewid llun proffil",
|
||||
"account_edit.verified_modal.details": "Ychwanegwch hygrededd at eich proffil Mastodon trwy wirio dolenni i wefannau personol. Dyma sut mae'n gweithio:",
|
||||
"account_edit.verified_modal.invisible_link.details": "Ychwanegwch y ddolen at eich pennyn. Y rhan bwysig yw rel=\"me\" sy'n atal dynwared ar wefannau gyda chynnwys sy'n cael ei gynhyrchu gan ddefnyddwyr. Gallwch hyd yn oed ddefnyddio tag dolen ym mhennyn y dudalen yn lle {tag}, ond rhaid bod yr HTML yn hygyrch ac heb weithredu JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Sut ydw i'n gwneud y ddolen yn anweledig?",
|
||||
@@ -373,6 +391,7 @@
|
||||
"collections.search_accounts_max_reached": "Rydych chi wedi ychwanegu'r nifer mwyaf o gyfrifon",
|
||||
"collections.sensitive": "Sensitif",
|
||||
"collections.topic_hint": "Ychwanegwch hashnod sy'n helpu eraill i ddeall prif bwnc y casgliad hwn.",
|
||||
"collections.topic_special_chars_hint": "Bydd nodau arbennig yn cael eu tynnu wrth gadw",
|
||||
"collections.view_collection": "Gweld y casgliad",
|
||||
"collections.view_other_collections_by_user": "Edrychwch ar gasgliadau eraill gan y defnyddiwr hwn",
|
||||
"collections.visibility_public": "Cyhoeddus",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Gå til profil",
|
||||
"account.hide_reblogs": "Skjul fremhævelser fra @{name}",
|
||||
"account.in_memoriam": "Til minde om.",
|
||||
"account.joined_long": "Tilmeldt {date}",
|
||||
"account.joined_short": "Oprettet",
|
||||
"account.languages": "Skift abonnementssprog",
|
||||
"account.link_verified_on": "Ejerskab af dette link blev tjekket {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Profil aufrufen",
|
||||
"account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden",
|
||||
"account.in_memoriam": "Zum Andenken.",
|
||||
"account.joined_long": "Registriert am {date}",
|
||||
"account.joined_short": "Registriert am",
|
||||
"account.languages": "Sprachen verwalten",
|
||||
"account.link_verified_on": "Das Profil mit dieser E-Mail-Adresse wurde bereits am {date} bestätigt",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Μετάβαση στο προφίλ",
|
||||
"account.hide_reblogs": "Απόκρυψη ενισχύσεων από @{name}",
|
||||
"account.in_memoriam": "Εις μνήμην.",
|
||||
"account.joined_long": "Έγινε μέλος {date}",
|
||||
"account.joined_short": "Έγινε μέλος",
|
||||
"account.languages": "Αλλαγή εγγεγραμμένων γλωσσών",
|
||||
"account.link_verified_on": "Η ιδιοκτησία αυτού του συνδέσμου ελέγχθηκε στις {date}",
|
||||
@@ -214,6 +213,7 @@
|
||||
"account_edit.save": "Αποθήκευση",
|
||||
"account_edit.upload_modal.back": "Πίσω",
|
||||
"account_edit.upload_modal.done": "Έγινε",
|
||||
"account_edit.upload_modal.next": "Επόμενο",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Μεγέθυνση",
|
||||
"account_edit.upload_modal.step_upload.button": "Περιήγηση αρχείων",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Αποθέστε εδώ για ανέβασμα",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Go to profile",
|
||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
"account.joined_long": "Joined on {date}",
|
||||
"account.joined_short": "Joined",
|
||||
"account.languages": "Change subscribed languages",
|
||||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||
|
||||
@@ -349,6 +349,8 @@
|
||||
"collections.accounts.empty_description": "Add up to {count} accounts you follow",
|
||||
"collections.accounts.empty_title": "This collection is empty",
|
||||
"collections.collection_description": "Description",
|
||||
"collections.collection_language": "Language",
|
||||
"collections.collection_language_none": "None",
|
||||
"collections.collection_name": "Name",
|
||||
"collections.collection_topic": "Topic",
|
||||
"collections.confirm_account_removal": "Are you sure you want to remove this account from this collection?",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ir al perfil",
|
||||
"account.hide_reblogs": "Ocultar adhesiones de @{name}",
|
||||
"account.in_memoriam": "Cuenta conmemorativa.",
|
||||
"account.joined_long": "En este servidor desde el {date}",
|
||||
"account.joined_short": "En este servidor desde el",
|
||||
"account.languages": "Cambiar idiomas suscritos",
|
||||
"account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ir al perfil",
|
||||
"account.hide_reblogs": "Ocultar impulsos de @{name}",
|
||||
"account.in_memoriam": "En memoria.",
|
||||
"account.joined_long": "Se unió el {date}",
|
||||
"account.joined_short": "Se unió",
|
||||
"account.languages": "Cambiar idiomas suscritos",
|
||||
"account.link_verified_on": "Se verificó la propiedad de este enlace el {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ir al perfil",
|
||||
"account.hide_reblogs": "Ocultar impulsos de @{name}",
|
||||
"account.in_memoriam": "Cuenta conmemorativa.",
|
||||
"account.joined_long": "Se unió el {date}",
|
||||
"account.joined_short": "Se unió",
|
||||
"account.languages": "Cambiar idiomas suscritos",
|
||||
"account.link_verified_on": "La propiedad de este enlace fue verificada el {date}",
|
||||
@@ -184,6 +183,15 @@
|
||||
"account_edit.field_reorder_modal.drag_start": "Campo \"{item}\" seleccionado.",
|
||||
"account_edit.field_reorder_modal.handle_label": "Arrastra el campo \"{item}\"",
|
||||
"account_edit.field_reorder_modal.title": "Reorganizar campos",
|
||||
"account_edit.image_alt_modal.add_title": "Añadir texto alternativo",
|
||||
"account_edit.image_alt_modal.details_content": "QUE HACER: <ul> <li>Descríbete tal y como apareces en la imagen</li> <li>Exprésate en tercera persona (p. ej. “Alex” en lugar de “yo”)</li> <li>Sé breve, unas pocas palabras son a menudo suficientes</li> </ul> QUE NO HACER: <ul> <li>Comenzar con “Foto de” – es redundante para lectores de pantalla</li> </ul> EJEMPLO: <ul> <li>“Alex visitiendo una camisa verde y gafas”</li> </ul>",
|
||||
"account_edit.image_alt_modal.details_title": "Consejo: Texto alternativo para fotos de perfil",
|
||||
"account_edit.image_alt_modal.edit_title": "Editar texto alternativo",
|
||||
"account_edit.image_alt_modal.text_hint": "El texto alternativo ayuda a los usuarios de lectores de pantalla a entender tu contenido.",
|
||||
"account_edit.image_alt_modal.text_label": "Texto alternativo",
|
||||
"account_edit.image_delete_modal.confirm": "¿Estás seguro de que deseas eliminar esta imagen? Esta acción no se puede deshacer.",
|
||||
"account_edit.image_delete_modal.delete_button": "Eliminar",
|
||||
"account_edit.image_delete_modal.title": "¿Eliminar imagen?",
|
||||
"account_edit.image_edit.add_button": "Añadir imagen",
|
||||
"account_edit.image_edit.alt_add_button": "Añadir texto alternativo",
|
||||
"account_edit.image_edit.alt_edit_button": "Editar texto alternativo",
|
||||
@@ -203,6 +211,16 @@
|
||||
"account_edit.profile_tab.subtitle": "Personaliza las pestañas de tu perfil y lo que muestran.",
|
||||
"account_edit.profile_tab.title": "Configuración de la pestaña de perfil",
|
||||
"account_edit.save": "Guardar",
|
||||
"account_edit.upload_modal.back": "Atrás",
|
||||
"account_edit.upload_modal.done": "Hecho",
|
||||
"account_edit.upload_modal.next": "Siguiente",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Zoom",
|
||||
"account_edit.upload_modal.step_upload.button": "Explorar archivos",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Suelta para subir",
|
||||
"account_edit.upload_modal.step_upload.header": "Elige una imagen",
|
||||
"account_edit.upload_modal.step_upload.hint": "Formato WEBP, PNG, GIF o JPG, hasta {limit}MB.{br}La imagen será escalada a {width}x{height}px.",
|
||||
"account_edit.upload_modal.title_add": "Añadir foto de perfil",
|
||||
"account_edit.upload_modal.title_replace": "Reemplazar foto de perfil",
|
||||
"account_edit.verified_modal.details": "Añade credibilidad a tu perfil de Mastodon verificando enlaces a tus webs personales. Así es como funciona:",
|
||||
"account_edit.verified_modal.invisible_link.details": "Añade el enlace en el encabezado. La parte importante es rel=\"me\", que evita la suplantación de identidad en webs con contenido generado por usuarios. Incluso puedes utilizar un enlace con etiqueta en el encabezado de la página en vez de {tag}, pero el HTML debe ser accesible sin ejecutar JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "¿Cómo puedo hacer el enlace invisible?",
|
||||
@@ -368,7 +386,7 @@
|
||||
"collections.remove_account": "Quitar esta cuenta",
|
||||
"collections.report_collection": "Informar de esta colección",
|
||||
"collections.revoke_collection_inclusion": "Sácame de esta colección",
|
||||
"collections.revoke_inclusion.confirmation": "Has salido de la \"{collection}\"",
|
||||
"collections.revoke_inclusion.confirmation": "Has salido de \"{collection}\"",
|
||||
"collections.revoke_inclusion.error": "Se ha producido un error, inténtalo de nuevo más tarde.",
|
||||
"collections.search_accounts_label": "Buscar cuentas para añadir…",
|
||||
"collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Vaata profiili",
|
||||
"account.hide_reblogs": "Peida @{name} jagamised",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
"account.joined_long": "Liitus {date}",
|
||||
"account.joined_short": "Liitus",
|
||||
"account.languages": "Muuda tellitud keeli",
|
||||
"account.link_verified_on": "Selle lingi autorsust kontrolliti {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Siirry profiiliin",
|
||||
"account.hide_reblogs": "Piilota käyttäjän @{name} tehostukset",
|
||||
"account.in_memoriam": "Muistoissamme.",
|
||||
"account.joined_long": "Liittynyt {date}",
|
||||
"account.joined_short": "Liittynyt",
|
||||
"account.languages": "Vaihda tilattuja kieliä",
|
||||
"account.link_verified_on": "Linkin omistus tarkistettiin {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Far til vanga",
|
||||
"account.hide_reblogs": "Fjal stimbran frá @{name}",
|
||||
"account.in_memoriam": "In memoriam.",
|
||||
"account.joined_long": "Meldaði til {date}",
|
||||
"account.joined_short": "Gjørdist limur",
|
||||
"account.languages": "Broyt fylgd mál",
|
||||
"account.link_verified_on": "Ognarskapur av hesum leinki var eftirkannaður {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Voir ce profil",
|
||||
"account.hide_reblogs": "Masquer les boosts de @{name}",
|
||||
"account.in_memoriam": "En souvenir de",
|
||||
"account.joined_long": "Ici depuis le {date}",
|
||||
"account.joined_short": "Inscrit·e",
|
||||
"account.languages": "Changer les langues abonnées",
|
||||
"account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}",
|
||||
@@ -185,6 +184,14 @@
|
||||
"account_edit.field_reorder_modal.handle_label": "Faites glisser le champ « {item} »",
|
||||
"account_edit.field_reorder_modal.title": "Réorganiser les champs",
|
||||
"account_edit.image_alt_modal.add_title": "Ajouter un texte alternatif",
|
||||
"account_edit.image_alt_modal.details_content": "À faire : <ul> <li>Se décrire comme vous apparaissez sur la photo</li> <li>Utiliser la troisième personne (par exemple « Alex » au lieu de « moi »)</li> <li>Être succinct·e – quelques mot suffisent souvent</li> </ul> À éviter : <ul> <li>Commencer par « Une photo de » – c'est redondant pour les lecteurs d'écran</li> </ul> Example : <ul> <li>« Alex portant une chemise vert et des lunettes »</li> </ul>",
|
||||
"account_edit.image_alt_modal.details_title": "Astuces : texte alternatif pour les photos de profil",
|
||||
"account_edit.image_alt_modal.edit_title": "Modifier le texte alternatif",
|
||||
"account_edit.image_alt_modal.text_hint": "Le texte alternatif aide les personnes utilisant un lecteur d'écran à comprendre votre contenu.",
|
||||
"account_edit.image_alt_modal.text_label": "Texte alternatif",
|
||||
"account_edit.image_delete_modal.confirm": "Voulez-vous vraiment supprimer cette image ? Cette action est irréversible.",
|
||||
"account_edit.image_delete_modal.delete_button": "Supprimer",
|
||||
"account_edit.image_delete_modal.title": "Supprimer l'image ?",
|
||||
"account_edit.image_edit.add_button": "Ajouter une image",
|
||||
"account_edit.image_edit.alt_add_button": "Ajouter un texte alternatif",
|
||||
"account_edit.image_edit.alt_edit_button": "Modifier le texte alternatif",
|
||||
@@ -206,6 +213,14 @@
|
||||
"account_edit.save": "Enregistrer",
|
||||
"account_edit.upload_modal.back": "Retour",
|
||||
"account_edit.upload_modal.done": "Terminé",
|
||||
"account_edit.upload_modal.next": "Suivant",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Agrandir",
|
||||
"account_edit.upload_modal.step_upload.button": "Parcourir les fichiers",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Déposer pour téléverser",
|
||||
"account_edit.upload_modal.step_upload.header": "Choisir une image",
|
||||
"account_edit.upload_modal.step_upload.hint": "Format WebP, PNG, GIF ou JPEG, jusqu'à {limit} Mo.{br}L'image sera redimensionnée à {width} × {height} px.",
|
||||
"account_edit.upload_modal.title_add": "Ajouter une photo de profil",
|
||||
"account_edit.upload_modal.title_replace": "Remplacer la photo de profil",
|
||||
"account_edit.verified_modal.details": "Ajouter de la crédibilité à votre profil Mastodon en vérifiant les liens vers vos sites Web personnels. Voici comment cela fonctionne :",
|
||||
"account_edit.verified_modal.invisible_link.details": "Ajouter le lien dans votre en-tête. La partie importante est « rel=\"me\" » qui empêche l'usurpation d'identité sur des sites Web ayant du contenu généré par d'autres utilisateur·rice·s. Vous pouvez aussi utiliser une balise link dans l'en-tête de la page au lieu de {tag}, mais le code HTML doit être accessible sans avoir besoin d'exécuter du JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Comment rendre le lien invisible ?",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Voir le profil",
|
||||
"account.hide_reblogs": "Masquer les partages de @{name}",
|
||||
"account.in_memoriam": "En mémoire.",
|
||||
"account.joined_long": "Ici depuis le {date}",
|
||||
"account.joined_short": "Ici depuis",
|
||||
"account.languages": "Modifier les langues d'abonnements",
|
||||
"account.link_verified_on": "La propriété de ce lien a été vérifiée le {date}",
|
||||
@@ -185,6 +184,14 @@
|
||||
"account_edit.field_reorder_modal.handle_label": "Faites glisser le champ « {item} »",
|
||||
"account_edit.field_reorder_modal.title": "Réorganiser les champs",
|
||||
"account_edit.image_alt_modal.add_title": "Ajouter un texte alternatif",
|
||||
"account_edit.image_alt_modal.details_content": "À faire : <ul> <li>Se décrire comme vous apparaissez sur la photo</li> <li>Utiliser la troisième personne (par exemple « Alex » au lieu de « moi »)</li> <li>Être succinct·e – quelques mot suffisent souvent</li> </ul> À éviter : <ul> <li>Commencer par « Une photo de » – c'est redondant pour les lecteurs d'écran</li> </ul> Example : <ul> <li>« Alex portant une chemise vert et des lunettes »</li> </ul>",
|
||||
"account_edit.image_alt_modal.details_title": "Astuces : texte alternatif pour les photos de profil",
|
||||
"account_edit.image_alt_modal.edit_title": "Modifier le texte alternatif",
|
||||
"account_edit.image_alt_modal.text_hint": "Le texte alternatif aide les personnes utilisant un lecteur d'écran à comprendre votre contenu.",
|
||||
"account_edit.image_alt_modal.text_label": "Texte alternatif",
|
||||
"account_edit.image_delete_modal.confirm": "Voulez-vous vraiment supprimer cette image ? Cette action est irréversible.",
|
||||
"account_edit.image_delete_modal.delete_button": "Supprimer",
|
||||
"account_edit.image_delete_modal.title": "Supprimer l'image ?",
|
||||
"account_edit.image_edit.add_button": "Ajouter une image",
|
||||
"account_edit.image_edit.alt_add_button": "Ajouter un texte alternatif",
|
||||
"account_edit.image_edit.alt_edit_button": "Modifier le texte alternatif",
|
||||
@@ -206,6 +213,14 @@
|
||||
"account_edit.save": "Enregistrer",
|
||||
"account_edit.upload_modal.back": "Retour",
|
||||
"account_edit.upload_modal.done": "Terminé",
|
||||
"account_edit.upload_modal.next": "Suivant",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Agrandir",
|
||||
"account_edit.upload_modal.step_upload.button": "Parcourir les fichiers",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Déposer pour téléverser",
|
||||
"account_edit.upload_modal.step_upload.header": "Choisir une image",
|
||||
"account_edit.upload_modal.step_upload.hint": "Format WebP, PNG, GIF ou JPEG, jusqu'à {limit} Mo.{br}L'image sera redimensionnée à {width} × {height} px.",
|
||||
"account_edit.upload_modal.title_add": "Ajouter une photo de profil",
|
||||
"account_edit.upload_modal.title_replace": "Remplacer la photo de profil",
|
||||
"account_edit.verified_modal.details": "Ajouter de la crédibilité à votre profil Mastodon en vérifiant les liens vers vos sites Web personnels. Voici comment cela fonctionne :",
|
||||
"account_edit.verified_modal.invisible_link.details": "Ajouter le lien dans votre en-tête. La partie importante est « rel=\"me\" » qui empêche l'usurpation d'identité sur des sites Web ayant du contenu généré par d'autres utilisateur·rice·s. Vous pouvez aussi utiliser une balise link dans l'en-tête de la page au lieu de {tag}, mais le code HTML doit être accessible sans avoir besoin d'exécuter du JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Comment rendre le lien invisible ?",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Téigh go dtí próifíl",
|
||||
"account.hide_reblogs": "Folaigh moltaí ó @{name}",
|
||||
"account.in_memoriam": "Ón tseanaimsir.",
|
||||
"account.joined_long": "Chuaigh isteach ar {date}",
|
||||
"account.joined_short": "Cláraithe",
|
||||
"account.languages": "Athraigh teangacha foscríofa",
|
||||
"account.link_verified_on": "Seiceáladh úinéireacht an naisc seo ar {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ir ao perfil",
|
||||
"account.hide_reblogs": "Agochar promocións de @{name}",
|
||||
"account.in_memoriam": "Lembranzas.",
|
||||
"account.joined_long": "Uníuse o {date}",
|
||||
"account.joined_short": "Uniuse",
|
||||
"account.languages": "Modificar os idiomas subscritos",
|
||||
"account.link_verified_on": "A propiedade desta ligazón foi verificada o {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "מעבר לפרופיל",
|
||||
"account.hide_reblogs": "להסתיר הידהודים מאת @{name}",
|
||||
"account.in_memoriam": "פרופיל זכרון.",
|
||||
"account.joined_long": "הצטרפו ב־{date}",
|
||||
"account.joined_short": "תאריך הצטרפות",
|
||||
"account.languages": "שנה רישום לשפות",
|
||||
"account.link_verified_on": "בעלות על הקישור הזה נבדקה לאחרונה ב{date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ugrás a profilhoz",
|
||||
"account.hide_reblogs": "@{name} megtolásainak elrejtése",
|
||||
"account.in_memoriam": "Emlékünkben.",
|
||||
"account.joined_long": "Csatlakozás ideje: {date}",
|
||||
"account.joined_short": "Csatlakozott",
|
||||
"account.languages": "Feliratkozott nyelvek módosítása",
|
||||
"account.link_verified_on": "A linket eredetiségét ebben az időpontban ellenőriztük: {date}",
|
||||
@@ -163,7 +162,7 @@
|
||||
"account_edit.featured_hashtags.item": "hashtagek",
|
||||
"account_edit.featured_hashtags.placeholder": "Segíts másoknak, hogy azonosíthassák a kedvenc témáid, és gyorsan elérjék azokat.",
|
||||
"account_edit.featured_hashtags.title": "Kiemelt hashtagek",
|
||||
"account_edit.field_delete_modal.confirm": "Biztos, hogy töröld ezt az egyéni mezőt? Ez a művelet nem vonható vissza.",
|
||||
"account_edit.field_delete_modal.confirm": "Biztos, hogy törlöd ezt az egyéni mezőt? Ez a művelet nem vonható vissza.",
|
||||
"account_edit.field_delete_modal.delete_button": "Törlés",
|
||||
"account_edit.field_delete_modal.title": "Egyéni mező törlése?",
|
||||
"account_edit.field_edit_modal.add_title": "Egyéni mező hozzáadása",
|
||||
@@ -184,6 +183,15 @@
|
||||
"account_edit.field_reorder_modal.drag_start": "A(z) „{item}” mező áthelyezéshez felvéve.",
|
||||
"account_edit.field_reorder_modal.handle_label": "A(z) „{item}” mező húzása",
|
||||
"account_edit.field_reorder_modal.title": "Mezők átrendezése",
|
||||
"account_edit.image_alt_modal.add_title": "Helyettesítő szöveg hozzáadása",
|
||||
"account_edit.image_alt_modal.details_content": "TEDD: <ul> <li>Írd le a képedet</li> <li>Használj egyes szám harmadik személyt (például „én” helyett „Alex”)</li> <li>Légy tömör – sokszor néhány szó is elég</li> </ul> NE TEDD: <ul> <li>Ne kezdd azzal, hogy „X fényképe” – a képernyőolvasók számára felesleges</li> </ul> Példa: <ul> <li>„Alex zöld inget és szemüveget viselve”</li> </ul>",
|
||||
"account_edit.image_alt_modal.details_title": "Tippek: helyettesítő szöveg a profilképekhez",
|
||||
"account_edit.image_alt_modal.edit_title": "Helyettesítő szöveg szerkesztése",
|
||||
"account_edit.image_alt_modal.text_hint": "A helyettesítő szövegek segítenek a képernyőolvasót használóknak abban, hogy megértsék a tartalmat.",
|
||||
"account_edit.image_alt_modal.text_label": "Helyettesítő szöveg",
|
||||
"account_edit.image_delete_modal.confirm": "Biztos, hogy törlöd ezt a képet? Ez a művelet nem vonható vissza.",
|
||||
"account_edit.image_delete_modal.delete_button": "Törlés",
|
||||
"account_edit.image_delete_modal.title": "Törlöd a képet?",
|
||||
"account_edit.image_edit.add_button": "Kép hozzáadása",
|
||||
"account_edit.image_edit.alt_add_button": "Helyettesítő szöveg hozzáadása",
|
||||
"account_edit.image_edit.alt_edit_button": "Helyettesítő szöveg szerkesztése",
|
||||
@@ -203,6 +211,15 @@
|
||||
"account_edit.profile_tab.subtitle": "Szabd testre a profilodon látható lapokat, és a megjelenített tartalmukat.",
|
||||
"account_edit.profile_tab.title": "Profil lap beállításai",
|
||||
"account_edit.save": "Mentés",
|
||||
"account_edit.upload_modal.back": "Vissza",
|
||||
"account_edit.upload_modal.done": "Kész",
|
||||
"account_edit.upload_modal.next": "Következő",
|
||||
"account_edit.upload_modal.step_crop.zoom": "Nagyítás",
|
||||
"account_edit.upload_modal.step_upload.button": "Fájlok tallózása",
|
||||
"account_edit.upload_modal.step_upload.dragging": "Ejtsd ide a feltöltéshez",
|
||||
"account_edit.upload_modal.step_upload.header": "Válassz egy képet",
|
||||
"account_edit.upload_modal.title_add": "Profilkép hozzáadása",
|
||||
"account_edit.upload_modal.title_replace": "Profilkép cseréje",
|
||||
"account_edit.verified_modal.details": "Növeld a Mastodon-profilod hitelességét a személyes webhelyekre mutató hivatkozások ellenőrzésével. Így működik:",
|
||||
"account_edit.verified_modal.invisible_link.details": "A hivatkozás hozzáadása a fejlécedhez. A fontos rész a rel=\"me\", mely megakadályozza, hogy mások a nevedben lépjenek fel olyan oldalakon, ahol van felhasználók által előállított tartalom. A(z) {tag} helyett a „link” címkét is használhatod az oldal fejlécében, de a HTML-nek elérhetőnek kell lennie JavaScript futtatása nélkül is.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Hogyan lehet egy hivatkozás láthatatlanná tenni?",
|
||||
@@ -374,6 +391,7 @@
|
||||
"collections.search_accounts_max_reached": "Elérte a hozzáadott fiókok maximális számát",
|
||||
"collections.sensitive": "Érzékeny",
|
||||
"collections.topic_hint": "Egy hashtag hozzáadása segít másoknak abban, hogy megértsék a gyűjtemény fő témáját.",
|
||||
"collections.topic_special_chars_hint": "A különleges karakterek mentéskor el lesznek távolítva",
|
||||
"collections.view_collection": "Gyűjtemény megtekintése",
|
||||
"collections.view_other_collections_by_user": "Felhasználó más gyűjteményeinek megtekintése",
|
||||
"collections.visibility_public": "Nyilvános",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Fara í notandasnið",
|
||||
"account.hide_reblogs": "Fela endurbirtingar fyrir @{name}",
|
||||
"account.in_memoriam": "Minning.",
|
||||
"account.joined_long": "Skáði sig {date}",
|
||||
"account.joined_short": "Gerðist þátttakandi",
|
||||
"account.languages": "Breyta tungumálum í áskrift",
|
||||
"account.link_verified_on": "Eignarhald á þessum tengli var athugað þann {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Vai al profilo",
|
||||
"account.hide_reblogs": "Nascondi condivisioni da @{name}",
|
||||
"account.in_memoriam": "In memoria.",
|
||||
"account.joined_long": "Su questa istanza dal {date}",
|
||||
"account.joined_short": "Iscritto",
|
||||
"account.languages": "Modifica le lingue d'iscrizione",
|
||||
"account.link_verified_on": "La proprietà di questo link è stata controllata il {date}",
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
"account.follows_you": "Yeṭṭafaṛ-ik·em-id",
|
||||
"account.go_to_profile": "Ddu ɣer umaɣnu",
|
||||
"account.hide_reblogs": "Ffer ayen i ibeṭṭu @{name}",
|
||||
"account.joined_long": "Yerna-d ass n {date}",
|
||||
"account.joined_short": "Izeddi da seg ass n",
|
||||
"account.languages": "Beddel tutlayin yettwajerden",
|
||||
"account.link_verified_on": "Taɣara n useɣwen-a tettwasenqed ass n {date}",
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
"account.go_to_profile": "프로필로 이동",
|
||||
"account.hide_reblogs": "@{name}의 부스트를 숨기기",
|
||||
"account.in_memoriam": "고인의 계정입니다.",
|
||||
"account.joined_long": "{date}에 가입함",
|
||||
"account.joined_short": "가입",
|
||||
"account.languages": "구독한 언어 변경",
|
||||
"account.link_verified_on": "{date}에 이 링크의 소유권이 확인 됨",
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"account.follow": "Sekti",
|
||||
"account.follow_back": "Sekti atgal",
|
||||
"account.follow_back_short": "Sekti atgal",
|
||||
"account.follow_request": "Prašyti sekti",
|
||||
"account.follow_request": "Prašymas sekti",
|
||||
"account.follow_request_cancel": "Atšaukti prašymą",
|
||||
"account.follow_request_cancel_short": "Atšaukti",
|
||||
"account.follow_request_short": "Prašymas",
|
||||
@@ -69,7 +69,6 @@
|
||||
"account.go_to_profile": "Eiti į profilį",
|
||||
"account.hide_reblogs": "Slėpti pasidalinimus iš @{name}",
|
||||
"account.in_memoriam": "Atminimui.",
|
||||
"account.joined_long": "Prisijungė {date}",
|
||||
"account.joined_short": "Prisijungė",
|
||||
"account.languages": "Keisti prenumeruojamas kalbas",
|
||||
"account.link_verified_on": "Šios nuorodos nuosavybė buvo patikrinta {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "行kàu個人資料",
|
||||
"account.hide_reblogs": "Tshàng tuì @{name} 來ê轉PO",
|
||||
"account.in_memoriam": "佇tsia追悼。",
|
||||
"account.joined_long": "佇 {date} 加入",
|
||||
"account.joined_short": "加入ê時",
|
||||
"account.languages": "變更訂閱的語言",
|
||||
"account.link_verified_on": "Tsit ê連結ê所有權佇 {date} 受檢查",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ga naar profiel",
|
||||
"account.hide_reblogs": "Boosts van @{name} verbergen",
|
||||
"account.in_memoriam": "In memoriam.",
|
||||
"account.joined_long": "Geregistreerd op {date}",
|
||||
"account.joined_short": "Geregistreerd op",
|
||||
"account.languages": "Getoonde talen wijzigen",
|
||||
"account.link_verified_on": "Eigendom van deze link is gecontroleerd op {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Gå til profil",
|
||||
"account.hide_reblogs": "Gøym framhevingar frå @{name}",
|
||||
"account.in_memoriam": "Til minne om.",
|
||||
"account.joined_long": "Vart med {date}",
|
||||
"account.joined_short": "Vart med",
|
||||
"account.languages": "Endre språktingingar",
|
||||
"account.link_verified_on": "Eigarskap for denne lenkja vart sist sjekka {date}",
|
||||
@@ -222,6 +221,13 @@
|
||||
"account_edit.upload_modal.step_upload.hint": "WEBP, PNG, GIF eller JPG-format, opp til {limit}MB.{br}Biletet blir skalert til {width}*{height} punkt.",
|
||||
"account_edit.upload_modal.title_add": "Legg til profilbilete",
|
||||
"account_edit.upload_modal.title_replace": "Byt ut profilbilete",
|
||||
"account_edit.verified_modal.details": "Auk truverdet til Mastodon-profilen din ved å stadfesta lenker til personlege nettstader. Slik verkar det:",
|
||||
"account_edit.verified_modal.invisible_link.details": "Den viktige delen er rel=\"me\", som på nettstader med brukargenerert innhald vil hindra at andre kan låst som dei er deg. Du kan til og med bruka link i staden for {tag} i toppteksten til sida, men HTML-koden må vera tilgjengeleg utan å måtte køyra JavaScript.",
|
||||
"account_edit.verified_modal.invisible_link.summary": "Korleis gjer eg lenka usynleg?",
|
||||
"account_edit.verified_modal.step1.header": "Kopier HTML-koden under og lim han inn i toppfeltet på nettstaden din",
|
||||
"account_edit.verified_modal.step2.details": "Viss du allereie har lagt til nettsida di i eit tilpassa felt, må du sletta ho og leggja ho til på nytt for å setja i gang stadfestinga.",
|
||||
"account_edit.verified_modal.step2.header": "Legg til nettstaden din som eige felt",
|
||||
"account_edit.verified_modal.title": "Korleis legg eg til ei stadfesta lenke",
|
||||
"account_edit_tags.add_tag": "Legg til #{tagName}",
|
||||
"account_edit_tags.column_title": "Rediger utvalde emneknaggar",
|
||||
"account_edit_tags.help_text": "Utvalde emneknaggar hjelper folk å oppdaga og samhandla med profilen din. Dei blir viste som filter på aktivitetsoversikta på profilsida di.",
|
||||
@@ -325,8 +331,8 @@
|
||||
"callout.dismiss": "Avvis",
|
||||
"carousel.current": "<sr>Side</sr> {current, number} / {max, number}",
|
||||
"carousel.slide": "Side {current, number} av {max, number}",
|
||||
"character_counter.recommended": "{currentLength}/{maxLength} anbefalte tegn",
|
||||
"character_counter.required": "{currentLength}/{maxLength} tegn",
|
||||
"character_counter.recommended": "{currentLength}/{maxLength} tilrådde teikn",
|
||||
"character_counter.required": "{currentLength}/{maxLength} teikn",
|
||||
"closed_registrations.other_server_instructions": "Sidan Mastodon er desentralisert kan du lage ein brukar på ein anna tenar og framleis interagere med denne.",
|
||||
"closed_registrations_modal.description": "Det er ikkje mogleg å opprette ein konto på {domain} nett no, men hugs at du ikkje treng ein konto på akkurat {domain} for å nytte Mastodon.",
|
||||
"closed_registrations_modal.find_another_server": "Finn ein annan tenar",
|
||||
@@ -356,10 +362,15 @@
|
||||
"collections.create_collection": "Lag ei samling",
|
||||
"collections.delete_collection": "Slett samlinga",
|
||||
"collections.description_length_hint": "Maks 100 teikn",
|
||||
"collections.detail.accept_inclusion": "Ok",
|
||||
"collections.detail.accounts_heading": "Kontoar",
|
||||
"collections.detail.author_added_you": "{author} la deg til i denne samlinga",
|
||||
"collections.detail.curated_by_author": "Kuratert av {author}",
|
||||
"collections.detail.curated_by_you": "Kuratert av deg",
|
||||
"collections.detail.loading": "Lastar inn samling…",
|
||||
"collections.detail.other_accounts_in_collection": "Andre i denne samlinga:",
|
||||
"collections.detail.revoke_inclusion": "Fjern meg",
|
||||
"collections.detail.sensitive_note": "Denne samlinga inneheld kontoar og innhald som kan vera ømtolige for nokre menneske.",
|
||||
"collections.detail.share": "Del denne samlinga",
|
||||
"collections.edit_details": "Rediger detaljar",
|
||||
"collections.error_loading_collections": "Noko gjekk gale då me prøvde å henta samlingane dine.",
|
||||
@@ -374,10 +385,14 @@
|
||||
"collections.old_last_post_note": "Sist lagt ut for over ei veke sidan",
|
||||
"collections.remove_account": "Fjern denne kontoen",
|
||||
"collections.report_collection": "Rapporter denne samlinga",
|
||||
"collections.revoke_collection_inclusion": "Fjern meg frå denne samlinga",
|
||||
"collections.revoke_inclusion.confirmation": "Du er fjerna frå «{collection}»",
|
||||
"collections.revoke_inclusion.error": "Noko gjekk gale, prøv att seinare.",
|
||||
"collections.search_accounts_label": "Søk etter kontoar å leggja til…",
|
||||
"collections.search_accounts_max_reached": "Du har nådd grensa for kor mange kontoar du kan leggja til",
|
||||
"collections.sensitive": "Ømtolig",
|
||||
"collections.topic_hint": "Legg til ein emneknagg som hjelper andre å forstå hovudemnet for denne samlinga.",
|
||||
"collections.topic_special_chars_hint": "Spesialteikn vil bli fjerna ved lagring",
|
||||
"collections.view_collection": "Sjå samlinga",
|
||||
"collections.view_other_collections_by_user": "Sjå andre samlingar frå denne personen",
|
||||
"collections.visibility_public": "Offentleg",
|
||||
@@ -498,7 +513,8 @@
|
||||
"confirmations.remove_from_followers.message": "{name} vil ikkje fylgja deg meir. Vil du halda fram?",
|
||||
"confirmations.remove_from_followers.title": "Fjern fylgjar?",
|
||||
"confirmations.revoke_collection_inclusion.confirm": "Fjern meg",
|
||||
"confirmations.revoke_collection_inclusion.title": "Fjern deg selv fra denne samlingen?",
|
||||
"confirmations.revoke_collection_inclusion.message": "Denne handlinga er endeleg, og kuratoren kan ikkje leggja deg til samlinga på nytt seinare.",
|
||||
"confirmations.revoke_collection_inclusion.title": "Vil du fjerna deg sjølv frå denne samlinga?",
|
||||
"confirmations.revoke_quote.confirm": "Fjern innlegget",
|
||||
"confirmations.revoke_quote.message": "Du kan ikkje angra denne handlinga.",
|
||||
"confirmations.revoke_quote.title": "Fjern innlegget?",
|
||||
@@ -821,6 +837,7 @@
|
||||
"navigation_bar.automated_deletion": "Automatisk sletting av innlegg",
|
||||
"navigation_bar.blocks": "Blokkerte brukarar",
|
||||
"navigation_bar.bookmarks": "Bokmerke",
|
||||
"navigation_bar.collections": "Samlingar",
|
||||
"navigation_bar.direct": "Private omtaler",
|
||||
"navigation_bar.domain_blocks": "Skjulte domene",
|
||||
"navigation_bar.favourites": "Favorittar",
|
||||
@@ -968,12 +985,14 @@
|
||||
"notifications_permission_banner.title": "Gå aldri glipp av noko",
|
||||
"onboarding.follows.back": "Tilbake",
|
||||
"onboarding.follows.empty": "Me kan ikkje visa deg nokon resultat no. Du kan prøva å søkja eller bla gjennom utforsk-sida for å finna folk å fylgja, eller du kan prøva att seinare.",
|
||||
"onboarding.follows.next": "Neste: Set opp profilen din",
|
||||
"onboarding.follows.search": "Søk",
|
||||
"onboarding.follows.title": "Fylg folk for å koma i gang",
|
||||
"onboarding.profile.discoverable": "Gjer profilen min synleg",
|
||||
"onboarding.profile.discoverable_hint": "Når du vel å gjera profilen din synleg på Mastodon, vil innlegga dine syna i søkjeresultat og populære innlegg, og profilen din kan bli føreslegen for folk med liknande interesser som deg.",
|
||||
"onboarding.profile.display_name": "Synleg namn",
|
||||
"onboarding.profile.display_name_hint": "Det fulle namnet eller kallenamnet ditt…",
|
||||
"onboarding.profile.finish": "Fullfør",
|
||||
"onboarding.profile.note": "Om meg",
|
||||
"onboarding.profile.note_hint": "Du kan @nemna folk eller #emneknaggar…",
|
||||
"onboarding.profile.title": "Profiloppsett",
|
||||
|
||||
@@ -71,7 +71,6 @@
|
||||
"account.go_to_profile": "Gå til profil",
|
||||
"account.hide_reblogs": "Skjul fremhevinger fra @{name}",
|
||||
"account.in_memoriam": "Til minne om.",
|
||||
"account.joined_long": "Ble med den {date}",
|
||||
"account.joined_short": "Ble med",
|
||||
"account.languages": "Endre hvilke språk du abonnerer på",
|
||||
"account.link_verified_on": "Eierskap av denne lenken ble sjekket {date}",
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
"account.follows_you": "ਤੁਹਾਨੂੰ ਫ਼ਾਲੋ ਕਰਦੇ ਹਨ",
|
||||
"account.go_to_profile": "ਪਰੋਫਾਇਲ ਉੱਤੇ ਜਾਓ",
|
||||
"account.hide_reblogs": "{name} ਵਲੋਂ ਬੂਸਟ ਨੂੰ ਲੁਕਾਓ",
|
||||
"account.joined_long": "{date} ਨੂੰ ਜੁਆਇਨ ਕੀਤਾ",
|
||||
"account.joined_short": "ਜੁਆਇਨ ਕੀਤਾ",
|
||||
"account.media": "ਮੀਡੀਆ",
|
||||
"account.mention": "@{name} ਦਾ ਜ਼ਿਕਰ",
|
||||
|
||||
@@ -57,7 +57,6 @@
|
||||
"account.go_to_profile": "Przejdź do profilu",
|
||||
"account.hide_reblogs": "Ukryj podbicia od @{name}",
|
||||
"account.in_memoriam": "Ku pamięci.",
|
||||
"account.joined_long": "Dołączył(a) dnia {date}",
|
||||
"account.joined_short": "Dołączył(a)",
|
||||
"account.languages": "Zmień subskrybowane języki",
|
||||
"account.link_verified_on": "Własność tego odnośnika została potwierdzona {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ir ao perfil",
|
||||
"account.hide_reblogs": "Ocultar impulsos de @{name}",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
"account.joined_long": "Entrou em {date}",
|
||||
"account.joined_short": "Entrou",
|
||||
"account.languages": "Mudar idiomas inscritos",
|
||||
"account.link_verified_on": "A propriedade deste link foi verificada em {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Ir para o perfil",
|
||||
"account.hide_reblogs": "Esconder partilhas de @{name}",
|
||||
"account.in_memoriam": "Em Memória.",
|
||||
"account.joined_long": "Juntou-se em {date}",
|
||||
"account.joined_short": "Juntou-se a",
|
||||
"account.languages": "Alterar idiomas subscritos",
|
||||
"account.link_verified_on": "O proprietário desta hiperligação foi verificado em {date}",
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
"account.go_to_profile": "Pojdi na profil",
|
||||
"account.hide_reblogs": "Skrij izpostavitve od @{name}",
|
||||
"account.in_memoriam": "V spomin.",
|
||||
"account.joined_long": "Pridružen/a {date}",
|
||||
"account.joined_short": "Pridružil/a",
|
||||
"account.languages": "Spremeni naročene jezike",
|
||||
"account.link_verified_on": "Lastništvo te povezave je bilo preverjeno {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Kalo te profili",
|
||||
"account.hide_reblogs": "Fshih përforcime nga @{name}",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
"account.joined_long": "U bë pjesë më {date}",
|
||||
"account.joined_short": "U bë pjesë",
|
||||
"account.languages": "Ndryshoni gjuhë pajtimesh",
|
||||
"account.link_verified_on": "Pronësia e kësaj lidhjeje qe kontrolluar më {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Gå till profilen",
|
||||
"account.hide_reblogs": "Dölj boostar från @{name}",
|
||||
"account.in_memoriam": "Till minne av.",
|
||||
"account.joined_long": "Gick med {date}",
|
||||
"account.joined_short": "Gick med",
|
||||
"account.languages": "Ändra vilka språk du helst vill se i ditt flöde",
|
||||
"account.link_verified_on": "Ägarskap för denna länk kontrollerades den {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Profile git",
|
||||
"account.hide_reblogs": "@{name} kişisinin yeniden paylaşımlarını gizle",
|
||||
"account.in_memoriam": "Hatırasına.",
|
||||
"account.joined_long": "{date} tarihinde katıldı",
|
||||
"account.joined_short": "Katıldı",
|
||||
"account.languages": "Abone olunan dilleri değiştir",
|
||||
"account.link_verified_on": "Bu bağlantının sahipliği {date} tarihinde denetlendi",
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
"account.go_to_profile": "Перейти до профілю",
|
||||
"account.hide_reblogs": "Сховати поширення від @{name}",
|
||||
"account.in_memoriam": "Пам'ятник.",
|
||||
"account.joined_long": "Долучилися {date}",
|
||||
"account.joined_short": "Дата приєднання",
|
||||
"account.languages": "Змінити обрані мови",
|
||||
"account.link_verified_on": "Права власності на це посилання були перевірені {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "Xem hồ sơ",
|
||||
"account.hide_reblogs": "Ẩn tút @{name} đăng lại",
|
||||
"account.in_memoriam": "Tưởng Niệm.",
|
||||
"account.joined_long": "Tham gia {date}",
|
||||
"account.joined_short": "Tham gia",
|
||||
"account.languages": "Đổi ngôn ngữ mong muốn",
|
||||
"account.link_verified_on": "Liên kết này đã được xác minh vào {date}",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "前往个人资料页",
|
||||
"account.hide_reblogs": "隐藏来自 @{name} 的转嘟",
|
||||
"account.in_memoriam": "谨此悼念。",
|
||||
"account.joined_long": "加入于 {date}",
|
||||
"account.joined_short": "加入于",
|
||||
"account.languages": "更改订阅语言",
|
||||
"account.link_verified_on": "此链接的所有权已在 {date} 检查",
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
"account.go_to_profile": "前往個人檔案",
|
||||
"account.hide_reblogs": "隱藏來自 @{name} 之轉嘟",
|
||||
"account.in_memoriam": "謹此悼念。",
|
||||
"account.joined_long": "加入於 {date}",
|
||||
"account.joined_short": "加入時間",
|
||||
"account.languages": "變更訂閱的語言",
|
||||
"account.link_verified_on": "已於 {date} 檢查此連結的擁有者權限",
|
||||
|
||||
@@ -82,6 +82,9 @@ export const accountDefaultValues: AccountShape = {
|
||||
last_status_at: '',
|
||||
locked: false,
|
||||
noindex: false,
|
||||
show_featured: true,
|
||||
show_media: true,
|
||||
show_media_replies: true,
|
||||
note: '',
|
||||
note_emojified: '',
|
||||
note_plain: 'string',
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { fetchAccount } from '@/mastodon/actions/accounts';
|
||||
import {
|
||||
apiDeleteFeaturedTag,
|
||||
@@ -14,7 +11,6 @@ import {
|
||||
apiPatchProfile,
|
||||
apiPostFeaturedTag,
|
||||
} from '@/mastodon/api/accounts';
|
||||
import { apiGetSearch } from '@/mastodon/api/search';
|
||||
import type { ApiAccountFieldJSON } from '@/mastodon/api_types/accounts';
|
||||
import type {
|
||||
ApiProfileJSON,
|
||||
@@ -24,7 +20,6 @@ import type {
|
||||
ApiFeaturedTagJSON,
|
||||
ApiHashtagJSON,
|
||||
} from '@/mastodon/api_types/tags';
|
||||
import type { AppDispatch } from '@/mastodon/store';
|
||||
import {
|
||||
createAppAsyncThunk,
|
||||
createAppSelector,
|
||||
@@ -59,40 +54,16 @@ export interface ProfileEditState {
|
||||
profile?: ProfileData;
|
||||
tagSuggestions?: ApiHashtagJSON[];
|
||||
isPending: boolean;
|
||||
search: {
|
||||
query: string;
|
||||
isLoading: boolean;
|
||||
results?: ApiHashtagJSON[];
|
||||
};
|
||||
}
|
||||
|
||||
const initialState: ProfileEditState = {
|
||||
isPending: false,
|
||||
search: {
|
||||
query: '',
|
||||
isLoading: false,
|
||||
},
|
||||
};
|
||||
|
||||
const profileEditSlice = createSlice({
|
||||
name: 'profileEdit',
|
||||
initialState,
|
||||
reducers: {
|
||||
setSearchQuery(state, action: PayloadAction<string>) {
|
||||
if (state.search.query === action.payload) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.search.query = action.payload;
|
||||
state.search.isLoading = true;
|
||||
state.search.results = undefined;
|
||||
},
|
||||
clearSearch(state) {
|
||||
state.search.query = '';
|
||||
state.search.isLoading = false;
|
||||
state.search.results = undefined;
|
||||
},
|
||||
},
|
||||
reducers: {},
|
||||
extraReducers(builder) {
|
||||
builder.addCase(fetchProfile.fulfilled, (state, action) => {
|
||||
state.profile = action.payload;
|
||||
@@ -172,37 +143,10 @@ const profileEditSlice = createSlice({
|
||||
);
|
||||
state.isPending = false;
|
||||
});
|
||||
|
||||
builder.addCase(fetchSearchResults.pending, (state) => {
|
||||
state.search.isLoading = true;
|
||||
});
|
||||
builder.addCase(fetchSearchResults.rejected, (state) => {
|
||||
state.search.isLoading = false;
|
||||
state.search.results = undefined;
|
||||
});
|
||||
builder.addCase(fetchSearchResults.fulfilled, (state, action) => {
|
||||
state.search.isLoading = false;
|
||||
const searchResults: ApiHashtagJSON[] = [];
|
||||
const currentTags = new Set(
|
||||
(state.profile?.featuredTags ?? []).map((tag) => tag.name),
|
||||
);
|
||||
|
||||
for (const tag of action.payload) {
|
||||
if (currentTags.has(tag.name)) {
|
||||
continue;
|
||||
}
|
||||
searchResults.push(tag);
|
||||
if (searchResults.length >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.search.results = searchResults;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const profileEdit = profileEditSlice.reducer;
|
||||
export const { clearSearch } = profileEditSlice.actions;
|
||||
|
||||
const transformTag = (result: ApiFeaturedTagJSON): TagData => ({
|
||||
id: result.id,
|
||||
@@ -426,27 +370,3 @@ export const deleteFeaturedTag = createDataLoadingThunk(
|
||||
`${profileEditSlice.name}/deleteFeaturedTag`,
|
||||
({ tagId }: { tagId: string }) => apiDeleteFeaturedTag(tagId),
|
||||
);
|
||||
|
||||
const debouncedFetchSearchResults = debounce(
|
||||
async (dispatch: AppDispatch, query: string) => {
|
||||
await dispatch(fetchSearchResults({ q: query }));
|
||||
},
|
||||
300,
|
||||
);
|
||||
|
||||
export const updateSearchQuery = createAppAsyncThunk(
|
||||
`${profileEditSlice.name}/updateSearchQuery`,
|
||||
(query: string, { dispatch }) => {
|
||||
dispatch(profileEditSlice.actions.setSearchQuery(query));
|
||||
|
||||
if (query.trim().length > 0) {
|
||||
void debouncedFetchSearchResults(dispatch, query);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchSearchResults = createDataLoadingThunk(
|
||||
`${profileEditSlice.name}/fetchSearchResults`,
|
||||
({ q }: { q: string }) => apiGetSearch({ q, type: 'hashtags', limit: 11 }),
|
||||
(result) => result.hashtags,
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from '../actions/timelines_typed';
|
||||
import { compareId } from '../compare_id';
|
||||
|
||||
/** @type {ImmutableMap<string, typeof initialTimeline>} */
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
const initialTimeline = ImmutableMap({
|
||||
@@ -36,7 +37,9 @@ const initialTimeline = ImmutableMap({
|
||||
top: true,
|
||||
isLoading: false,
|
||||
hasMore: true,
|
||||
/** @type {ImmutableList<string>} */
|
||||
pendingItems: ImmutableList(),
|
||||
/** @type {ImmutableList<string>} */
|
||||
items: ImmutableList(),
|
||||
});
|
||||
|
||||
@@ -197,6 +200,7 @@ const reconnectTimeline = (state, usePendingItems) => {
|
||||
});
|
||||
};
|
||||
|
||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||
export default function timelines(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case TIMELINE_LOAD_PENDING:
|
||||
|
||||
@@ -18,7 +18,7 @@ export function isServerFeatureEnabled(feature: ServerFeatures) {
|
||||
return initialState?.features.includes(feature) ?? false;
|
||||
}
|
||||
|
||||
type ClientFeatures = 'collections' | 'profile_editing';
|
||||
type ClientFeatures = 'collections';
|
||||
|
||||
export function isClientFeatureEnabled(feature: ClientFeatures) {
|
||||
try {
|
||||
|
||||
@@ -28,6 +28,12 @@ export const HASHTAG_PATTERN_REGEX = buildHashtagPatternRegex();
|
||||
|
||||
export const HASHTAG_REGEX = buildHashtagRegex();
|
||||
|
||||
export const trimHashFromStart = (input: string) => {
|
||||
return input.startsWith('#') || input.startsWith('#')
|
||||
? input.slice(1)
|
||||
: input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats an input string as a hashtag:
|
||||
* - Prepends `#` unless present
|
||||
@@ -41,11 +47,7 @@ export const inputToHashtag = (input: string): string => {
|
||||
|
||||
const trailingSpace = /\s+$/.exec(input)?.[0] ?? '';
|
||||
const trimmedInput = input.trimEnd();
|
||||
|
||||
const withoutHash =
|
||||
trimmedInput.startsWith('#') || trimmedInput.startsWith('#')
|
||||
? trimmedInput.slice(1)
|
||||
: trimmedInput;
|
||||
const withoutHash = trimHashFromStart(trimmedInput);
|
||||
|
||||
// Split by space, filter empty strings, and capitalise the start of each word but the first
|
||||
const words = withoutHash
|
||||
|
||||
@@ -45,6 +45,9 @@ export const accountFactory: FactoryFunction<ApiAccountJSON> = ({
|
||||
indexable: true,
|
||||
last_status_at: '2023-01-01',
|
||||
locked: false,
|
||||
show_featured: true,
|
||||
show_media: true,
|
||||
show_media_replies: true,
|
||||
mute_expires_at: null,
|
||||
note: 'This is a test user account.',
|
||||
statuses_count: 0,
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||
def perform
|
||||
if @account.uri == object_uri
|
||||
delete_person
|
||||
else
|
||||
return delete_person if @account.uri == object_uri
|
||||
return delete_feature_authorization! unless !Mastodon::Feature.collections_federation_enabled? || feature_authorization_from_object.nil?
|
||||
|
||||
delete_object
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -66,7 +65,18 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||
DistributionWorker.perform_async(@quote.status_id, { 'update' => true }) if @quote.status.present?
|
||||
end
|
||||
|
||||
def delete_feature_authorization!
|
||||
collection_item = feature_authorization_from_object
|
||||
DeleteCollectionItemService.new.call(collection_item, revoke: true)
|
||||
end
|
||||
|
||||
def forwarder
|
||||
@forwarder ||= ActivityPub::Forwarder.new(@account, @json, @status)
|
||||
end
|
||||
|
||||
def feature_authorization_from_object
|
||||
return @collection_item if instance_variable_defined?(:@collection_item)
|
||||
|
||||
@collection_item = CollectionItem.local.find_by(approval_uri: value_or_id(@object), account_id: @account.id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,6 +29,7 @@ class Web::PushSubscription < ApplicationRecord
|
||||
validates_with WebPushKeyValidator
|
||||
|
||||
delegate :locale, to: :user
|
||||
delegate :token, to: :access_token, prefix: :associated_access
|
||||
|
||||
generates_token_for :unsubscribe, expires_in: Web::PushNotificationWorker::TTL
|
||||
|
||||
@@ -36,10 +37,6 @@ class Web::PushSubscription < ApplicationRecord
|
||||
policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification)
|
||||
end
|
||||
|
||||
def associated_access_token
|
||||
access_token.token
|
||||
end
|
||||
|
||||
class << self
|
||||
def unsubscribe_for(application_id, resource_owner)
|
||||
access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id).not_revoked.pluck(:id)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::FetchFeaturedCollectionsCollectionService < BaseService
|
||||
include JsonLdHelper
|
||||
|
||||
MAX_PAGES = 10
|
||||
MAX_ITEMS = 50
|
||||
|
||||
def call(account, request_id: nil)
|
||||
return if account.collections_url.blank? || account.suspended? || account.local?
|
||||
|
||||
@request_id = request_id
|
||||
@account = account
|
||||
@items, = collection_items(@account.collections_url, max_pages: MAX_PAGES, reference_uri: @account.uri)
|
||||
process_items(@items)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_items(items)
|
||||
return if items.nil?
|
||||
|
||||
items.take(MAX_ITEMS).each do |collection_json|
|
||||
if collection_json.is_a?(String)
|
||||
ActivityPub::FetchRemoteFeaturedCollectionService.new.call(collection_json, request_id: @request_id)
|
||||
else
|
||||
ActivityPub::ProcessFeaturedCollectionService.new.call(@account, collection_json, request_id: @request_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,7 +3,7 @@
|
||||
class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService
|
||||
include JsonLdHelper
|
||||
|
||||
def call(uri, on_behalf_of = nil)
|
||||
def call(uri, request_id: nil, on_behalf_of: nil)
|
||||
json = fetch_resource(uri, true, on_behalf_of)
|
||||
|
||||
return unless supported_context?(json)
|
||||
@@ -17,6 +17,6 @@ class ActivityPub::FetchRemoteFeaturedCollectionService < BaseService
|
||||
existing_collection = account.collections.find_by(uri:)
|
||||
return existing_collection if existing_collection.present?
|
||||
|
||||
ActivityPub::ProcessFeaturedCollectionService.new.call(account, json)
|
||||
ActivityPub::ProcessFeaturedCollectionService.new.call(account, json, request_id:)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -60,6 +60,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
unless @options[:only_key] || @account.suspended?
|
||||
check_featured_collection! if @json['featured'].present?
|
||||
check_featured_tags_collection! if @json['featuredTags'].present?
|
||||
check_featured_collections_collection! if @json['featuredCollections'].present? && Mastodon::Feature.collections_federation_enabled?
|
||||
check_links! if @account.fields.any?(&:requires_verification?)
|
||||
end
|
||||
|
||||
@@ -201,6 +202,10 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
ActivityPub::SynchronizeFeaturedTagsCollectionWorker.perform_async(@account.id, @json['featuredTags'])
|
||||
end
|
||||
|
||||
def check_featured_collections_collection!
|
||||
ActivityPub::SynchronizeFeaturedCollectionsCollectionWorker.perform_async(@account.id, @options[:request_id])
|
||||
end
|
||||
|
||||
def check_links!
|
||||
VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), @account.id)
|
||||
end
|
||||
|
||||
@@ -7,9 +7,10 @@ class ActivityPub::ProcessFeaturedCollectionService
|
||||
|
||||
ITEMS_LIMIT = 150
|
||||
|
||||
def call(account, json)
|
||||
def call(account, json, request_id: nil)
|
||||
@account = account
|
||||
@json = json
|
||||
@request_id = request_id
|
||||
return if non_matching_uri_hosts?(@account.uri, @json['id'])
|
||||
|
||||
with_redis_lock("collection:#{@json['id']}") do
|
||||
@@ -46,7 +47,7 @@ class ActivityPub::ProcessFeaturedCollectionService
|
||||
|
||||
def process_items!
|
||||
@json['orderedItems'].take(ITEMS_LIMIT).each do |item_json|
|
||||
ActivityPub::ProcessFeaturedItemWorker.perform_async(@collection.id, item_json)
|
||||
ActivityPub::ProcessFeaturedItemWorker.perform_async(@collection.id, item_json, @request_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,7 +5,8 @@ class ActivityPub::ProcessFeaturedItemService
|
||||
include Lockable
|
||||
include Redisable
|
||||
|
||||
def call(collection, uri_or_object)
|
||||
def call(collection, uri_or_object, request_id: nil)
|
||||
@request_id = request_id
|
||||
item_json = uri_or_object.is_a?(String) ? fetch_resource(uri_or_object, true) : uri_or_object
|
||||
return if non_matching_uri_hosts?(collection.uri, item_json['id'])
|
||||
|
||||
@@ -35,8 +36,8 @@ class ActivityPub::ProcessFeaturedItemService
|
||||
private
|
||||
|
||||
def verify_authorization!
|
||||
ActivityPub::VerifyFeaturedItemService.new.call(@collection_item, @approval_uri)
|
||||
ActivityPub::VerifyFeaturedItemService.new.call(@collection_item, @approval_uri, request_id: @request_id)
|
||||
rescue Mastodon::RecursionLimitExceededError, Mastodon::UnexpectedResponseError, *Mastodon::HTTP_CONNECTION_ERRORS
|
||||
ActivityPub::VerifyFeaturedItemWorker.perform_in(rand(30..600).seconds, @collection_item.id, @approval_uri)
|
||||
ActivityPub::VerifyFeaturedItemWorker.perform_in(rand(30..600).seconds, @collection_item.id, @approval_uri, @request_id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
class ActivityPub::VerifyFeaturedItemService
|
||||
include JsonLdHelper
|
||||
|
||||
def call(collection_item, approval_uri)
|
||||
def call(collection_item, approval_uri, request_id: nil)
|
||||
@collection_item = collection_item
|
||||
@authorization = fetch_resource(approval_uri, true, raise_on_error: :temporary)
|
||||
|
||||
@@ -16,7 +16,7 @@ class ActivityPub::VerifyFeaturedItemService
|
||||
return unless matching_type? && matching_collection_uri?
|
||||
|
||||
account = Account.where(uri: @collection_item.object_uri).first
|
||||
account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri)
|
||||
account ||= ActivityPub::FetchRemoteAccountService.new.call(@collection_item.object_uri, request_id:)
|
||||
return if account.blank?
|
||||
|
||||
@collection_item.update!(account:, approval_uri:, state: :accepted)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class DeleteCollectionItemService
|
||||
def call(collection_item)
|
||||
def call(collection_item, revoke: false)
|
||||
@collection_item = collection_item
|
||||
@collection = collection_item.collection
|
||||
@collection_item.destroy!
|
||||
|
||||
revoke ? @collection_item.revoke! : @collection_item.destroy!
|
||||
|
||||
distribute_remove_activity if Mastodon::Feature.collections_federation_enabled?
|
||||
end
|
||||
|
||||
@@ -88,11 +88,18 @@ class ReportService < BaseService
|
||||
has_followers = @target_account.followers.with_domain(domain).exists?
|
||||
visibility = has_followers ? %i(public unlisted private) : %i(public unlisted)
|
||||
scope = @target_account.statuses.with_discarded
|
||||
scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain)))
|
||||
scope.merge!(scope.where(visibility: visibility).or(scope.where(domain_mentions(domain))))
|
||||
# Allow missing posts to not drop reports that include e.g. a deleted post
|
||||
scope.where(id: Array(@status_ids)).pluck(:id)
|
||||
end
|
||||
|
||||
def domain_mentions(domain)
|
||||
Mention
|
||||
.joins(:account)
|
||||
.where(Account.arel_table[:domain].lower.eq domain)
|
||||
.select(1).arel.exists
|
||||
end
|
||||
|
||||
def reported_collection_ids
|
||||
@target_account.collections.find(Array(@collection_ids)).pluck(:id)
|
||||
end
|
||||
|
||||
@@ -6,10 +6,10 @@ class ActivityPub::ProcessFeaturedItemWorker
|
||||
|
||||
sidekiq_options queue: 'pull', retry: 3
|
||||
|
||||
def perform(collection_id, id_or_json)
|
||||
def perform(collection_id, id_or_json, request_id = nil)
|
||||
collection = Collection.find(collection_id)
|
||||
|
||||
ActivityPub::ProcessFeaturedItemService.new.call(collection, id_or_json)
|
||||
ActivityPub::ProcessFeaturedItemService.new.call(collection, id_or_json, request_id:)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::SynchronizeFeaturedCollectionsCollectionWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: 'pull', lock: :until_executed, lock_ttl: 1.day.to_i
|
||||
|
||||
def perform(account_id, request_id = nil)
|
||||
account = Account.find(account_id)
|
||||
|
||||
ActivityPub::FetchFeaturedCollectionsCollectionService.new.call(account, request_id:)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
end
|
||||
@@ -7,10 +7,10 @@ class ActivityPub::VerifyFeaturedItemWorker
|
||||
|
||||
sidekiq_options queue: 'pull', retry: 5
|
||||
|
||||
def perform(collection_item_id, approval_uri)
|
||||
def perform(collection_item_id, approval_uri, request_id = nil)
|
||||
collection_item = CollectionItem.find(collection_item_id)
|
||||
|
||||
ActivityPub::VerifyFeaturedItemService.new.call(collection_item, approval_uri)
|
||||
ActivityPub::VerifyFeaturedItemService.new.call(collection_item, approval_uri, request_id:)
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# Do nothing
|
||||
nil
|
||||
|
||||
@@ -810,6 +810,8 @@ be:
|
||||
administrator_description: Карыстальнікі з гэтым дазволам будуць абыходзіць усе абмежаванні
|
||||
delete_user_data: Выдаленне даных карыстальнікаў
|
||||
delete_user_data_description: Дазваляе карыстальнікам без затрымкі выдаляць даныя іншых карыстальнікаў
|
||||
invite_bypass_approval: Запрашаць карыстальнікаў без разгляду
|
||||
invite_bypass_approval_description: Дазваляе людзям, якіх запрасілі на сервер гэтыя карыстальнікі, абыходзіць ухвалу мадэратараў
|
||||
invite_users: Запрашэнне карыстальнікаў
|
||||
invite_users_description: Дазваляе запрашаць новых людзей на сервер
|
||||
manage_announcements: Кіраванне аб’явамі
|
||||
@@ -1337,6 +1339,7 @@ be:
|
||||
invited_by: 'Вы можаце далучыцца да %{domain} дзякуючы запрашэнню, якое вы атрымалі ад:'
|
||||
preamble: Правілы вызначаныя мадэратарамі дамена %{domain}.
|
||||
preamble_invited: Перш чым працягнуць, азнаёмцеся з асноўнымі правіламі, усталяванымі мадэратарамі %{domain}.
|
||||
read_more: Падрабязней
|
||||
title: Некалькі базавых правілаў.
|
||||
title_invited: Вас запрасілі.
|
||||
security: Бяспека
|
||||
|
||||
@@ -1363,6 +1363,7 @@ cy:
|
||||
progress:
|
||||
confirm: Cadarnhau'r e-bost
|
||||
details: Eich manylion
|
||||
list: Y drefn cofrestru
|
||||
review: Ein hadolygiad
|
||||
rules: Derbyn rheolau
|
||||
providers:
|
||||
@@ -1378,6 +1379,7 @@ cy:
|
||||
invited_by: 'Gallwch ymuno â %{domain} diolch i''r gwahoddiad a gawsoch gan:'
|
||||
preamble: Mae'r rhain yn cael eu gosod a'u gorfodi gan y %{domain} cymedrolwyr.
|
||||
preamble_invited: Cyn i chi barhau, ystyriwch y rheolau sylfaenol a osodwyd gan gymedrolwyr %{domain}.
|
||||
read_more: Darllen rhagor
|
||||
title: Rhai rheolau sylfaenol.
|
||||
title_invited: Rydych wedi derbyn gwahoddiad.
|
||||
security: Diogelwch
|
||||
|
||||
@@ -778,6 +778,8 @@ da:
|
||||
administrator_description: Brugere med denne rolle kan omgå alle tilladelser
|
||||
delete_user_data: Slet brugerdata
|
||||
delete_user_data_description: Tillader brugere at slette andre brugeres data straks
|
||||
invite_bypass_approval: Invitere brugere uden gennemgang
|
||||
invite_bypass_approval_description: Gør det muligt for personer, der er inviteret til serveren af disse brugere, at undgå moderatorgodkendelse
|
||||
invite_users: Invitér brugere
|
||||
invite_users_description: Tillader brugere at invitere nye personer til serveren
|
||||
manage_announcements: Administrer annonceringer
|
||||
|
||||
@@ -778,6 +778,8 @@ de:
|
||||
administrator_description: Beschränkung aller Berechtigungen umgehen
|
||||
delete_user_data: Kontodaten löschen
|
||||
delete_user_data_description: Daten anderer Profile ohne Verzögerung löschen
|
||||
invite_bypass_approval: Einladungen ohne Überprüfungen
|
||||
invite_bypass_approval_description: Neue Benutzer*innen zur Registrierung auf diesem Server einladen, ohne manuell genehmigt werden zu müssen
|
||||
invite_users: Einladungen
|
||||
invite_users_description: Neue Benutzer*innen zur Registrierung auf diesem Server einladen
|
||||
manage_announcements: Ankündigungen
|
||||
|
||||
@@ -778,6 +778,8 @@ el:
|
||||
administrator_description: Οι χρήστες με αυτό το δικαίωμα θα παρακάμπτουν κάθε δικαίωμα
|
||||
delete_user_data: Διαγραφή Δεδομένων Χρήστη
|
||||
delete_user_data_description: Επιτρέπει στους χρήστες να διαγράφουν τα δεδομένα άλλων χρηστών χωρίς καθυστέρηση
|
||||
invite_bypass_approval: Πρόσκληση Χρηστών χωρίς έλεγχο
|
||||
invite_bypass_approval_description: Επιτρέπει σε άτομα που προσκαλούνται στον διακομιστή από αυτούς τους χρήστες, να παρακάμψουν την έγκριση από συντονιστές
|
||||
invite_users: Πρόσκληση Χρηστών
|
||||
invite_users_description: Επιτρέπει στους χρήστες να προσκαλούν νέα άτομα στον διακομιστή
|
||||
manage_announcements: Διαχείριση Ανακοινώσεων
|
||||
|
||||
@@ -778,6 +778,8 @@ es-AR:
|
||||
administrator_description: Los usuarios con este permiso saltarán todos los permisos
|
||||
delete_user_data: Eliminar datos del usuario
|
||||
delete_user_data_description: Permite a los usuarios eliminar los datos de otros usuarios sin demora
|
||||
invite_bypass_approval: Invitar a usuarios sin revisión
|
||||
invite_bypass_approval_description: Permite —a las personas invitadas al servidor por estos usuarios— eludir la aprobación de moderación
|
||||
invite_users: Invitar usuarios
|
||||
invite_users_description: Permite a los usuarios invitar a nuevas personas al servidor
|
||||
manage_announcements: Administrar anuncios
|
||||
|
||||
@@ -778,6 +778,8 @@ es-MX:
|
||||
administrator_description: Los usuarios con este permiso saltarán todos los permisos
|
||||
delete_user_data: Borrar Datos de Usuario
|
||||
delete_user_data_description: Permite a los usuarios eliminar los datos de otros usuarios sin demora
|
||||
invite_bypass_approval: Invitar a usuarios sin revisión
|
||||
invite_bypass_approval_description: Permite que las personas invitadas al servidor por estos usuarios no tengan que pasar por el proceso de aprobación de la moderación
|
||||
invite_users: Invitar usuarios
|
||||
invite_users_description: Permite a los usuarios invitar a nuevas personas al servidor
|
||||
manage_announcements: Administrar Anuncios
|
||||
@@ -1295,6 +1297,7 @@ es-MX:
|
||||
invited_by: 'Puedes unirte a %{domain} gracias a la invitación que has recibido de:'
|
||||
preamble: Estas son establecidas y aplicadas por los moderadores de %{domain}.
|
||||
preamble_invited: Antes de continuar, por favor, revisa las reglas básicas establecidas por los moderadores de %{domain}.
|
||||
read_more: Leer más
|
||||
title: Algunas reglas básicas.
|
||||
title_invited: Has sido invitado.
|
||||
security: Cambiar contraseña
|
||||
|
||||
@@ -778,6 +778,8 @@ es:
|
||||
administrator_description: Los usuarios con este permiso saltarán todos los permisos
|
||||
delete_user_data: Borrar Datos de Usuario
|
||||
delete_user_data_description: Permite a los usuarios eliminar los datos de otros usuarios sin demora
|
||||
invite_bypass_approval: Invitar usuarios sin revisión
|
||||
invite_bypass_approval_description: Permite registrarse a las personas invitadas al servidor por estos usuarios sin la aprobación de la moderación
|
||||
invite_users: Invitar usuarios
|
||||
invite_users_description: Permite a los usuarios invitar a nuevas personas al servidor
|
||||
manage_announcements: Administrar Anuncios
|
||||
@@ -1279,6 +1281,7 @@ es:
|
||||
progress:
|
||||
confirm: Confirmar dirección de correo
|
||||
details: Tus detalles
|
||||
list: Progreso de registro
|
||||
review: Nuestra revisión
|
||||
rules: Aceptar reglas
|
||||
providers:
|
||||
@@ -1294,6 +1297,7 @@ es:
|
||||
invited_by: 'Puedes unirte a %{domain} gracias a la invitación que has recibido de:'
|
||||
preamble: Estas son establecidas y aplicadas por los moderadores de %{domain}.
|
||||
preamble_invited: Antes de continuar, por favor, revisa las reglas básicas establecidas por los moderadores de %{domain}.
|
||||
read_more: Leer más
|
||||
title: Algunas reglas básicas.
|
||||
title_invited: Has sido invitado.
|
||||
security: Cambiar contraseña
|
||||
|
||||
@@ -1295,6 +1295,7 @@ fi:
|
||||
invited_by: 'Voit liittyä palvelimelle %{domain} kutsulla, jonka sait seuraavalta käyttäjältä:'
|
||||
preamble: Palvelimen %{domain} moderaattorit määrittävät ja valvovat sääntöjä.
|
||||
preamble_invited: Ennen kuin jatkat ota huomioon palvelimen %{domain} moderaattorien asettamat perussäännöt.
|
||||
read_more: Lue lisää
|
||||
title: Joitakin perussääntöjä.
|
||||
title_invited: Sinut on kutsuttu.
|
||||
security: Turvallisuus
|
||||
|
||||
@@ -1298,6 +1298,7 @@ fr-CA:
|
||||
invited_by: 'Vous pouvez rejoindre %{domain} grâve à l''invitation reçue de:'
|
||||
preamble: Celles-ci sont définies et appliqués par les modérateurs de %{domain}.
|
||||
preamble_invited: Avant de continuer, veuillez lire les règles de base définies par les modérateurs de %{domain}.
|
||||
read_more: Lire plus
|
||||
title: Quelques règles de base.
|
||||
title_invited: Vous avez été invité·e.
|
||||
security: Sécurité
|
||||
|
||||
@@ -1298,6 +1298,7 @@ fr:
|
||||
invited_by: 'Vous pouvez rejoindre %{domain} grâce à l''invitation de :'
|
||||
preamble: Celles-ci sont définies et appliqués par les modérateurs de %{domain}.
|
||||
preamble_invited: Avant de continuer, veuillez lire les règles de base définies par les modérateurs de %{domain}.
|
||||
read_more: Lire plus
|
||||
title: Quelques règles de base.
|
||||
title_invited: Vous avez été invité·e.
|
||||
security: Sécurité
|
||||
|
||||
@@ -826,6 +826,8 @@ ga:
|
||||
administrator_description: Seachnóidh úsáideoirí a bhfuil an cead seo acu gach cead
|
||||
delete_user_data: Scrios Sonraí Úsáideora
|
||||
delete_user_data_description: Ligeann sé d'úsáideoirí sonraí úsáideoirí eile a scriosadh gan mhoill
|
||||
invite_bypass_approval: Tabhair cuireadh d'úsáideoirí gan athbhreithniú
|
||||
invite_bypass_approval_description: Ceadaíonn sé do dhaoine a bhfuil cuireadh tugtha dóibh chuig an bhfreastalaí ag na húsáideoirí seo ceadú modhnóireachta a sheachaint
|
||||
invite_users: Tabhair cuireadh d'Úsáideoirí
|
||||
invite_users_description: Ligeann sé d'úsáideoirí cuireadh a thabhairt do dhaoine nua chuig an bhfreastalaí
|
||||
manage_announcements: Bainistigh Fógraí
|
||||
@@ -1360,6 +1362,7 @@ ga:
|
||||
invited_by: 'Is féidir leat páirt a ghlacadh i %{domain} a bhuíochas leis an gcuireadh a fuair tú ó:'
|
||||
preamble: Socraíonn agus cuireann na modhnóirí %{domain} iad seo i bhfeidhm.
|
||||
preamble_invited: Sula dtéann tú ar aghaidh, smaoinigh le do thoil ar na bunrialacha atá socraithe ag modhnóirí %{domain}.
|
||||
read_more: Léigh tuilleadh
|
||||
title: Roinnt bunrialacha.
|
||||
title_invited: Tá cuireadh faighte agat.
|
||||
security: Slándáil
|
||||
|
||||
@@ -778,6 +778,8 @@ gl:
|
||||
administrator_description: As usuarias con este permiso poderán superar calquera restrición
|
||||
delete_user_data: Eliminar datos de usuarias
|
||||
delete_user_data_description: Permite eliminar datos doutras usuarias sen demoras
|
||||
invite_bypass_approval: Invitar sen precisar revisión
|
||||
invite_bypass_approval_description: Permitir que as persoas invitadas ao servidor por estas usuarias non precisen aprobación
|
||||
invite_users: Convidar usuarias
|
||||
invite_users_description: Permite que outras usuarias conviden a xente ao servidor
|
||||
manage_announcements: Xestionar anuncios
|
||||
|
||||
@@ -810,6 +810,8 @@ he:
|
||||
administrator_description: משתמשים עם הרשאה זו יוכלו לעקוף כל הרשאה
|
||||
delete_user_data: מחיקת כל נתוני המשתמש
|
||||
delete_user_data_description: מאפשר למשתמשים למחוק נתוני משתמשים אחרים ללא דיחוי
|
||||
invite_bypass_approval: הזמנה להרשם ללא הליך בדיקה
|
||||
invite_bypass_approval_description: להרשות למשתמשים אלו להזמין א.נשים להרשם לשרת תוך עקיפת מנגנון הפיקוח על ההרשמה
|
||||
invite_users: הזמנת משתמשים
|
||||
invite_users_description: מאפשר למשתמשים להזמין אנשים חדשים לשרת
|
||||
manage_announcements: ניהול הכרזות
|
||||
@@ -1337,6 +1339,7 @@ he:
|
||||
invited_by: 'ניתן להצטרף אל %{domain} הודות להזמנה מאת:'
|
||||
preamble: אלו נקבעים ונאכפים ע"י המנחים של %{domain}.
|
||||
preamble_invited: לפני ההמשך יש להתחשב בחוקי המקום כפי שקבעו מנהלי הדיון על %{domain}.
|
||||
read_more: לקריאה נוספת
|
||||
title: כמה חוקים בסיסיים.
|
||||
title_invited: קיבלת הזמנה.
|
||||
security: אבטחה
|
||||
|
||||
@@ -1279,6 +1279,7 @@ hu:
|
||||
progress:
|
||||
confirm: E-mail megerősítése
|
||||
details: Saját adatok
|
||||
list: Regisztrációs folyamat
|
||||
review: A felülvizsgálatunk
|
||||
rules: Szabályok elfogadása
|
||||
providers:
|
||||
@@ -1294,6 +1295,7 @@ hu:
|
||||
invited_by: 'Csatlakozhatsz a %{domain} kiszolgálóhoz, köszönhetően a meghívónak, melyet tőle kaptál:'
|
||||
preamble: Ezeket a(z) %{domain} moderátorai adjak meg és tartatják be.
|
||||
preamble_invited: Mielőtt csatlakozol, kérlek vedd fontolóra a %{domain} moderátorai által állított szabályokat.
|
||||
read_more: Bővebben
|
||||
title: Néhány alapszabály.
|
||||
title_invited: Meghívtak.
|
||||
security: Biztonság
|
||||
|
||||
@@ -778,6 +778,8 @@ is:
|
||||
administrator_description: Notendur með þessa heimild fara framhjá öllum öðrum heimildum
|
||||
delete_user_data: Eyða gögnum notanda
|
||||
delete_user_data_description: Leyfir notendum að eyða gögnum annarra notenda án tafar
|
||||
invite_bypass_approval: Bjóða notendum án samþykktar
|
||||
invite_bypass_approval_description: Leyfir fólki sem boðið er á netþjóninn af þessum notendum að sleppa við samþykki umsjónaraðila
|
||||
invite_users: Bjóða notendum
|
||||
invite_users_description: Leyfir notendum að bjóða nýju fólki inn á netþjóninn
|
||||
manage_announcements: Sýsla með tilkynningar
|
||||
|
||||
@@ -778,6 +778,8 @@ it:
|
||||
administrator_description: Gli utenti con questo permesso saranno esentati da ogni permesso
|
||||
delete_user_data: Cancella dati utente
|
||||
delete_user_data_description: Consente agli utenti di eliminare subito i dati degli altri utenti
|
||||
invite_bypass_approval: Invita utenti senza revisione
|
||||
invite_bypass_approval_description: Permette alle persone invitate al server da questi utenti, di aggirare l'approvazione della moderazione
|
||||
invite_users: Invita Utenti
|
||||
invite_users_description: Consente agli utenti di invitare nuove persone su questo server
|
||||
manage_announcements: Gestisci Annunci
|
||||
|
||||
@@ -587,6 +587,7 @@ lt:
|
||||
manage_taxonomies_description: Leidžia naudotojams peržiūrėti tendencingą turinį ir atnaujinti grotažymių nustatymus
|
||||
settings:
|
||||
branding:
|
||||
preamble: Jūsų serverio pavadinimas išskiria jį iš kitų tinklo serverių. Ši informacija gali būti rodoma įvairiose aplinkose, pavyzdžiui, „Mastodon“ žiniatinklio sąsajoje, programėlėse, nuorodų peržiūrose kitose svetainėse, žinučių programėlėse ir pan. Dėl to geriausia, kad ši informacija būtų aiški, trumpa ir glausta.
|
||||
title: Firminio ženklo kūrimas
|
||||
captcha_enabled:
|
||||
desc_html: Tai priklauso nuo hCaptcha išorinių skriptų, kurie gali kelti susirūpinimą dėl saugumo ir privatumo. Be to, <strong>dėl to registracijos procesas kai kuriems žmonėms (ypač neįgaliesiems) gali būti gerokai sunkiau prieinami</strong>. Dėl šių priežasčių apsvarstyk alternatyvias priemones, pavyzdžiui, patvirtinimu arba kvietimu grindžiamą registraciją.
|
||||
|
||||
@@ -1279,6 +1279,7 @@ nn:
|
||||
progress:
|
||||
confirm: Stadfest e-post
|
||||
details: Opplysingane dine
|
||||
list: Innmeldingsprosess
|
||||
review: Vår gjennomgang
|
||||
rules: Godta reglane
|
||||
providers:
|
||||
@@ -1294,6 +1295,7 @@ nn:
|
||||
invited_by: 'Du kan bli med i %{domain} takka vere invitasjonen du har fått frå:'
|
||||
preamble: Disse angis og håndheves av %{domain}-moderatorene.
|
||||
preamble_invited: Før du held fram, ver snill og sjå gjennom reglane bestemt av moderatorane av %{domain}.
|
||||
read_more: Les meir
|
||||
title: Nokre grunnreglar.
|
||||
title_invited: Du har blitt invitert.
|
||||
security: Tryggleik
|
||||
|
||||
@@ -40,14 +40,14 @@ be:
|
||||
text: Вы можаце абскардзіць рашэнне толькі адзін раз
|
||||
defaults:
|
||||
autofollow: Людзі, якія зарэгістраваліся праз запрашэнне, аўтаматычна падпішуцца на вас
|
||||
avatar: WEBP, PNG, GIF ці JPG. Не больш за %{size}. Будзе сціснуты да памеру %{dimensions}} пікселяў
|
||||
avatar: WEBP, PNG, GIF ці JPG. Не больш за %{size}. Будзе сціснуты да памеру %{dimensions} пікселяў
|
||||
bot: Паведаміць іншым, што гэты ўліковы запіс у асноўным выконвае аўтаматычныя дзеянні і можа не кантралявацца
|
||||
context: Адзін ці некалькі кантэкстаў, да якіх трэба прымяніць фільтр
|
||||
current_password: У мэтах бяспекі, увядзіце пароль бягучага ўліковага запісу
|
||||
current_username: Каб пацвердзіць, увядзіце, калі ласка імя карыстальніка бягучага ўліковага запісу
|
||||
digest: Будзе даслана толькі пасля доўгага перыяду неактыўнасці і толькі, калі Вы атрымалі асабістыя паведамленні падчас Вашай адсутнасці
|
||||
email: Пацвярджэнне будзе выслана па электроннай пошце
|
||||
header: WEBP, PNG, GIF ці JPG. Не больш за %{size}. Будзе сціснуты да памеру %{dimensions}} пікселяў
|
||||
header: WEBP, PNG, GIF ці JPG. Не больш за %{size}. Будзе сціснуты да памеру %{dimensions} пікселяў
|
||||
inbox_url: Капіраваць URL са старонкі рэтранслятара, якім вы хочаце карыстацца
|
||||
irreversible: Адфільтраваныя пасты прападуць незваротна, нават калі фільтр потым будзе выдалены
|
||||
locale: Мова карыстальніцкага інтэрфейсу, электронных паведамленняў і апавяшчэнняў
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user