Merge remote-tracking branch 'parent/main' into kbtopic-remove-quote
This commit is contained in:
commit
f3c3ea42c2
301 changed files with 6618 additions and 3070 deletions
|
@ -2,6 +2,7 @@ import { browserHistory } from 'mastodon/components/router';
|
|||
import { debounceWithDispatchAndArguments } from 'mastodon/utils/debounce';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
import { me } from '../initial_state';
|
||||
|
||||
import {
|
||||
followAccountSuccess, unfollowAccountSuccess,
|
||||
|
@ -12,6 +13,7 @@ import {
|
|||
blockAccountSuccess, unblockAccountSuccess,
|
||||
pinAccountSuccess, unpinAccountSuccess,
|
||||
fetchRelationshipsSuccess,
|
||||
fetchEndorsedAccounts,
|
||||
} from './accounts_typed';
|
||||
import { importFetchedAccount, importFetchedAccounts } from './importer';
|
||||
|
||||
|
@ -634,6 +636,7 @@ export function pinAccount(id) {
|
|||
|
||||
api().post(`/api/v1/accounts/${id}/pin`).then(response => {
|
||||
dispatch(pinAccountSuccess({ relationship: response.data }));
|
||||
dispatch(fetchEndorsedAccounts({ accountId: me }));
|
||||
}).catch(error => {
|
||||
dispatch(pinAccountFail(error));
|
||||
});
|
||||
|
@ -646,6 +649,7 @@ export function unpinAccount(id) {
|
|||
|
||||
api().post(`/api/v1/accounts/${id}/unpin`).then(response => {
|
||||
dispatch(unpinAccountSuccess({ relationship: response.data }));
|
||||
dispatch(fetchEndorsedAccounts({ accountId: me }));
|
||||
}).catch(error => {
|
||||
dispatch(unpinAccountFail(error));
|
||||
});
|
||||
|
|
7
app/javascript/mastodon/actions/navigation.ts
Normal file
7
app/javascript/mastodon/actions/navigation.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
export const openNavigation = createAction('navigation/open');
|
||||
|
||||
export const closeNavigation = createAction('navigation/close');
|
||||
|
||||
export const toggleNavigation = createAction('navigation/toggle');
|
|
@ -121,10 +121,15 @@ export const clickSearchResult = createAppAsyncThunk(
|
|||
|
||||
export const forgetSearchResult = createAppAsyncThunk(
|
||||
'search/forgetResult',
|
||||
(q: string, { dispatch, getState }) => {
|
||||
(
|
||||
{ q, type }: { q: string; type?: RecentSearchType },
|
||||
{ dispatch, getState },
|
||||
) => {
|
||||
const previous = getState().search.recent;
|
||||
const me = getState().meta.get('me') as string;
|
||||
const current = previous.filter((result) => result.q !== q);
|
||||
const current = previous.filter(
|
||||
(result) => result.q !== q || result.type !== type,
|
||||
);
|
||||
|
||||
searchHistory.set(me, current);
|
||||
dispatch(updateSearchHistory(current));
|
||||
|
|
|
@ -14,6 +14,8 @@ import {
|
|||
muteAccount,
|
||||
unmuteAccount,
|
||||
followAccountSuccess,
|
||||
unpinAccount,
|
||||
pinAccount,
|
||||
} from 'mastodon/actions/accounts';
|
||||
import { showAlertForError } from 'mastodon/actions/alerts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
|
@ -64,7 +66,7 @@ const messages = defineMessages({
|
|||
},
|
||||
});
|
||||
|
||||
export const Account: React.FC<{
|
||||
interface AccountProps {
|
||||
size?: number;
|
||||
id: string;
|
||||
hidden?: boolean;
|
||||
|
@ -73,7 +75,10 @@ export const Account: React.FC<{
|
|||
withBio?: boolean;
|
||||
hideButtons?: boolean;
|
||||
children?: ReactNode;
|
||||
}> = ({
|
||||
withMenu?: boolean;
|
||||
}
|
||||
|
||||
export const Account: React.FC<AccountProps> = ({
|
||||
id,
|
||||
size = 46,
|
||||
hidden,
|
||||
|
@ -82,6 +87,7 @@ export const Account: React.FC<{
|
|||
withBio,
|
||||
hideButtons,
|
||||
children,
|
||||
withMenu = true,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { signedIn } = useIdentity();
|
||||
|
@ -132,8 +138,6 @@ export const Account: React.FC<{
|
|||
},
|
||||
];
|
||||
} else if (defaultAction !== 'block') {
|
||||
arr = [];
|
||||
|
||||
if (isRemote && accountUrl) {
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.openOriginalPage),
|
||||
|
@ -186,6 +190,25 @@ export const Account: React.FC<{
|
|||
text: intl.formatMessage(messages.addToLists),
|
||||
action: handleAddToLists,
|
||||
});
|
||||
|
||||
if (id !== me && (relationship?.following || relationship?.requested)) {
|
||||
const handleEndorseToggle = () => {
|
||||
if (relationship.endorsed) {
|
||||
dispatch(unpinAccount(id));
|
||||
} else {
|
||||
dispatch(pinAccount(id));
|
||||
}
|
||||
};
|
||||
arr.push({
|
||||
text: intl.formatMessage(
|
||||
// Defined in features/account_timeline/components/account_header.tsx
|
||||
relationship.endorsed
|
||||
? { id: 'account.unendorse' }
|
||||
: { id: 'account.endorse' },
|
||||
),
|
||||
action: handleEndorseToggle,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,9 +233,10 @@ export const Account: React.FC<{
|
|||
);
|
||||
}
|
||||
|
||||
let button: React.ReactNode, dropdown: React.ReactNode;
|
||||
let button: React.ReactNode;
|
||||
let dropdown: React.ReactNode;
|
||||
|
||||
if (menu.length > 0) {
|
||||
if (menu.length > 0 && withMenu) {
|
||||
dropdown = (
|
||||
<Dropdown
|
||||
items={menu}
|
||||
|
@ -268,43 +292,69 @@ export const Account: React.FC<{
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<Link
|
||||
className='account__display-name'
|
||||
title={account?.acct}
|
||||
to={`/@${account?.acct}`}
|
||||
data-hover-card-account={id}
|
||||
>
|
||||
<div className='account__avatar-wrapper'>
|
||||
{account ? (
|
||||
<Avatar account={account} size={size} />
|
||||
<div
|
||||
className={classNames('account', {
|
||||
'account--minimal': minimal,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames('account__wrapper', {
|
||||
'account__wrapper--with-bio': account && withBio,
|
||||
})}
|
||||
>
|
||||
<div className='account__info-wrapper'>
|
||||
<Link
|
||||
className='account__display-name'
|
||||
title={account?.acct}
|
||||
to={`/@${account?.acct}`}
|
||||
data-hover-card-account={id}
|
||||
>
|
||||
<div className='account__avatar-wrapper'>
|
||||
{account ? (
|
||||
<Avatar account={account} size={size} />
|
||||
) : (
|
||||
<Skeleton width={size} height={size} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='account__contents'>
|
||||
<DisplayName account={account} />
|
||||
|
||||
{!minimal && (
|
||||
<div className='account__details'>
|
||||
{account ? (
|
||||
<>
|
||||
<ShortNumber
|
||||
value={account.followers_count}
|
||||
renderer={FollowersCounter}
|
||||
isHide={account.other_settings.hide_followers_count}
|
||||
/>{' '}
|
||||
{verification} {muteTimeRemaining}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton width='7ch' />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{account &&
|
||||
withBio &&
|
||||
(account.note.length > 0 ? (
|
||||
<div
|
||||
className='account__note translate'
|
||||
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
|
||||
/>
|
||||
) : (
|
||||
<Skeleton width={size} height={size} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='account__contents'>
|
||||
<DisplayName account={account} />
|
||||
|
||||
{!minimal && (
|
||||
<div className='account__details'>
|
||||
{account ? (
|
||||
<>
|
||||
<ShortNumber
|
||||
value={account.followers_count}
|
||||
renderer={FollowersCounter}
|
||||
isHide={account.other_settings.hide_followers_count}
|
||||
/>{' '}
|
||||
{verification} {muteTimeRemaining}
|
||||
</>
|
||||
) : (
|
||||
<Skeleton width='7ch' />
|
||||
)}
|
||||
<div className='account__note account__note--missing'>
|
||||
<FormattedMessage
|
||||
id='account.no_bio'
|
||||
defaultMessage='No description provided.'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!minimal && children && (
|
||||
<div>
|
||||
|
@ -322,22 +372,6 @@ export const Account: React.FC<{
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{account &&
|
||||
withBio &&
|
||||
(account.note.length > 0 ? (
|
||||
<div
|
||||
className='account__note translate'
|
||||
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
|
||||
/>
|
||||
) : (
|
||||
<div className='account__note account__note--missing'>
|
||||
<FormattedMessage
|
||||
id='account.no_bio'
|
||||
defaultMessage='No description provided.'
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ interface Props {
|
|||
withLink?: boolean;
|
||||
counter?: number | string;
|
||||
counterBorderColor?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<Props> = ({
|
||||
|
@ -27,6 +28,7 @@ export const Avatar: React.FC<Props> = ({
|
|||
inline = false,
|
||||
withLink = false,
|
||||
style: styleFromParent,
|
||||
className,
|
||||
counter,
|
||||
counterBorderColor,
|
||||
}) => {
|
||||
|
@ -52,7 +54,7 @@ export const Avatar: React.FC<Props> = ({
|
|||
|
||||
const avatar = (
|
||||
<div
|
||||
className={classNames('account__avatar', {
|
||||
className={classNames(className, 'account__avatar', {
|
||||
'account__avatar--inline': inline,
|
||||
'account__avatar--loading': loading,
|
||||
})}
|
||||
|
|
97
app/javascript/mastodon/components/button/button.stories.tsx
Normal file
97
app/javascript/mastodon/components/button/button.stories.tsx
Normal file
|
@ -0,0 +1,97 @@
|
|||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { fn, expect } from 'storybook/test';
|
||||
|
||||
import { Button } from '.';
|
||||
|
||||
const meta = {
|
||||
title: 'Components/Button',
|
||||
component: Button,
|
||||
args: {
|
||||
secondary: false,
|
||||
compact: false,
|
||||
dangerous: false,
|
||||
disabled: false,
|
||||
onClick: fn(),
|
||||
},
|
||||
argTypes: {
|
||||
text: {
|
||||
control: 'text',
|
||||
type: 'string',
|
||||
description:
|
||||
'Alternative way of specifying the button label. Will override `children` if provided.',
|
||||
},
|
||||
type: {
|
||||
type: 'string',
|
||||
control: 'text',
|
||||
table: {
|
||||
type: { summary: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['test'],
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const buttonTest: Story['play'] = async ({ args, canvas, userEvent }) => {
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(args.onClick).toHaveBeenCalled();
|
||||
};
|
||||
|
||||
const disabledButtonTest: Story['play'] = async ({
|
||||
args,
|
||||
canvas,
|
||||
userEvent,
|
||||
}) => {
|
||||
await userEvent.click(canvas.getByRole('button'));
|
||||
await expect(args.onClick).not.toHaveBeenCalled();
|
||||
};
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
children: 'Primary button',
|
||||
},
|
||||
play: buttonTest,
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
secondary: true,
|
||||
children: 'Secondary button',
|
||||
},
|
||||
play: buttonTest,
|
||||
};
|
||||
|
||||
export const Compact: Story = {
|
||||
args: {
|
||||
compact: true,
|
||||
children: 'Compact button',
|
||||
},
|
||||
play: buttonTest,
|
||||
};
|
||||
|
||||
export const Dangerous: Story = {
|
||||
args: {
|
||||
dangerous: true,
|
||||
children: 'Dangerous button',
|
||||
},
|
||||
play: buttonTest,
|
||||
};
|
||||
|
||||
export const PrimaryDisabled: Story = {
|
||||
args: {
|
||||
...Primary.args,
|
||||
disabled: true,
|
||||
},
|
||||
play: disabledButtonTest,
|
||||
};
|
||||
|
||||
export const SecondaryDisabled: Story = {
|
||||
args: {
|
||||
...Secondary.args,
|
||||
disabled: true,
|
||||
},
|
||||
play: disabledButtonTest,
|
||||
};
|
|
@ -22,6 +22,10 @@ interface PropsWithText extends BaseProps {
|
|||
|
||||
type Props = PropsWithText | PropsChildren;
|
||||
|
||||
/**
|
||||
* Primary UI component for user interaction that doesn't result in navigation.
|
||||
*/
|
||||
|
||||
export const Button: React.FC<Props> = ({
|
||||
type = 'button',
|
||||
onClick,
|
|
@ -9,7 +9,8 @@ import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react';
|
|||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import UnfoldLessIcon from '@/material-icons/400-24px/unfold_less.svg?react';
|
||||
import UnfoldMoreIcon from '@/material-icons/400-24px/unfold_more.svg?react';
|
||||
import type { IconProp } from 'mastodon/components/icon';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
|
||||
|
@ -238,7 +239,10 @@ export const ColumnHeader: React.FC<Props> = ({
|
|||
onClick={handleToggleClick}
|
||||
>
|
||||
<i className='icon-with-badge'>
|
||||
<Icon id='sliders' icon={SettingsIcon} />
|
||||
<Icon
|
||||
id='sliders'
|
||||
icon={collapsed ? UnfoldMoreIcon : UnfoldLessIcon}
|
||||
/>
|
||||
{collapseIssues && <i className='icon-with-badge__issue-badge' />}
|
||||
</i>
|
||||
</button>
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import type { ComponentPropsWithRef } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useId,
|
||||
} from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
|
@ -11,11 +18,14 @@ import { animated, useSpring } from '@react-spring/web';
|
|||
import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import { expandAccountFeaturedTimeline } from '@/mastodon/actions/timelines';
|
||||
import { Icon } from '@/mastodon/components/icon';
|
||||
import { IconButton } from '@/mastodon/components/icon_button';
|
||||
import StatusContainer from '@/mastodon/containers/status_container';
|
||||
import { usePrevious } from '@/mastodon/hooks/usePrevious';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react';
|
||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
|
||||
|
||||
const messages = defineMessages({
|
||||
previous: { id: 'featured_carousel.previous', defaultMessage: 'Previous' },
|
||||
|
@ -31,6 +41,7 @@ export const FeaturedCarousel: React.FC<{
|
|||
tagged?: string;
|
||||
}> = ({ accountId, tagged }) => {
|
||||
const intl = useIntl();
|
||||
const accessibilityId = useId();
|
||||
|
||||
// Load pinned statuses
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -74,6 +85,7 @@ export const FeaturedCarousel: React.FC<{
|
|||
const [currentSlideHeight, setCurrentSlideHeight] = useState(
|
||||
wrapperRef.current?.scrollHeight ?? 0,
|
||||
);
|
||||
const previousSlideHeight = usePrevious(currentSlideHeight);
|
||||
const observerRef = useRef<ResizeObserver>(
|
||||
new ResizeObserver(() => {
|
||||
handleSlideChange(0);
|
||||
|
@ -82,8 +94,10 @@ export const FeaturedCarousel: React.FC<{
|
|||
const wrapperStyles = useSpring({
|
||||
x: `-${slideIndex * 100}%`,
|
||||
height: currentSlideHeight,
|
||||
// Don't animate from zero to the height of the initial slide
|
||||
immediate: !previousSlideHeight,
|
||||
});
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
// Update slide height when the component mounts
|
||||
if (currentSlideHeight === 0) {
|
||||
handleSlideChange(0);
|
||||
|
@ -110,11 +124,15 @@ export const FeaturedCarousel: React.FC<{
|
|||
className='featured-carousel'
|
||||
{...bind()}
|
||||
aria-roledescription='carousel'
|
||||
aria-labelledby='featured-carousel-title'
|
||||
aria-labelledby={`${accessibilityId}-title`}
|
||||
role='region'
|
||||
>
|
||||
<div className='featured-carousel__header'>
|
||||
<h4 className='featured-carousel__title' id='featured-carousel-title'>
|
||||
<h4
|
||||
className='featured-carousel__title'
|
||||
id={`${accessibilityId}-title`}
|
||||
>
|
||||
<Icon id='thumb-tack' icon={PushPinIcon} />
|
||||
<FormattedMessage
|
||||
id='featured_carousel.header'
|
||||
defaultMessage='{count, plural, one {Pinned Post} other {Pinned Posts}}'
|
||||
|
|
|
@ -45,6 +45,19 @@ export const HoverCardAccount = forwardRef<
|
|||
|
||||
const { familiarFollowers } = useFetchFamiliarFollowers({ accountId });
|
||||
|
||||
const relationship = useAppSelector((state) =>
|
||||
accountId ? state.relationships.get(accountId) : undefined,
|
||||
);
|
||||
const isMutual = relationship?.followed_by && relationship.following;
|
||||
const isFollower = relationship?.followed_by;
|
||||
const hasRelationshipLoaded = !!relationship;
|
||||
|
||||
const shouldDisplayFamiliarFollowers =
|
||||
familiarFollowers.length > 0 &&
|
||||
hasRelationshipLoaded &&
|
||||
!isMutual &&
|
||||
!isFollower;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
|
@ -86,7 +99,7 @@ export const HoverCardAccount = forwardRef<
|
|||
renderer={FollowersCounter}
|
||||
isHide={account.other_settings.hide_followers_count}
|
||||
/>
|
||||
{familiarFollowers.length > 0 && (
|
||||
{shouldDisplayFamiliarFollowers && (
|
||||
<>
|
||||
·
|
||||
<div className='hover-card__familiar-followers'>
|
||||
|
@ -102,6 +115,22 @@ export const HoverCardAccount = forwardRef<
|
|||
</div>
|
||||
</>
|
||||
)}
|
||||
{(isMutual || isFollower) && (
|
||||
<>
|
||||
·
|
||||
{isMutual ? (
|
||||
<FormattedMessage
|
||||
id='account.mutual'
|
||||
defaultMessage='You follow each other'
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='account.follows_you'
|
||||
defaultMessage='Follows you'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FollowButton accountId={accountId} />
|
||||
|
|
|
@ -28,6 +28,7 @@ interface Props {
|
|||
href?: string;
|
||||
ariaHidden?: boolean;
|
||||
data_id?: string;
|
||||
ariaControls?: string;
|
||||
}
|
||||
|
||||
export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
||||
|
@ -54,6 +55,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
|||
tabIndex = 0,
|
||||
ariaHidden = false,
|
||||
data_id = undefined,
|
||||
ariaControls,
|
||||
},
|
||||
buttonRef,
|
||||
) => {
|
||||
|
@ -155,6 +157,7 @@ export const IconButton = forwardRef<HTMLButtonElement, Props>(
|
|||
aria-label={title}
|
||||
aria-expanded={expanded}
|
||||
aria-hidden={ariaHidden}
|
||||
aria-controls={ariaControls}
|
||||
title={title}
|
||||
className={classes}
|
||||
onClick={handleClick}
|
||||
|
|
|
@ -7,7 +7,7 @@ interface Props {
|
|||
id: string;
|
||||
icon: IconProp;
|
||||
count: number;
|
||||
issueBadge: boolean;
|
||||
issueBadge?: boolean;
|
||||
className: string;
|
||||
}
|
||||
export const IconWithBadge: React.FC<Props> = ({
|
||||
|
|
|
@ -14,7 +14,6 @@ import { fetchPoll, vote } from 'mastodon/actions/polls';
|
|||
import { Icon } from 'mastodon/components/icon';
|
||||
import emojify from 'mastodon/features/emoji/emoji';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import { reduceMotion } from 'mastodon/initial_state';
|
||||
import { makeEmojiMap } from 'mastodon/models/custom_emoji';
|
||||
import type * as Model from 'mastodon/models/poll';
|
||||
import type { Status } from 'mastodon/models/status';
|
||||
|
@ -265,7 +264,6 @@ const PollOption: React.FC<PollOptionProps> = (props) => {
|
|||
to: {
|
||||
width: `${percent}%`,
|
||||
},
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -624,11 +624,11 @@ class Status extends ImmutablePureComponent {
|
|||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
{children}
|
||||
|
||||
{media}
|
||||
{hashtagBar}
|
||||
{emojiReactionsBar}
|
||||
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ interface BaseRule {
|
|||
|
||||
interface Rule extends BaseRule {
|
||||
id: string;
|
||||
translations: Record<string, BaseRule>;
|
||||
translations?: Record<string, BaseRule>;
|
||||
}
|
||||
|
||||
export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
|
||||
|
@ -113,15 +113,23 @@ const rulesSelector = createSelector(
|
|||
(rules, locale): Rule[] => {
|
||||
return rules.map((rule) => {
|
||||
const translations = rule.translations;
|
||||
if (translations[locale]) {
|
||||
rule.text = translations[locale].text;
|
||||
rule.hint = translations[locale].hint;
|
||||
|
||||
// Handle cached responses from earlier versions
|
||||
if (!translations) {
|
||||
return rule;
|
||||
}
|
||||
|
||||
const partialLocale = locale.split('-')[0];
|
||||
if (partialLocale && translations[partialLocale]) {
|
||||
rule.text = translations[partialLocale].text;
|
||||
rule.hint = translations[partialLocale].hint;
|
||||
}
|
||||
|
||||
if (translations[locale]) {
|
||||
rule.text = translations[locale].text;
|
||||
rule.hint = translations[locale].hint;
|
||||
}
|
||||
|
||||
return rule;
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { is } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' },
|
||||
});
|
||||
|
||||
class InlineAlert extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
show: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
mountMessage: false,
|
||||
};
|
||||
|
||||
static TRANSITION_DELAY = 200;
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
if (!this.props.show && nextProps.show) {
|
||||
this.setState({ mountMessage: true });
|
||||
} else if (this.props.show && !nextProps.show) {
|
||||
setTimeout(() => this.setState({ mountMessage: false }), InlineAlert.TRANSITION_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { show } = this.props;
|
||||
const { mountMessage } = this.state;
|
||||
|
||||
return (
|
||||
<span aria-live='polite' role='status' className='inline-alert' style={{ opacity: show ? 1 : 0 }}>
|
||||
{mountMessage && <FormattedMessage id='generic.saved' defaultMessage='Saved' />}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccountNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
value: PropTypes.string,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
value: null,
|
||||
saving: false,
|
||||
saved: false,
|
||||
};
|
||||
|
||||
UNSAFE_componentWillMount () {
|
||||
this._reset();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
const accountWillChange = !is(this.props.accountId, nextProps.accountId);
|
||||
const newState = {};
|
||||
|
||||
if (accountWillChange && this._isDirty()) {
|
||||
this._save(false);
|
||||
}
|
||||
|
||||
if (accountWillChange || nextProps.value === this.state.value) {
|
||||
newState.saving = false;
|
||||
}
|
||||
|
||||
if (this.props.value !== nextProps.value) {
|
||||
newState.value = nextProps.value;
|
||||
}
|
||||
|
||||
this.setState(newState);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this._isDirty()) {
|
||||
this._save(false);
|
||||
}
|
||||
}
|
||||
|
||||
setTextareaRef = c => {
|
||||
this.textarea = c;
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
this.setState({ value: e.target.value, saving: false });
|
||||
};
|
||||
|
||||
handleKeyDown = e => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.textarea) {
|
||||
this.textarea.blur();
|
||||
} else {
|
||||
this._save();
|
||||
}
|
||||
} else if (e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
|
||||
this._reset(() => {
|
||||
if (this.textarea) {
|
||||
this.textarea.blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
if (this._isDirty()) {
|
||||
this._save();
|
||||
}
|
||||
};
|
||||
|
||||
_save (showMessage = true) {
|
||||
this.setState({ saving: true }, () => this.props.onSave(this.state.value));
|
||||
|
||||
if (showMessage) {
|
||||
this.setState({ saved: true }, () => setTimeout(() => this.setState({ saved: false }), 2000));
|
||||
}
|
||||
}
|
||||
|
||||
_reset (callback) {
|
||||
this.setState({ value: this.props.value }, callback);
|
||||
}
|
||||
|
||||
_isDirty () {
|
||||
return !this.state.saving && this.props.value !== null && this.state.value !== null && this.state.value !== this.props.value;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accountId, intl } = this.props;
|
||||
const { value, saved } = this.state;
|
||||
|
||||
if (!accountId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account__header__account-note'>
|
||||
<label htmlFor={`account-note-${accountId}`}>
|
||||
<FormattedMessage id='account.account_note_header' defaultMessage='Personal note' /> <InlineAlert show={saved} />
|
||||
</label>
|
||||
|
||||
<Textarea
|
||||
id={`account-note-${accountId}`}
|
||||
className='account__header__account-note__content'
|
||||
disabled={this.props.value === null || value === null}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value || ''}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onBlur={this.handleBlur}
|
||||
ref={this.setTextareaRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(AccountNote);
|
|
@ -0,0 +1,131 @@
|
|||
import type { ChangeEventHandler, KeyboardEventHandler } from 'react';
|
||||
import { useState, useRef, useCallback, useId } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
|
||||
import { submitAccountNote } from '@/mastodon/actions/account_notes';
|
||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: {
|
||||
id: 'account_note.placeholder',
|
||||
defaultMessage: 'Click to add a note',
|
||||
},
|
||||
});
|
||||
|
||||
const AccountNoteUI: React.FC<{
|
||||
initialValue: string | undefined;
|
||||
onSubmit: (newNote: string) => void;
|
||||
wasSaved: boolean;
|
||||
}> = ({ initialValue, onSubmit, wasSaved }) => {
|
||||
const intl = useIntl();
|
||||
const uniqueId = useId();
|
||||
const [value, setValue] = useState(initialValue ?? '');
|
||||
const isLoading = initialValue === undefined;
|
||||
const canSubmitOnBlurRef = useRef(true);
|
||||
|
||||
const handleChange = useCallback<ChangeEventHandler<HTMLTextAreaElement>>(
|
||||
(e) => {
|
||||
setValue(e.target.value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback<KeyboardEventHandler<HTMLTextAreaElement>>(
|
||||
(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
|
||||
setValue(initialValue ?? '');
|
||||
|
||||
canSubmitOnBlurRef.current = false;
|
||||
e.currentTarget.blur();
|
||||
} else if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
|
||||
onSubmit(value);
|
||||
|
||||
canSubmitOnBlurRef.current = false;
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
},
|
||||
[initialValue, onSubmit, value],
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (initialValue !== value && canSubmitOnBlurRef.current) {
|
||||
onSubmit(value);
|
||||
}
|
||||
canSubmitOnBlurRef.current = true;
|
||||
}, [initialValue, onSubmit, value]);
|
||||
|
||||
return (
|
||||
<div className='account__header__account-note'>
|
||||
<label htmlFor={`account-note-${uniqueId}`}>
|
||||
<FormattedMessage
|
||||
id='account.account_note_header'
|
||||
defaultMessage='Personal note'
|
||||
/>{' '}
|
||||
<span
|
||||
aria-live='polite'
|
||||
role='status'
|
||||
className='inline-alert'
|
||||
style={{ opacity: wasSaved ? 1 : 0 }}
|
||||
>
|
||||
{wasSaved && (
|
||||
<FormattedMessage id='generic.saved' defaultMessage='Saved' />
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
{isLoading ? (
|
||||
<div className='account__header__account-note__loading-indicator-wrapper'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
) : (
|
||||
<Textarea
|
||||
id={`account-note-${uniqueId}`}
|
||||
className='account__header__account-note__content'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AccountNote: React.FC<{
|
||||
accountId: string;
|
||||
}> = ({ accountId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const initialValue = useAppSelector((state) =>
|
||||
state.relationships.get(accountId)?.get('note'),
|
||||
);
|
||||
const [wasSaved, setWasSaved] = useState(false);
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(note: string) => {
|
||||
setWasSaved(true);
|
||||
void dispatch(submitAccountNote({ accountId, note }));
|
||||
|
||||
setTimeout(() => {
|
||||
setWasSaved(false);
|
||||
}, 2000);
|
||||
},
|
||||
[dispatch, accountId],
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountNoteUI
|
||||
key={`${accountId}-${initialValue}`}
|
||||
initialValue={initialValue}
|
||||
wasSaved={wasSaved}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { submitAccountNote } from 'mastodon/actions/account_notes';
|
||||
|
||||
import AccountNote from '../components/account_note';
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
value: state.relationships.getIn([accountId, 'note']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
|
||||
onSave (value) {
|
||||
dispatch(submitAccountNote({ accountId: accountId, note: value }));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AccountNote);
|
|
@ -52,8 +52,8 @@ import { getFeaturedHashtagBar } from 'mastodon/components/hashtag_bar';
|
|||
import { Icon } from 'mastodon/components/icon';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { AccountNote } from 'mastodon/features/account/components/account_note';
|
||||
import { DomainPill } from 'mastodon/features/account/components/domain_pill';
|
||||
import AccountNoteContainer from 'mastodon/features/account/containers/account_note_container';
|
||||
import FollowRequestNoteContainer from 'mastodon/features/account/containers/follow_request_note_container';
|
||||
import { useLinks } from 'mastodon/hooks/useLinks';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
|
@ -490,7 +490,7 @@ export const AccountHeader: React.FC<{
|
|||
return arr;
|
||||
}
|
||||
|
||||
if (signedIn && account.id !== me && !account.suspended) {
|
||||
if (signedIn && !account.suspended) {
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.mention, {
|
||||
name: account.username,
|
||||
|
@ -514,37 +514,7 @@ export const AccountHeader: React.FC<{
|
|||
arr.push(null);
|
||||
}
|
||||
|
||||
if (account.id === me) {
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.edit_profile),
|
||||
href: '/settings/profile',
|
||||
});
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.preferences),
|
||||
href: '/settings/preferences',
|
||||
});
|
||||
arr.push(null);
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.follow_requests),
|
||||
to: '/follow_requests',
|
||||
});
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.favourites),
|
||||
to: '/favourites',
|
||||
});
|
||||
arr.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.followed_tags),
|
||||
to: '/followed_tags',
|
||||
});
|
||||
arr.push(null);
|
||||
arr.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
|
||||
arr.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
|
||||
arr.push({
|
||||
text: intl.formatMessage(messages.domain_blocks),
|
||||
to: '/domain_blocks',
|
||||
});
|
||||
} else if (signedIn) {
|
||||
if (signedIn) {
|
||||
if (relationship?.following) {
|
||||
if (!relationship.muting) {
|
||||
if (relationship.showing_reblogs) {
|
||||
|
@ -697,8 +667,7 @@ export const AccountHeader: React.FC<{
|
|||
}
|
||||
|
||||
if (
|
||||
(account.id !== me &&
|
||||
(permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) ||
|
||||
(permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS ||
|
||||
(isRemote &&
|
||||
(permissions & PERMISSION_MANAGE_FEDERATION) ===
|
||||
PERMISSION_MANAGE_FEDERATION)
|
||||
|
@ -969,19 +938,21 @@ export const AccountHeader: React.FC<{
|
|||
>
|
||||
<Avatar
|
||||
account={suspended || hidden ? undefined : account}
|
||||
size={90}
|
||||
size={92}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{!hidden && bellBtn}
|
||||
{!hidden && shareBtn}
|
||||
<Dropdown
|
||||
disabled={menu.length === 0}
|
||||
items={menu}
|
||||
icon='ellipsis-v'
|
||||
iconComponent={MoreHorizIcon}
|
||||
/>
|
||||
{accountId !== me && (
|
||||
<Dropdown
|
||||
disabled={menu.length === 0}
|
||||
items={menu}
|
||||
icon='ellipsis-v'
|
||||
iconComponent={MoreHorizIcon}
|
||||
/>
|
||||
)}
|
||||
{!hidden && actionBtn}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1008,7 +979,7 @@ export const AccountHeader: React.FC<{
|
|||
<div className='account__header__badges'>{badges}</div>
|
||||
)}
|
||||
|
||||
{account.id !== me && signedIn && (
|
||||
{account.id !== me && signedIn && !(suspended || hidden) && (
|
||||
<FamiliarFollowers accountId={accountId} />
|
||||
)}
|
||||
|
||||
|
@ -1019,7 +990,7 @@ export const AccountHeader: React.FC<{
|
|||
onClickCapture={handleLinkClick}
|
||||
>
|
||||
{account.id !== me && signedIn && (
|
||||
<AccountNoteContainer accountId={accountId} />
|
||||
<AccountNote accountId={accountId} />
|
||||
)}
|
||||
|
||||
{account.note.length > 0 && account.note !== '<p></p>' && (
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { fetchAccountsFamiliarFollowers } from '@/mastodon/actions/accounts_familiar_followers';
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import { getAccountFamiliarFollowers } from '@/mastodon/selectors/accounts';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
@ -14,14 +15,15 @@ export const useFetchFamiliarFollowers = ({
|
|||
const familiarFollowers = useAppSelector((state) =>
|
||||
accountId ? getAccountFamiliarFollowers(state, accountId) : null,
|
||||
);
|
||||
const { signedIn } = useIdentity();
|
||||
|
||||
const hasNoData = familiarFollowers === null;
|
||||
|
||||
useEffect(() => {
|
||||
if (hasNoData && accountId && accountId !== me) {
|
||||
if (hasNoData && signedIn && accountId && accountId !== me) {
|
||||
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
|
||||
}
|
||||
}, [dispatch, accountId, hasNoData]);
|
||||
}, [dispatch, accountId, hasNoData, signedIn]);
|
||||
|
||||
return {
|
||||
familiarFollowers: hasNoData ? [] : familiarFollowers,
|
||||
|
|
|
@ -137,7 +137,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { accountId, statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl } = this.props;
|
||||
const { accountId, statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, hidden, multiColumn, remote, remoteUrl, params: { tagged } } = this.props;
|
||||
|
||||
if (isLoading && statusIds.isEmpty()) {
|
||||
return (
|
||||
|
@ -174,8 +174,8 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
<StatusList
|
||||
prepend={
|
||||
<>
|
||||
<AccountHeader accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={this.props.params.tagged} />
|
||||
<FeaturedCarousel accountId={this.props.accountId} />
|
||||
<AccountHeader accountId={this.props.accountId} hideTabs={forceEmptyState} tagged={tagged} />
|
||||
{!forceEmptyState && <FeaturedCarousel accountId={this.props.accountId} tagged={tagged} />}
|
||||
</>
|
||||
}
|
||||
alwaysPrepend
|
||||
|
|
|
@ -27,7 +27,7 @@ import { Audio } from 'mastodon/features/audio';
|
|||
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
|
||||
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
|
||||
import { Video, getPointerPosition } from 'mastodon/features/video';
|
||||
import { me, reduceMotion } from 'mastodon/initial_state';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
|
@ -110,7 +110,7 @@ const Preview: React.FC<{
|
|||
left: `${x * 100}%`,
|
||||
top: `${y * 100}%`,
|
||||
},
|
||||
immediate: reduceMotion || draggingRef.current,
|
||||
immediate: draggingRef.current,
|
||||
});
|
||||
const media = useAppSelector((state) =>
|
||||
(
|
||||
|
|
|
@ -17,12 +17,9 @@ import { Blurhash } from 'mastodon/components/blurhash';
|
|||
import { Icon } from 'mastodon/components/icon';
|
||||
import { SpoilerButton } from 'mastodon/components/spoiler_button';
|
||||
import { formatTime, getPointerPosition } from 'mastodon/features/video';
|
||||
import { useAudioContext } from 'mastodon/hooks/useAudioContext';
|
||||
import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer';
|
||||
import {
|
||||
displayMedia,
|
||||
useBlurhash,
|
||||
reduceMotion,
|
||||
} from 'mastodon/initial_state';
|
||||
import { displayMedia, useBlurhash } from 'mastodon/initial_state';
|
||||
import { playerSettings } from 'mastodon/settings';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -119,12 +116,17 @@ export const Audio: React.FC<{
|
|||
const seekRef = useRef<HTMLDivElement>(null);
|
||||
const volumeRef = useRef<HTMLDivElement>(null);
|
||||
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
|
||||
const [resumeAudio, suspendAudio, frequencyBands] = useAudioVisualizer(
|
||||
audioRef,
|
||||
3,
|
||||
);
|
||||
const accessibilityId = useId();
|
||||
|
||||
const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } =
|
||||
useAudioContext({ audioElementRef: audioRef });
|
||||
|
||||
const frequencyBands = useAudioVisualizer({
|
||||
audioContextRef,
|
||||
sourceRef,
|
||||
numBands: 3,
|
||||
});
|
||||
|
||||
const [style, spring] = useSpring(() => ({
|
||||
progress: '0%',
|
||||
buffer: '0%',
|
||||
|
@ -152,22 +154,23 @@ export const Audio: React.FC<{
|
|||
restoreVolume(audioRef.current);
|
||||
setVolume(audioRef.current.volume);
|
||||
setMuted(audioRef.current.muted);
|
||||
if (gainNodeRef.current) {
|
||||
gainNodeRef.current.gain.value = audioRef.current.volume;
|
||||
}
|
||||
void spring.start({
|
||||
volume: `${audioRef.current.volume * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
spring,
|
||||
setVolume,
|
||||
setMuted,
|
||||
deployPictureInPicture,
|
||||
src,
|
||||
poster,
|
||||
backgroundColor,
|
||||
accentColor,
|
||||
foregroundColor,
|
||||
deployPictureInPicture,
|
||||
accentColor,
|
||||
gainNodeRef,
|
||||
spring,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -178,7 +181,11 @@ export const Audio: React.FC<{
|
|||
|
||||
audioRef.current.volume = volume;
|
||||
audioRef.current.muted = muted;
|
||||
}, [volume, muted]);
|
||||
|
||||
if (gainNodeRef.current) {
|
||||
gainNodeRef.current.gain.value = muted ? 0 : volume;
|
||||
}
|
||||
}, [volume, muted, gainNodeRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof visible !== 'undefined') {
|
||||
|
@ -192,11 +199,10 @@ export const Audio: React.FC<{
|
|||
}, [visible, sensitive]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!revealed && audioRef.current) {
|
||||
audioRef.current.pause();
|
||||
suspendAudio();
|
||||
if (!revealed) {
|
||||
pauseAudio();
|
||||
}
|
||||
}, [suspendAudio, revealed]);
|
||||
}, [pauseAudio, revealed]);
|
||||
|
||||
useEffect(() => {
|
||||
let nextFrame: ReturnType<typeof requestAnimationFrame>;
|
||||
|
@ -206,7 +212,6 @@ export const Audio: React.FC<{
|
|||
if (audioRef.current && audioRef.current.duration > 0) {
|
||||
void spring.start({
|
||||
progress: `${(audioRef.current.currentTime / audioRef.current.duration) * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
config: config.stiff,
|
||||
});
|
||||
}
|
||||
|
@ -228,13 +233,11 @@ export const Audio: React.FC<{
|
|||
}
|
||||
|
||||
if (audioRef.current.paused) {
|
||||
resumeAudio();
|
||||
void audioRef.current.play();
|
||||
playAudio();
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
suspendAudio();
|
||||
pauseAudio();
|
||||
}
|
||||
}, [resumeAudio, suspendAudio]);
|
||||
}, [playAudio, pauseAudio]);
|
||||
|
||||
const handlePlay = useCallback(() => {
|
||||
setPaused(false);
|
||||
|
@ -254,7 +257,6 @@ export const Audio: React.FC<{
|
|||
if (lastTimeRange > -1) {
|
||||
void spring.start({
|
||||
buffer: `${Math.ceil(audioRef.current.buffered.end(lastTimeRange) / audioRef.current.duration) * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
}
|
||||
}, [spring]);
|
||||
|
@ -269,7 +271,6 @@ export const Audio: React.FC<{
|
|||
|
||||
void spring.start({
|
||||
volume: `${audioRef.current.muted ? 0 : audioRef.current.volume * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
|
||||
persistVolume(audioRef.current.volume, audioRef.current.muted);
|
||||
|
@ -349,8 +350,7 @@ export const Audio: React.FC<{
|
|||
document.removeEventListener('mouseup', handleSeekMouseUp, true);
|
||||
|
||||
setDragging(false);
|
||||
resumeAudio();
|
||||
void audioRef.current?.play();
|
||||
playAudio();
|
||||
};
|
||||
|
||||
const handleSeekMouseMove = (e: MouseEvent) => {
|
||||
|
@ -377,7 +377,7 @@ export const Audio: React.FC<{
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
},
|
||||
[setDragging, spring, resumeAudio],
|
||||
[playAudio, spring],
|
||||
);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
|
@ -442,11 +442,13 @@ export const Audio: React.FC<{
|
|||
if (typeof startMuted !== 'undefined') {
|
||||
audioRef.current.muted = startMuted;
|
||||
}
|
||||
}, [setDuration, startTime, startVolume, startMuted]);
|
||||
|
||||
const handleCanPlayThrough = useCallback(() => {
|
||||
if (startPlaying) {
|
||||
void audioRef.current.play();
|
||||
playAudio();
|
||||
}
|
||||
}, [setDuration, startTime, startVolume, startMuted, startPlaying]);
|
||||
}, [startPlaying, playAudio]);
|
||||
|
||||
const seekBy = (time: number) => {
|
||||
if (!audioRef.current) {
|
||||
|
@ -489,7 +491,7 @@ export const Audio: React.FC<{
|
|||
return;
|
||||
}
|
||||
|
||||
const newVolume = audioRef.current.volume + step;
|
||||
const newVolume = Math.max(0, audioRef.current.volume + step);
|
||||
|
||||
if (!isNaN(newVolume)) {
|
||||
audioRef.current.volume = newVolume;
|
||||
|
@ -591,6 +593,7 @@ export const Audio: React.FC<{
|
|||
onPause={handlePause}
|
||||
onProgress={handleProgress}
|
||||
onLoadedData={handleLoadedData}
|
||||
onCanPlayThrough={handleCanPlayThrough}
|
||||
onTimeUpdate={handleTimeUpdate}
|
||||
onVolumeChange={handleVolumeChange}
|
||||
crossOrigin='anonymous'
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||
preferences: {
|
||||
id: 'navigation_bar.preferences',
|
||||
defaultMessage: 'Preferences',
|
||||
},
|
||||
reaction_deck: {
|
||||
id: 'navigation_bar.reaction_deck',
|
||||
defaultMessage: 'Reaction deck',
|
||||
},
|
||||
follow_requests: {
|
||||
id: 'navigation_bar.follow_requests',
|
||||
defaultMessage: 'Follow requests',
|
||||
},
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
emoji_reactions: {
|
||||
id: 'navigation_bar.emoji_reactions',
|
||||
defaultMessage: 'Stamps',
|
||||
},
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
followed_tags: {
|
||||
id: 'navigation_bar.followed_tags',
|
||||
defaultMessage: 'Followed hashtags',
|
||||
},
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: {
|
||||
id: 'navigation_bar.domain_blocks',
|
||||
defaultMessage: 'Blocked domains',
|
||||
},
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
});
|
||||
|
||||
export const ActionBar: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const menu = useMemo(() => {
|
||||
const handleLogoutClick = () => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
text: intl.formatMessage(messages.edit_profile),
|
||||
href: '/settings/profile',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.preferences),
|
||||
href: '/settings/preferences',
|
||||
},
|
||||
null,
|
||||
{
|
||||
text: intl.formatMessage(messages.follow_requests),
|
||||
to: '/follow_requests',
|
||||
},
|
||||
{ text: intl.formatMessage(messages.favourites), to: '/favourites' },
|
||||
{ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
|
||||
{
|
||||
text: intl.formatMessage(messages.emoji_reactions),
|
||||
to: '/emoji_reactions',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.reaction_deck),
|
||||
to: '/reaction_deck',
|
||||
},
|
||||
{ text: intl.formatMessage(messages.lists), to: '/lists' },
|
||||
{
|
||||
text: intl.formatMessage(messages.followed_tags),
|
||||
to: '/followed_tags',
|
||||
},
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
|
||||
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
|
||||
{
|
||||
text: intl.formatMessage(messages.domain_blocks),
|
||||
to: '/domain_blocks',
|
||||
},
|
||||
{ text: intl.formatMessage(messages.filters), href: '/filters' },
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.logout), action: handleLogoutClick },
|
||||
];
|
||||
}, [intl, dispatch]);
|
||||
|
||||
return <Dropdown items={menu} icon='bars' iconComponent={MoreHorizIcon} />;
|
||||
};
|
|
@ -2,34 +2,44 @@ import { useCallback } from 'react';
|
|||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { cancelReplyCompose } from 'mastodon/actions/compose';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
||||
import { ActionBar } from './action_bar';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
export const NavigationBar = () => {
|
||||
const dispatch = useDispatch();
|
||||
export const NavigationBar: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const isReplying = useSelector(state => !!state.getIn(['compose', 'in_reply_to']));
|
||||
const isReplying = useAppSelector(
|
||||
(state) => !!state.compose.get('in_reply_to'),
|
||||
);
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
dispatch(cancelReplyCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Account id={me} minimal />
|
||||
{isReplying ? <IconButton title={intl.formatMessage(messages.cancel)} iconComponent={CloseIcon} onClick={handleCancelClick} /> : <ActionBar />}
|
||||
|
||||
{isReplying && (
|
||||
<IconButton
|
||||
title={intl.formatMessage(messages.cancel)}
|
||||
icon=''
|
||||
iconComponent={CloseIcon}
|
||||
onClick={handleCancelClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { useCallback, useState, useRef } from 'react';
|
||||
import { useCallback, useState, useRef, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
defineMessages,
|
||||
|
@ -72,6 +72,10 @@ export const Search: React.FC<{
|
|||
const [expanded, setExpanded] = useState(false);
|
||||
const [selectedOption, setSelectedOption] = useState(-1);
|
||||
const [quickActions, setQuickActions] = useState<SearchOption[]>([]);
|
||||
useEffect(() => {
|
||||
setValue(initialValue ?? '');
|
||||
setQuickActions([]);
|
||||
}, [initialValue]);
|
||||
const searchOptions: SearchOption[] = [];
|
||||
|
||||
if (searchEnabled) {
|
||||
|
@ -263,7 +267,7 @@ export const Search: React.FC<{
|
|||
},
|
||||
forget: (e) => {
|
||||
e.stopPropagation();
|
||||
void dispatch(forgetSearchResult(search.q));
|
||||
void dispatch(forgetSearchResult(search));
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -539,8 +543,10 @@ export const Search: React.FC<{
|
|||
<div className='search__popout__menu'>
|
||||
{recentOptions.length > 0 ? (
|
||||
recentOptions.map(({ label, key, action, forget }, i) => (
|
||||
<button
|
||||
<div
|
||||
key={key}
|
||||
tabIndex={0}
|
||||
role='button'
|
||||
onMouseDown={action}
|
||||
className={classNames(
|
||||
'search__popout__menu__item search__popout__menu__item--flex',
|
||||
|
@ -551,7 +557,7 @@ export const Search: React.FC<{
|
|||
<button className='icon-button' onMouseDown={forget}>
|
||||
<Icon id='times' icon={CloseIcon} />
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className='search__popout__menu__message'>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { animated, useSpring } from '@react-spring/web';
|
|||
|
||||
import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { reduceMotion } from 'mastodon/initial_state';
|
||||
|
||||
interface UploadProgressProps {
|
||||
active: boolean;
|
||||
|
@ -20,7 +19,7 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
|
|||
const styles = useSpring({
|
||||
from: { width: '0%' },
|
||||
to: { width: `${progress}%` },
|
||||
immediate: reduceMotion || !active, // If this is not active, update the UI immediately.
|
||||
immediate: !active, // If this is not active, update the UI immediately.
|
||||
});
|
||||
if (!active) {
|
||||
return null;
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
|
||||
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import Column from 'mastodon/components/column';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
|
||||
import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose';
|
||||
import { mascot } from '../../initial_state';
|
||||
import { isMobile } from '../../is_mobile';
|
||||
|
||||
import { Search } from './components/search';
|
||||
import ComposeFormContainer from './containers/compose_form_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
|
||||
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
|
||||
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
columns: state.getIn(['settings', 'columns']),
|
||||
});
|
||||
|
||||
class Compose extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columns: ImmutablePropTypes.list.isRequired,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(mountCompose());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(unmountCompose());
|
||||
}
|
||||
|
||||
handleLogoutClick = e => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
onFocus = () => {
|
||||
this.props.dispatch(changeComposing(true));
|
||||
};
|
||||
|
||||
onBlur = () => {
|
||||
this.props.dispatch(changeComposing(false));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { multiColumn, intl } = this.props;
|
||||
|
||||
if (multiColumn) {
|
||||
const { columns } = this.props;
|
||||
|
||||
return (
|
||||
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
|
||||
<nav className='drawer__header'>
|
||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' icon={MenuIcon} /></Link>
|
||||
{!columns.some(column => column.get('id') === 'HOME') && (
|
||||
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' icon={HomeIcon} /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
|
||||
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' icon={NotificationsIcon} /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
|
||||
<Link to='/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' icon={PeopleIcon} /></Link>
|
||||
)}
|
||||
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||
<Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' icon={PublicIcon} /></Link>
|
||||
)}
|
||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' icon={SettingsIcon} /></a>
|
||||
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' icon={LogoutIcon} /></a>
|
||||
</nav>
|
||||
|
||||
{multiColumn && <Search /> }
|
||||
|
||||
<div className='drawer__pager'>
|
||||
<div className='drawer__inner' onFocus={this.onFocus}>
|
||||
<ComposeFormContainer autoFocus={!isMobile(window.innerWidth)} />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column onFocus={this.onFocus}>
|
||||
<ComposeFormContainer />
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Compose));
|
200
app/javascript/mastodon/features/compose/index.tsx
Normal file
200
app/javascript/mastodon/features/compose/index.tsx
Normal file
|
@ -0,0 +1,200 @@
|
|||
import { useEffect, useCallback } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
|
||||
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
|
||||
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
|
||||
import { mountCompose, unmountCompose } from 'mastodon/actions/compose';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { mascot } from 'mastodon/initial_state';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { Search } from './components/search';
|
||||
import ComposeFormContainer from './containers/compose_form_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
notifications: {
|
||||
id: 'tabs_bar.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
},
|
||||
public: {
|
||||
id: 'navigation_bar.public_timeline',
|
||||
defaultMessage: 'Federated timeline',
|
||||
},
|
||||
community: {
|
||||
id: 'navigation_bar.community_timeline',
|
||||
defaultMessage: 'Local timeline',
|
||||
},
|
||||
preferences: {
|
||||
id: 'navigation_bar.preferences',
|
||||
defaultMessage: 'Preferences',
|
||||
},
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
|
||||
});
|
||||
|
||||
type ColumnMap = ImmutableMap<'id' | 'uuid' | 'params', string>;
|
||||
|
||||
const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const columns = useAppSelector(
|
||||
(state) =>
|
||||
(state.settings as ImmutableMap<string, unknown>).get(
|
||||
'columns',
|
||||
) as ImmutableList<ColumnMap>,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(mountCompose());
|
||||
|
||||
return () => {
|
||||
dispatch(unmountCompose());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const handleLogoutClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
|
||||
|
||||
return false;
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
if (multiColumn) {
|
||||
return (
|
||||
<div
|
||||
className='drawer'
|
||||
role='region'
|
||||
aria-label={intl.formatMessage(messages.compose)}
|
||||
>
|
||||
<nav className='drawer__header'>
|
||||
<Link
|
||||
to='/getting-started'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.start)}
|
||||
aria-label={intl.formatMessage(messages.start)}
|
||||
>
|
||||
<Icon id='bars' icon={MenuIcon} />
|
||||
</Link>
|
||||
{!columns.some((column) => column.get('id') === 'HOME') && (
|
||||
<Link
|
||||
to='/home'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.home_timeline)}
|
||||
aria-label={intl.formatMessage(messages.home_timeline)}
|
||||
>
|
||||
<Icon id='home' icon={HomeIcon} />
|
||||
</Link>
|
||||
)}
|
||||
{!columns.some((column) => column.get('id') === 'NOTIFICATIONS') && (
|
||||
<Link
|
||||
to='/notifications'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.notifications)}
|
||||
aria-label={intl.formatMessage(messages.notifications)}
|
||||
>
|
||||
<Icon id='bell' icon={NotificationsIcon} />
|
||||
</Link>
|
||||
)}
|
||||
{!columns.some((column) => column.get('id') === 'COMMUNITY') && (
|
||||
<Link
|
||||
to='/public/local'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.community)}
|
||||
aria-label={intl.formatMessage(messages.community)}
|
||||
>
|
||||
<Icon id='users' icon={PeopleIcon} />
|
||||
</Link>
|
||||
)}
|
||||
{!columns.some((column) => column.get('id') === 'PUBLIC') && (
|
||||
<Link
|
||||
to='/public'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.public)}
|
||||
aria-label={intl.formatMessage(messages.public)}
|
||||
>
|
||||
<Icon id='globe' icon={PublicIcon} />
|
||||
</Link>
|
||||
)}
|
||||
<a
|
||||
href='/settings/preferences'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.preferences)}
|
||||
aria-label={intl.formatMessage(messages.preferences)}
|
||||
>
|
||||
<Icon id='cog' icon={SettingsIcon} />
|
||||
</a>
|
||||
<a
|
||||
href='/auth/sign_out'
|
||||
className='drawer__tab'
|
||||
title={intl.formatMessage(messages.logout)}
|
||||
aria-label={intl.formatMessage(messages.logout)}
|
||||
onClick={handleLogoutClick}
|
||||
>
|
||||
<Icon id='sign-out' icon={LogoutIcon} />
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<Search singleColumn={false} />
|
||||
|
||||
<div className='drawer__pager'>
|
||||
<div className='drawer__inner'>
|
||||
<ComposeFormContainer />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
<img alt='' draggable='false' src={mascot ?? elephantUIPlane} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column
|
||||
bindToDocument={!multiColumn}
|
||||
label={intl.formatMessage(messages.compose)}
|
||||
>
|
||||
<ColumnHeader
|
||||
icon='pencil'
|
||||
iconComponent={EditIcon}
|
||||
title={intl.formatMessage(messages.compose)}
|
||||
multiColumn={multiColumn}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
<div className='scrollable'>
|
||||
<ComposeFormContainer />
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default Compose;
|
|
@ -1,75 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { dismissSuggestion } from 'mastodon/actions/suggestions';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { FollowButton } from 'mastodon/components/follow_button';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
dismiss: { id: 'follow_suggestions.dismiss', defaultMessage: "Don't show again" },
|
||||
});
|
||||
|
||||
export const Card = ({ id, source }) => {
|
||||
const intl = useIntl();
|
||||
const account = useSelector(state => state.getIn(['accounts', id]));
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
dispatch(dismissSuggestion({ accountId: id }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
let label;
|
||||
|
||||
switch (source) {
|
||||
case 'friends_of_friends':
|
||||
label = <FormattedMessage id='follow_suggestions.friends_of_friends_longer' defaultMessage='Popular among people you follow' />;
|
||||
break;
|
||||
case 'similar_to_recently_followed':
|
||||
label = <FormattedMessage id='follow_suggestions.similar_to_recently_followed_longer' defaultMessage='Similar to profiles you recently followed' />;
|
||||
break;
|
||||
case 'featured':
|
||||
label = <FormattedMessage id='follow_suggestions.featured_longer' defaultMessage='Hand-picked by the {domain} team' values={{ domain }} />;
|
||||
break;
|
||||
case 'most_followed':
|
||||
label = <FormattedMessage id='follow_suggestions.popular_suggestion_longer' defaultMessage='Popular on {domain}' values={{ domain }} />;
|
||||
break;
|
||||
case 'most_interactions':
|
||||
label = <FormattedMessage id='follow_suggestions.popular_suggestion_longer' defaultMessage='Popular on {domain}' values={{ domain }} />;
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='explore__suggestions__card'>
|
||||
<div className='explore__suggestions__card__source'>
|
||||
{label}
|
||||
</div>
|
||||
|
||||
<div className='explore__suggestions__card__body'>
|
||||
<Link to={`/@${account.get('acct')}`} data-hover-card-account={account.id}><Avatar account={account} size={48} /></Link>
|
||||
|
||||
<div className='explore__suggestions__card__body__main'>
|
||||
<div className='explore__suggestions__card__body__main__name-button'>
|
||||
<Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`} data-hover-card-account={account.id}><DisplayName account={account} /></Link>
|
||||
<IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
|
||||
<FollowButton accountId={account.get('id')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Card.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
source: PropTypes.oneOf(['friends_of_friends', 'similar_to_recently_followed', 'featured', 'most_followed', 'most_interactions']),
|
||||
};
|
124
app/javascript/mastodon/features/explore/components/card.tsx
Normal file
124
app/javascript/mastodon/features/explore/components/card.tsx
Normal file
|
@ -0,0 +1,124 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { dismissSuggestion } from 'mastodon/actions/suggestions';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { FollowButton } from 'mastodon/components/follow_button';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { domain } from 'mastodon/initial_state';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
dismiss: {
|
||||
id: 'follow_suggestions.dismiss',
|
||||
defaultMessage: "Don't show again",
|
||||
},
|
||||
});
|
||||
|
||||
type SuggestionSource =
|
||||
| 'friends_of_friends'
|
||||
| 'similar_to_recently_followed'
|
||||
| 'featured'
|
||||
| 'most_followed'
|
||||
| 'most_interactions';
|
||||
|
||||
export const Card: React.FC<{ id: string; source: SuggestionSource }> = ({
|
||||
id,
|
||||
source,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const account = useAppSelector((state) => state.accounts.get(id));
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
void dispatch(dismissSuggestion({ accountId: id }));
|
||||
}, [id, dispatch]);
|
||||
|
||||
let label;
|
||||
|
||||
switch (source) {
|
||||
case 'friends_of_friends':
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='follow_suggestions.friends_of_friends_longer'
|
||||
defaultMessage='Popular among people you follow'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'similar_to_recently_followed':
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='follow_suggestions.similar_to_recently_followed_longer'
|
||||
defaultMessage='Similar to profiles you recently followed'
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'featured':
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='follow_suggestions.featured_longer'
|
||||
defaultMessage='Hand-picked by the {domain} team'
|
||||
values={{ domain }}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'most_followed':
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='follow_suggestions.popular_suggestion_longer'
|
||||
defaultMessage='Popular on {domain}'
|
||||
values={{ domain }}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'most_interactions':
|
||||
label = (
|
||||
<FormattedMessage
|
||||
id='follow_suggestions.popular_suggestion_longer'
|
||||
defaultMessage='Popular on {domain}'
|
||||
values={{ domain }}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!account) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='explore-suggestions-card'>
|
||||
<div className='explore-suggestions-card__source'>{label}</div>
|
||||
|
||||
<div className='explore-suggestions-card__body'>
|
||||
<Link
|
||||
to={`/@${account.get('acct')}`}
|
||||
data-hover-card-account={account.id}
|
||||
className='explore-suggestions-card__link'
|
||||
>
|
||||
<Avatar
|
||||
account={account}
|
||||
size={48}
|
||||
className='explore-suggestions-card__avatar'
|
||||
/>
|
||||
<DisplayName account={account} />
|
||||
</Link>
|
||||
<div className='explore-suggestions-card__actions'>
|
||||
<IconButton
|
||||
icon='close'
|
||||
iconComponent={CloseIcon}
|
||||
onClick={handleDismiss}
|
||||
title={intl.formatMessage(messages.dismiss)}
|
||||
className='explore-suggestions-card__dismiss-button'
|
||||
/>
|
||||
<FollowButton accountId={account.get('id')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -66,7 +66,7 @@ export const Story = ({
|
|||
<a className='story__thumbnail' href={url} target='blank' rel='noopener'>
|
||||
{thumbnail ? (
|
||||
<>
|
||||
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
|
||||
{!thumbnailLoaded && <Blurhash hash={blurhash} className='story__thumbnail__preview' />}
|
||||
<img src={thumbnail} onLoad={handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
|
||||
</>
|
||||
) : <Skeleton />}
|
||||
|
|
|
@ -9,7 +9,9 @@ import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
|
|||
import { Column } from 'mastodon/components/column';
|
||||
import type { ColumnRef } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
import { SymbolLogo } from 'mastodon/components/logo';
|
||||
import { Search } from 'mastodon/features/compose/components/search';
|
||||
import { useBreakpoint } from 'mastodon/features/ui/hooks/useBreakpoint';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
|
||||
import Links from './links';
|
||||
|
@ -25,6 +27,7 @@ const Explore: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
|||
const { signedIn } = useIdentity();
|
||||
const intl = useIntl();
|
||||
const columnRef = useRef<ColumnRef>(null);
|
||||
const logoRequired = useBreakpoint('full');
|
||||
|
||||
const handleHeaderClick = useCallback(() => {
|
||||
columnRef.current?.scrollTop();
|
||||
|
@ -38,7 +41,7 @@ const Explore: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
|||
>
|
||||
<ColumnHeader
|
||||
icon={'explore'}
|
||||
iconComponent={ExploreIcon}
|
||||
iconComponent={logoRequired ? SymbolLogo : ExploreIcon}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClick={handleHeaderClick}
|
||||
multiColumn={multiColumn}
|
||||
|
|
|
@ -270,7 +270,6 @@ const ReactionsBar = ({
|
|||
leave: {
|
||||
scale: 0,
|
||||
},
|
||||
immediate: reduceMotion,
|
||||
keys: visibleReactions.map(x => x.get('name')),
|
||||
});
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
|||
|
||||
import { dtlTag, enableDtlMenu, me, showTrends } from '../../initial_state';
|
||||
import { NavigationBar } from '../compose/components/navigation_bar';
|
||||
import ColumnLink from '../ui/components/column_link';
|
||||
import { ColumnLink } from '../ui/components/column_link';
|
||||
import ColumnSubheading from '../ui/components/column_subheading';
|
||||
|
||||
import TrendsContainer from './containers/trends_container';
|
||||
|
|
|
@ -40,6 +40,19 @@ export const ColumnSettings: React.FC = () => {
|
|||
}
|
||||
/>
|
||||
|
||||
<SettingToggle
|
||||
prefix='home_timeline'
|
||||
settings={settings}
|
||||
settingPath={['shows', 'quote']}
|
||||
onChange={onChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='home.column_settings.show_quotes'
|
||||
defaultMessage='Show quotes'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingToggle
|
||||
prefix='home_timeline'
|
||||
settings={settings}
|
||||
|
|
|
@ -10,12 +10,14 @@ import { connect } from 'react-redux';
|
|||
|
||||
import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import { SymbolLogo } from 'mastodon/components/logo';
|
||||
import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/announcements';
|
||||
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
|
||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
||||
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
import { criticalUpdatesPending } from 'mastodon/initial_state';
|
||||
import { withBreakpoint } from 'mastodon/features/ui/hooks/useBreakpoint';
|
||||
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import { expandHomeTimeline } from '../../actions/timelines';
|
||||
|
@ -52,6 +54,7 @@ class HomeTimeline extends PureComponent {
|
|||
hasAnnouncements: PropTypes.bool,
|
||||
unreadAnnouncements: PropTypes.number,
|
||||
showAnnouncements: PropTypes.bool,
|
||||
matchesBreakpoint: PropTypes.bool,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
|
@ -121,7 +124,7 @@ class HomeTimeline extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
||||
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements, matchesBreakpoint } = this.props;
|
||||
const pinned = !!columnId;
|
||||
const { signedIn } = this.props.identity;
|
||||
const banners = [];
|
||||
|
@ -150,7 +153,7 @@ class HomeTimeline extends PureComponent {
|
|||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='home'
|
||||
iconComponent={HomeIcon}
|
||||
iconComponent={matchesBreakpoint ? SymbolLogo : HomeIcon}
|
||||
active={hasUnread}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onPin={this.handlePin}
|
||||
|
@ -187,4 +190,4 @@ class HomeTimeline extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));
|
||||
export default connect(mapStateToProps)(withBreakpoint(withIdentity(injectIntl(HomeTimeline))));
|
||||
|
|
|
@ -170,7 +170,7 @@ export const Follows: React.FC<{
|
|||
}
|
||||
>
|
||||
{displayedAccountIds.map((accountId) => (
|
||||
<Account id={accountId} key={accountId} withBio />
|
||||
<Account id={accountId} key={accountId} withBio withMenu={false} />
|
||||
))}
|
||||
</ScrollableList>
|
||||
|
||||
|
|
|
@ -452,13 +452,13 @@ export const DetailedStatus: React.FC<{
|
|||
{...(statusContentProps as any)}
|
||||
/>
|
||||
|
||||
{status.get('quote') && (
|
||||
<QuotedStatus quote={status.get('quote')} />
|
||||
)}
|
||||
|
||||
{media}
|
||||
{hashtagBar}
|
||||
{emojiReactionsBar}
|
||||
|
||||
{status.get('quote') && (
|
||||
<QuotedStatus quote={status.get('quote')} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useRouteMatch, NavLink } from 'react-router-dom';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const ColumnLink = ({ icon, activeIcon, iconComponent, activeIconComponent, text, to, href, method, badge, transparent, optional, children, ...other }) => {
|
||||
const match = useRouteMatch(to);
|
||||
const className = classNames('column-link', { 'column-link--transparent': transparent, 'column-link--optional': optional });
|
||||
const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null;
|
||||
const iconElement = (typeof icon === 'string' || iconComponent) ? <Icon id={icon} icon={iconComponent} className='column-link__icon' /> : icon;
|
||||
const activeIconElement = activeIcon ?? (activeIconComponent ? <Icon id={icon} icon={activeIconComponent} className='column-link__icon' /> : iconElement);
|
||||
const active = match?.isExact;
|
||||
const childElement = typeof children !== 'undefined' ? <p>{children}</p> : null;
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className={className} data-method={method} {...other}>
|
||||
{active ? activeIconElement : iconElement}
|
||||
<span>{text}</span>
|
||||
{badgeElement}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<NavLink to={to} className={className} exact {...other}>
|
||||
{active ? activeIconElement : iconElement}
|
||||
<span>{text}</span>
|
||||
{badgeElement}
|
||||
{childElement}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
ColumnLink.propTypes = {
|
||||
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
||||
iconComponent: PropTypes.func,
|
||||
activeIcon: PropTypes.node,
|
||||
activeIconComponent: PropTypes.func,
|
||||
text: PropTypes.string.isRequired,
|
||||
to: PropTypes.string,
|
||||
href: PropTypes.string,
|
||||
method: PropTypes.string,
|
||||
badge: PropTypes.node,
|
||||
transparent: PropTypes.bool,
|
||||
children: PropTypes.any,
|
||||
optional: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ColumnLink;
|
109
app/javascript/mastodon/features/ui/components/column_link.tsx
Normal file
109
app/javascript/mastodon/features/ui/components/column_link.tsx
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useRouteMatch, NavLink } from 'react-router-dom';
|
||||
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import type { IconProp } from 'mastodon/components/icon';
|
||||
|
||||
export const ColumnLink: React.FC<{
|
||||
icon: React.ReactNode;
|
||||
iconComponent?: IconProp;
|
||||
activeIcon?: React.ReactNode;
|
||||
activeIconComponent?: IconProp;
|
||||
isActive?: (match: unknown, location: { pathname: string }) => boolean;
|
||||
text: string;
|
||||
to?: string;
|
||||
href?: string;
|
||||
onClick?: () => void;
|
||||
method?: string;
|
||||
badge?: React.ReactNode;
|
||||
transparent?: boolean;
|
||||
optional?: boolean;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
id?: string;
|
||||
}> = ({
|
||||
icon,
|
||||
activeIcon,
|
||||
iconComponent,
|
||||
activeIconComponent,
|
||||
text,
|
||||
to,
|
||||
href,
|
||||
onClick,
|
||||
method,
|
||||
badge,
|
||||
transparent,
|
||||
optional,
|
||||
children,
|
||||
...other
|
||||
}) => {
|
||||
const match = useRouteMatch(to ?? '');
|
||||
const className = classNames('column-link', {
|
||||
'column-link--transparent': transparent,
|
||||
'column-link--optional': optional,
|
||||
});
|
||||
const badgeElement =
|
||||
typeof badge !== 'undefined' ? (
|
||||
<span className='column-link__badge'>{badge}</span>
|
||||
) : null;
|
||||
const iconElement = iconComponent ? (
|
||||
<Icon
|
||||
id={typeof icon === 'string' ? icon : ''}
|
||||
icon={iconComponent}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
) : (
|
||||
icon
|
||||
);
|
||||
const activeIconElement =
|
||||
activeIcon ??
|
||||
(activeIconComponent ? (
|
||||
<Icon
|
||||
id={typeof icon === 'string' ? icon : ''}
|
||||
icon={activeIconComponent}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
) : (
|
||||
iconElement
|
||||
));
|
||||
const active = !!match;
|
||||
const childElement = typeof children !== 'undefined' ? <p>{children}</p> : null;
|
||||
|
||||
const handleClick = useCallback((ev: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
ev.preventDefault();
|
||||
onClick?.();
|
||||
}, [onClick]);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<a href={href} className={className} data-method={method} {...other}>
|
||||
{active ? activeIconElement : iconElement}
|
||||
<span>{text}</span>
|
||||
{badgeElement}
|
||||
{childElement}
|
||||
</a>
|
||||
);
|
||||
} else if (to) {
|
||||
return (
|
||||
<NavLink to={to} className={className} {...other}>
|
||||
{active ? activeIconElement : iconElement}
|
||||
<span>{text}</span>
|
||||
{badgeElement}
|
||||
{childElement}
|
||||
</NavLink>
|
||||
);
|
||||
} else if (onClick) {
|
||||
return (
|
||||
<a href={href} className={className} onClick={handleClick} {...other}>
|
||||
{active ? activeIconElement : iconElement}
|
||||
<span>{text}</span>
|
||||
{badgeElement}
|
||||
{childElement}
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
|
@ -28,9 +28,9 @@ import { useColumnsContext } from '../util/columns_context';
|
|||
|
||||
import BundleColumnError from './bundle_column_error';
|
||||
import { ColumnLoading } from './column_loading';
|
||||
import ComposePanel from './compose_panel';
|
||||
import { ComposePanel } from './compose_panel';
|
||||
import DrawerLoading from './drawer_loading';
|
||||
import NavigationPanel from './navigation_panel';
|
||||
import { NavigationPanel } from './navigation_panel';
|
||||
|
||||
const componentMap = {
|
||||
'COMPOSE': Compose,
|
||||
|
@ -142,11 +142,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
<div className='columns-area columns-area--mobile'>{children}</div>
|
||||
</div>
|
||||
|
||||
<div className='columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational'>
|
||||
<div className='columns-area__panels__pane__inner'>
|
||||
<NavigationPanel />
|
||||
</div>
|
||||
</div>
|
||||
<NavigationPanel />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/compose';
|
||||
import ServerBanner from 'mastodon/components/server_banner';
|
||||
import { Search } from 'mastodon/features/compose/components/search';
|
||||
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
|
||||
import { LinkFooter } from 'mastodon/features/ui/components/link_footer';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
|
||||
class ComposePanel extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
onFocus = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(changeComposing(true));
|
||||
};
|
||||
|
||||
onBlur = () => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(changeComposing(false));
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(mountCompose());
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(unmountCompose());
|
||||
}
|
||||
|
||||
render() {
|
||||
const { signedIn } = this.props.identity;
|
||||
|
||||
return (
|
||||
<div className='compose-panel' onFocus={this.onFocus}>
|
||||
<Search openInRoute />
|
||||
|
||||
{!signedIn && (
|
||||
<>
|
||||
<ServerBanner />
|
||||
<div className='flex-spacer' />
|
||||
</>
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
<ComposeFormContainer singleColumn />
|
||||
)}
|
||||
|
||||
<LinkFooter />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect()(withIdentity(ComposePanel));
|
|
@ -0,0 +1,56 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { useLayout } from '@/mastodon/hooks/useLayout';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import {
|
||||
changeComposing,
|
||||
mountCompose,
|
||||
unmountCompose,
|
||||
} from 'mastodon/actions/compose';
|
||||
import ServerBanner from 'mastodon/components/server_banner';
|
||||
import { Search } from 'mastodon/features/compose/components/search';
|
||||
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
|
||||
import { LinkFooter } from 'mastodon/features/ui/components/link_footer';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
|
||||
export const ComposePanel: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const handleFocus = useCallback(() => {
|
||||
dispatch(changeComposing(true));
|
||||
}, [dispatch]);
|
||||
const { signedIn } = useIdentity();
|
||||
const hideComposer = useAppSelector((state) => {
|
||||
const mounted = state.compose.get('mounted');
|
||||
if (typeof mounted === 'number') {
|
||||
return mounted > 1;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(mountCompose());
|
||||
return () => {
|
||||
dispatch(unmountCompose());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const { singleColumn } = useLayout();
|
||||
|
||||
return (
|
||||
<div className='compose-panel' onFocus={handleFocus}>
|
||||
<Search singleColumn={singleColumn} />
|
||||
|
||||
{!signedIn && (
|
||||
<>
|
||||
<ServerBanner />
|
||||
<div className='flex-spacer' />
|
||||
</>
|
||||
)}
|
||||
|
||||
{signedIn && !hideComposer && <ComposeFormContainer singleColumn />}
|
||||
{signedIn && hideComposer && <div className='compose-form' />}
|
||||
|
||||
<LinkFooter multiColumn={!singleColumn} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,129 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { fetchServer } from 'mastodon/actions/server';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
|
||||
|
||||
const Account = connect(state => ({
|
||||
account: state.getIn(['accounts', me]),
|
||||
}))(({ account }) => (
|
||||
<Link to={`/@${account.get('acct')}`} title={account.get('acct')}>
|
||||
<Avatar account={account} size={35} />
|
||||
</Link>
|
||||
));
|
||||
|
||||
const messages = defineMessages({
|
||||
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
||||
reload: { id: 'navigation_bar.refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openClosedRegistrationsModal() {
|
||||
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
|
||||
},
|
||||
dispatchServer() {
|
||||
dispatch(fetchServer());
|
||||
}
|
||||
});
|
||||
|
||||
class Header extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
openClosedRegistrationsModal: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
signupUrl: PropTypes.string.isRequired,
|
||||
dispatchServer: PropTypes.func,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatchServer } = this.props;
|
||||
dispatchServer();
|
||||
}
|
||||
|
||||
handleReload (e) {
|
||||
e.preventDefault();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
render () {
|
||||
const { signedIn } = this.props.identity;
|
||||
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
|
||||
|
||||
let content;
|
||||
|
||||
if (signedIn) {
|
||||
content = (
|
||||
<>
|
||||
{<button onClick={this.handleReload} className='button button-secondary' aria-label={intl.formatMessage(messages.reload)}><Icon id='refresh' icon={RefreshIcon} /></button>}
|
||||
{location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' icon={SearchIcon} /></Link>}
|
||||
{location.pathname !== '/publish' && <Link to='/publish' className='button button-secondary'><FormattedMessage id='compose_form.publish_form' defaultMessage='New post' /></Link>}
|
||||
<Account />
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
|
||||
if (sso_redirect) {
|
||||
content = (
|
||||
<a href={sso_redirect} data-method='post' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sso_redirect' defaultMessage='Login or Register' /></a>
|
||||
);
|
||||
} else {
|
||||
let signupButton;
|
||||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
<a href={signupUrl} className='button'>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
signupButton = (
|
||||
<button className='button' onClick={openClosedRegistrationsModal}>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
content = (
|
||||
<>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui__header'>
|
||||
<Link to='/' className='ui__header__logo'>
|
||||
<WordmarkLogo />
|
||||
<SymbolLogo />
|
||||
</Link>
|
||||
|
||||
<div className='ui__header__links'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));
|
|
@ -1,57 +0,0 @@
|
|||
import { useEffect } from 'react';
|
||||
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
|
||||
import { fetchAntennas } from 'mastodon/actions/antennas';
|
||||
import { fetchLists } from 'mastodon/actions/lists';
|
||||
|
||||
import ColumnLink from './column_link';
|
||||
|
||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||
if (!lists) {
|
||||
return lists;
|
||||
}
|
||||
|
||||
return lists.toList().filter(item => !!item && item.get('favourite')).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
|
||||
});
|
||||
|
||||
const getOrderedAntennas = createSelector([state => state.get('antennas')], antennas => {
|
||||
if (!antennas) {
|
||||
return antennas;
|
||||
}
|
||||
|
||||
return antennas.toList().filter(item => !!item && item.get('favourite') && item.get('title') !== undefined).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
|
||||
});
|
||||
|
||||
export const ListPanel = () => {
|
||||
const dispatch = useDispatch();
|
||||
const lists = useSelector(state => getOrderedLists(state));
|
||||
const antennas = useSelector(state => getOrderedAntennas(state));
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchLists());
|
||||
dispatch(fetchAntennas());
|
||||
}, [dispatch]);
|
||||
|
||||
const size = (lists ? lists.size : 0) + (antennas ? antennas.size : 0);
|
||||
if (size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='list-panel'>
|
||||
<hr />
|
||||
|
||||
{lists && lists.map(list => (
|
||||
<ColumnLink icon='list-ul' key={list.get('id')} iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={list.get('title')} to={`/lists/${list.get('id')}`} transparent />
|
||||
))}
|
||||
{antennas && antennas.map(antenna => (
|
||||
<ColumnLink icon='wifi' key={antenna.get('id')} iconComponent={AntennaIcon} activeIconComponent={AntennaIcon} text={antenna.get('title')} to={`/antennas/${antenna.get('id')}`} transparent />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
152
app/javascript/mastodon/features/ui/components/list_panel.tsx
Normal file
152
app/javascript/mastodon/features/ui/components/list_panel.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
import { useEffect, useState, useCallback, useId } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?react';
|
||||
import ArrowLeftIcon from '@/material-icons/400-24px/arrow_left.svg?react';
|
||||
import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
|
||||
import { fetchAntennas } from 'mastodon/actions/antennas';
|
||||
import { fetchLists } from 'mastodon/actions/lists';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { getOrderedAntennas } from 'mastodon/selectors/antennas';
|
||||
import { getOrderedLists } from 'mastodon/selectors/lists';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { ColumnLink } from './column_link';
|
||||
|
||||
const messages = defineMessages({
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
antennas: { id: 'navigation_bar.antennas', defaultMessage: 'Antennas' },
|
||||
expand: {
|
||||
id: 'navigation_panel.expand_lists',
|
||||
defaultMessage: 'Expand list menu',
|
||||
},
|
||||
collapse: {
|
||||
id: 'navigation_panel.collapse_lists',
|
||||
defaultMessage: 'Collapse list menu',
|
||||
},
|
||||
});
|
||||
|
||||
export const ListPanel: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const lists = useAppSelector((state) => getOrderedLists(state));
|
||||
const antennas = useAppSelector(state => getOrderedAntennas(state));
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [expandedAntenna, setExpandedAntenna] = useState(false);
|
||||
const accessibilityId = useId();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchLists());
|
||||
dispatch(fetchAntennas());
|
||||
}, [dispatch]);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setExpanded((value) => !value);
|
||||
}, [setExpanded]);
|
||||
|
||||
const handleClickAntenna = useCallback(() => {
|
||||
setExpandedAntenna((value) => !value);
|
||||
}, [setExpandedAntenna]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='navigation-panel__list-panel'>
|
||||
<div className='navigation-panel__list-panel__header'>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/lists'
|
||||
icon='list-ul'
|
||||
iconComponent={ListAltIcon}
|
||||
activeIconComponent={ListAltActiveIcon}
|
||||
text={intl.formatMessage(messages.lists)}
|
||||
id={`${accessibilityId}-title`}
|
||||
/>
|
||||
|
||||
{lists.length > 0 && (
|
||||
<IconButton
|
||||
icon='down'
|
||||
expanded={expanded}
|
||||
iconComponent={expanded ? ArrowDropDownIcon : ArrowLeftIcon}
|
||||
title={intl.formatMessage(
|
||||
expanded ? messages.collapse : messages.expand,
|
||||
)}
|
||||
onClick={handleClick}
|
||||
aria-controls={`${accessibilityId}-content`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{lists.length > 0 && expanded && (
|
||||
<div
|
||||
className='navigation-panel__list-panel__items'
|
||||
role='region'
|
||||
id={`${accessibilityId}-content`}
|
||||
aria-labelledby={`${accessibilityId}-title`}
|
||||
>
|
||||
{lists.map((list) => (
|
||||
<ColumnLink
|
||||
icon='list-ul'
|
||||
key={list.get('id')}
|
||||
iconComponent={ListAltIcon}
|
||||
activeIconComponent={ListAltActiveIcon}
|
||||
text={list.get('title')}
|
||||
to={`/lists/${list.get('id')}`}
|
||||
transparent
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='navigation-panel__list-panel'>
|
||||
<div className='navigation-panel__list-panel__header'>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/antennas'
|
||||
icon='list-ul'
|
||||
iconComponent={AntennaIcon}
|
||||
activeIconComponent={AntennaIcon}
|
||||
text={intl.formatMessage(messages.antennas)}
|
||||
id={`${accessibilityId}-title`}
|
||||
/>
|
||||
|
||||
{antennas.length > 0 && (
|
||||
<IconButton
|
||||
icon='down'
|
||||
expanded={expanded}
|
||||
iconComponent={expanded ? ArrowDropDownIcon : ArrowLeftIcon}
|
||||
title={intl.formatMessage(
|
||||
expanded ? messages.collapse : messages.expand,
|
||||
)}
|
||||
onClick={handleClickAntenna}
|
||||
aria-controls={`${accessibilityId}-content`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{antennas.length > 0 && expandedAntenna && (
|
||||
<div
|
||||
className='navigation-panel__list-panel__items'
|
||||
role='region'
|
||||
id={`${accessibilityId}-content`}
|
||||
aria-labelledby={`${accessibilityId}-title`}
|
||||
>
|
||||
{antennas.map((antenna) => (
|
||||
<ColumnLink
|
||||
icon='list-ul'
|
||||
key={antenna.get('id')}
|
||||
iconComponent={AntennaIcon}
|
||||
activeIconComponent={AntennaIcon}
|
||||
text={antenna.get('title')}
|
||||
to={`/antennas/${antenna.get('id')}`}
|
||||
transparent
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -168,8 +168,8 @@ class MediaModal extends ImmutablePureComponent {
|
|||
|
||||
const index = this.getIndex();
|
||||
|
||||
const leftNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--left' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' icon={ChevronLeftIcon} /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--right' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' icon={ChevronRightIcon} /></button>;
|
||||
const leftNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--prev' onClick={this.handlePrevClick} aria-label={intl.formatMessage(messages.previous)}><Icon id='chevron-left' icon={ChevronLeftIcon} /></button>;
|
||||
const rightNav = media.size > 1 && <button tabIndex={0} className='media-modal__nav media-modal__nav--next' onClick={this.handleNextClick} aria-label={intl.formatMessage(messages.next)}><Icon id='chevron-right' icon={ChevronRightIcon} /></button>;
|
||||
|
||||
const content = media.map((image, idx) => {
|
||||
const width = image.getIn(['meta', 'original', 'width']) || null;
|
||||
|
|
148
app/javascript/mastodon/features/ui/components/more_link.tsx
Normal file
148
app/javascript/mastodon/features/ui/components/more_link.tsx
Normal file
|
@ -0,0 +1,148 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { enableEmojiReaction } from '@/mastodon/initial_state';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { Dropdown } from 'mastodon/components/dropdown_menu';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import type { MenuItem } from 'mastodon/models/dropdown_menu';
|
||||
import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
followedTags: {
|
||||
id: 'navigation_bar.followed_tags',
|
||||
defaultMessage: 'Followed hashtags',
|
||||
},
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domainBlocks: {
|
||||
id: 'navigation_bar.domain_blocks',
|
||||
defaultMessage: 'Blocked domains',
|
||||
},
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
|
||||
administration: {
|
||||
id: 'navigation_bar.administration',
|
||||
defaultMessage: 'Administration',
|
||||
},
|
||||
moderation: { id: 'navigation_bar.moderation', defaultMessage: 'Moderation' },
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
automatedDeletion: {
|
||||
id: 'navigation_bar.automated_deletion',
|
||||
defaultMessage: 'Automated post deletion',
|
||||
},
|
||||
accountSettings: {
|
||||
id: 'navigation_bar.account_settings',
|
||||
defaultMessage: 'Password and security',
|
||||
},
|
||||
importExport: {
|
||||
id: 'navigation_bar.import_export',
|
||||
defaultMessage: 'Import and export',
|
||||
},
|
||||
privacyAndReach: {
|
||||
id: 'navigation_bar.privacy_and_reach',
|
||||
defaultMessage: 'Privacy and reach',
|
||||
},
|
||||
reaction_deck: {
|
||||
id: 'navigation_bar.reaction_deck',
|
||||
defaultMessage: 'Reaction deck',
|
||||
},
|
||||
emoji_reactions: {
|
||||
id: 'navigation_bar.emoji_reactions',
|
||||
defaultMessage: 'Emoji reactions',
|
||||
},
|
||||
});
|
||||
|
||||
export const MoreLink: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { permissions } = useIdentity();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const emojiReactionMenu = useMemo(() => {
|
||||
if (!enableEmojiReaction) return [];
|
||||
return [{
|
||||
text: intl.formatMessage(messages.emoji_reactions),
|
||||
to: '/emoji_reactions',
|
||||
}];
|
||||
}, [enableEmojiReaction, intl]);
|
||||
|
||||
const menu = useMemo(() => {
|
||||
const arr: MenuItem[] = [
|
||||
{
|
||||
text: intl.formatMessage(messages.followedTags),
|
||||
to: '/followed_tags',
|
||||
},
|
||||
...emojiReactionMenu,
|
||||
{
|
||||
text: intl.formatMessage(messages.reaction_deck),
|
||||
to: '/reaction_deck',
|
||||
},
|
||||
null,
|
||||
{ text: intl.formatMessage(messages.filters), href: '/filters' },
|
||||
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
|
||||
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
|
||||
{
|
||||
text: intl.formatMessage(messages.domainBlocks),
|
||||
to: '/domain_blocks',
|
||||
},
|
||||
];
|
||||
|
||||
arr.push(
|
||||
null,
|
||||
{
|
||||
href: '/settings/privacy',
|
||||
text: intl.formatMessage(messages.privacyAndReach),
|
||||
},
|
||||
{
|
||||
href: '/statuses_cleanup',
|
||||
text: intl.formatMessage(messages.automatedDeletion),
|
||||
},
|
||||
{
|
||||
href: '/auth/edit',
|
||||
text: intl.formatMessage(messages.accountSettings),
|
||||
},
|
||||
{
|
||||
href: '/settings/export',
|
||||
text: intl.formatMessage(messages.importExport),
|
||||
},
|
||||
);
|
||||
|
||||
if (canManageReports(permissions)) {
|
||||
arr.push(null, {
|
||||
href: '/admin/reports',
|
||||
text: intl.formatMessage(messages.moderation),
|
||||
});
|
||||
}
|
||||
|
||||
if (canViewAdminDashboard(permissions)) {
|
||||
arr.push({
|
||||
href: '/admin/dashboard',
|
||||
text: intl.formatMessage(messages.administration),
|
||||
});
|
||||
}
|
||||
|
||||
const handleLogoutClick = () => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
|
||||
};
|
||||
|
||||
arr.push(null, {
|
||||
text: intl.formatMessage(messages.logout),
|
||||
action: handleLogoutClick,
|
||||
});
|
||||
|
||||
return arr;
|
||||
}, [intl, dispatch, permissions, emojiReactionMenu]);
|
||||
|
||||
return (
|
||||
<Dropdown items={menu}>
|
||||
<button className='column-link column-link--transparent'>
|
||||
<Icon id='' icon={MoreHorizIcon} className='column-link__icon' />
|
||||
|
||||
<FormattedMessage id='navigation_bar.more' defaultMessage='More' />
|
||||
</button>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,204 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { NavLink, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
|
||||
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
|
||||
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { toggleNavigation } from 'mastodon/actions/navigation';
|
||||
import { fetchServer } from 'mastodon/actions/server';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';
|
||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
const messages = defineMessages({
|
||||
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||
publish: { id: 'tabs_bar.publish', defaultMessage: 'New Post' },
|
||||
notifications: {
|
||||
id: 'tabs_bar.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
},
|
||||
menu: { id: 'tabs_bar.menu', defaultMessage: 'Menu' },
|
||||
});
|
||||
|
||||
const IconLabelButton: React.FC<{
|
||||
to: string;
|
||||
icon?: React.ReactNode;
|
||||
activeIcon?: React.ReactNode;
|
||||
title: string;
|
||||
}> = ({ to, icon, activeIcon, title }) => {
|
||||
const match = useRouteMatch(to);
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
className='ui__navigation-bar__item'
|
||||
activeClassName='active'
|
||||
to={to}
|
||||
aria-label={title}
|
||||
>
|
||||
{match && activeIcon ? activeIcon : icon}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
||||
const NotificationsButton = () => {
|
||||
const count = useAppSelector(selectUnreadNotificationGroupsCount);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<IconLabelButton
|
||||
to='/notifications'
|
||||
icon={
|
||||
<IconWithBadge
|
||||
id='bell'
|
||||
icon={NotificationsIcon}
|
||||
count={count}
|
||||
className=''
|
||||
/>
|
||||
}
|
||||
activeIcon={
|
||||
<IconWithBadge
|
||||
id='bell'
|
||||
icon={NotificationsActiveIcon}
|
||||
count={count}
|
||||
className=''
|
||||
/>
|
||||
}
|
||||
title={intl.formatMessage(messages.notifications)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const LoginOrSignUp: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const signupUrl = useAppSelector(
|
||||
(state) =>
|
||||
(state.server.getIn(['server', 'registrations', 'url'], null) as
|
||||
| string
|
||||
| null) ?? '/auth/sign_up',
|
||||
);
|
||||
|
||||
const openClosedRegistrationsModal = useCallback(() => {
|
||||
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS', modalProps: {} }));
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchServer());
|
||||
}, [dispatch]);
|
||||
|
||||
if (sso_redirect) {
|
||||
return (
|
||||
<div className='ui__navigation-bar__sign-up'>
|
||||
<a
|
||||
href={sso_redirect}
|
||||
data-method='post'
|
||||
className='button button--block button-tertiary'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.sso_redirect'
|
||||
defaultMessage='Login or Register'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let signupButton;
|
||||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
<a href={signupUrl} className='button'>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.create_account'
|
||||
defaultMessage='Create account'
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
signupButton = (
|
||||
<button className='button' onClick={openClosedRegistrationsModal}>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.create_account'
|
||||
defaultMessage='Create account'
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='ui__navigation-bar__sign-up'>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button-tertiary'>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.sign_in'
|
||||
defaultMessage='Login'
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const NavigationBar: React.FC = () => {
|
||||
const { signedIn } = useIdentity();
|
||||
const dispatch = useAppDispatch();
|
||||
const open = useAppSelector((state) => state.navigation.open);
|
||||
const intl = useIntl();
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
dispatch(toggleNavigation());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className='ui__navigation-bar'>
|
||||
{!signedIn && <LoginOrSignUp />}
|
||||
|
||||
<div
|
||||
className={classNames('ui__navigation-bar__items', {
|
||||
active: signedIn,
|
||||
})}
|
||||
>
|
||||
{signedIn && (
|
||||
<>
|
||||
<IconLabelButton
|
||||
title={intl.formatMessage(messages.home)}
|
||||
to='/home'
|
||||
icon={<Icon id='' icon={HomeIcon} />}
|
||||
activeIcon={<Icon id='' icon={HomeActiveIcon} />}
|
||||
/>
|
||||
<IconLabelButton
|
||||
title={intl.formatMessage(messages.search)}
|
||||
to='/explore'
|
||||
icon={<Icon id='' icon={SearchIcon} />}
|
||||
/>
|
||||
<IconLabelButton
|
||||
title={intl.formatMessage(messages.publish)}
|
||||
to='/publish'
|
||||
icon={<Icon id='' icon={AddIcon} />}
|
||||
/>
|
||||
<NotificationsButton />
|
||||
</>
|
||||
)}
|
||||
|
||||
<button
|
||||
className={classNames('ui__navigation-bar__item', { active: open })}
|
||||
onClick={handleClick}
|
||||
aria-label={intl.formatMessage(messages.menu)}
|
||||
>
|
||||
<Icon id='' icon={MenuIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,244 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { Component, useEffect } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, useIntl } from 'react-intl';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import CirclesIcon from '@/material-icons/400-24px/account_circle-fill.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
||||
import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
|
||||
import ExploreActiveIcon from '@/material-icons/400-24px/explore-fill.svg?react';
|
||||
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
|
||||
import ModerationIcon from '@/material-icons/400-24px/gavel.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
|
||||
import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import AdministrationIcon from '@/material-icons/400-24px/manufacturing.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
|
||||
import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
|
||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
|
||||
import { WordmarkLogo } from 'mastodon/components/logo';
|
||||
import { NavigationPortal } from 'mastodon/components/navigation_portal';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag, enableLocalTimeline, isHideItem } from 'mastodon/initial_state';
|
||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||
import { canManageReports, canViewAdminDashboard } from 'mastodon/permissions';
|
||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||
|
||||
import ColumnLink from './column_link';
|
||||
import DisabledAccountBanner from './disabled_account_banner';
|
||||
import { ListPanel } from './list_panel';
|
||||
import SignInBanner from './sign_in_banner';
|
||||
|
||||
const messages = defineMessages({
|
||||
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
|
||||
explore: { id: 'explore.title', defaultMessage: 'Explore' },
|
||||
local: { id: 'column.local', defaultMessage: 'Local' },
|
||||
deepLocal: { id: 'column.deep_local', defaultMessage: 'Deep' },
|
||||
firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
antennas: { id: 'navigation_bar.antennas', defaultMessage: 'Antennas' },
|
||||
circles: { id: 'navigation_bar.circles', defaultMessage: 'Circles' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
administration: { id: 'navigation_bar.administration', defaultMessage: 'Administration' },
|
||||
moderation: { id: 'navigation_bar.moderation', defaultMessage: 'Moderation' },
|
||||
followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
|
||||
about: { id: 'navigation_bar.about', defaultMessage: 'About' },
|
||||
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
||||
advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' },
|
||||
openedInClassicInterface: { id: 'navigation_bar.opened_in_classic_interface', defaultMessage: 'Posts, accounts, and other specific pages are opened by default in the classic web interface.' },
|
||||
followRequests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
});
|
||||
|
||||
const NotificationsLink = () => {
|
||||
|
||||
const count = useSelector(selectUnreadNotificationGroupsCount);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
key='notifications'
|
||||
transparent
|
||||
to='/notifications'
|
||||
icon={<IconWithBadge id='bell' icon={NotificationsIcon} count={count} className='column-link__icon' />}
|
||||
activeIcon={<IconWithBadge id='bell' icon={NotificationsActiveIcon} count={count} className='column-link__icon' />}
|
||||
text={intl.formatMessage(messages.notifications)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FollowRequestsLink = () => {
|
||||
const count = useSelector(state => state.getIn(['user_lists', 'follow_requests', 'items'])?.size ?? 0);
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchFollowRequests());
|
||||
}, [dispatch]);
|
||||
|
||||
if (count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/follow_requests'
|
||||
icon={<IconWithBadge id='user-plus' icon={PersonAddIcon} count={count} className='column-link__icon' />}
|
||||
activeIcon={<IconWithBadge id='user-plus' icon={PersonAddActiveIcon} count={count} className='column-link__icon' />}
|
||||
text={intl.formatMessage(messages.followRequests)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
class NavigationPanel extends Component {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
isFirehoseActive = (match, location) => {
|
||||
return (match || location.pathname.startsWith('/public')) && !location.pathname.endsWith('/fixed');
|
||||
};
|
||||
|
||||
isAntennasActive = (match, location) => {
|
||||
return (match || location.pathname.startsWith('/antennas'));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl } = this.props;
|
||||
const { signedIn, disabledAccountId, permissions } = this.props.identity;
|
||||
|
||||
const explorer = (trendsEnabled ? (
|
||||
<ColumnLink transparent to='/explore' icon='explore' iconComponent={ExploreIcon} activeIconComponent={ExploreActiveIcon} text={intl.formatMessage(messages.explore)} />
|
||||
) : (
|
||||
<ColumnLink transparent to='/search' icon='search' iconComponent={SearchIcon} text={intl.formatMessage(messages.search)} />
|
||||
));
|
||||
|
||||
let banner = undefined;
|
||||
|
||||
if (transientSingleColumn) {
|
||||
banner = (
|
||||
<div className='switch-to-advanced'>
|
||||
{intl.formatMessage(messages.openedInClassicInterface)}
|
||||
{" "}
|
||||
<a href={`/deck${location.pathname}`} className='switch-to-advanced__toggle'>
|
||||
{intl.formatMessage(messages.advancedInterface)}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navigation-panel'>
|
||||
<div className='navigation-panel__logo'>
|
||||
<Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
|
||||
</div>
|
||||
|
||||
{banner &&
|
||||
<div className='navigation-panel__banner'>
|
||||
{banner}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className='navigation-panel__menu'>
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink transparent to='/home' icon='home' iconComponent={HomeIcon} activeIconComponent={HomeActiveIcon} text={intl.formatMessage(messages.home)} />
|
||||
<NotificationsLink />
|
||||
</>
|
||||
)}
|
||||
|
||||
{signedIn && enableLocalTimeline && (
|
||||
<ColumnLink transparent to='/public/local/fixed' icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.local)} />
|
||||
)}
|
||||
|
||||
{signedIn && enableDtlMenu && dtlTag && (
|
||||
<ColumnLink transparent to={`/tags/${dtlTag}`} icon='users' iconComponent={PeopleIcon} text={intl.formatMessage(messages.deepLocal)} />
|
||||
)}
|
||||
|
||||
{!signedIn && explorer}
|
||||
|
||||
{signedIn && (
|
||||
<ColumnLink transparent to='/public' isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
|
||||
)}
|
||||
|
||||
{(!signedIn && timelinePreview) && (
|
||||
<ColumnLink transparent to={enableLocalTimeline ? '/public/local' : '/public'} isActive={this.isFirehoseActive} icon='globe' iconComponent={PublicIcon} text={intl.formatMessage(messages.firehose)} />
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ListPanel />
|
||||
<hr />
|
||||
</>
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink transparent to='/lists' icon='list-ul' iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={intl.formatMessage(messages.lists)} />
|
||||
<ColumnLink transparent to='/antennas' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} isActive={this.isAntennasActive} />
|
||||
<ColumnLink transparent to='/circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} />
|
||||
<FollowRequestsLink />
|
||||
<ColumnLink transparent to='/conversations' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{signedIn && explorer}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink transparent to='/bookmark_categories' icon='bookmarks' iconComponent={BookmarksIcon} activeIconComponent={BookmarksActiveIcon} text={intl.formatMessage(messages.bookmarks)} />
|
||||
{ !isHideItem('favourite_menu') && <ColumnLink transparent to='/favourites' icon='star' iconComponent={StarIcon} activeIconComponent={StarActiveIcon} text={intl.formatMessage(messages.favourites)} /> }
|
||||
<hr />
|
||||
|
||||
<ColumnLink transparent href='/settings/preferences' icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} />
|
||||
|
||||
{canManageReports(permissions) && <ColumnLink transparent href='/admin/reports' icon='flag' iconComponent={ModerationIcon} text={intl.formatMessage(messages.moderation)} />}
|
||||
{canViewAdminDashboard(permissions) && <ColumnLink transparent href='/admin/dashboard' icon='tachometer' iconComponent={AdministrationIcon} text={intl.formatMessage(messages.administration)} />}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!signedIn && (
|
||||
<div className='navigation-panel__sign-in-banner'>
|
||||
<hr />
|
||||
{ disabledAccountId ? <DisabledAccountBanner /> : <SignInBanner /> }
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='navigation-panel__legal'>
|
||||
<hr />
|
||||
<ColumnLink transparent to='/about' icon='ellipsis-h' iconComponent={MoreHorizIcon} text={intl.formatMessage(messages.about)} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-spacer' />
|
||||
|
||||
<NavigationPortal />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(withIdentity(NavigationPanel));
|
|
@ -0,0 +1,515 @@
|
|||
import { useEffect, useCallback, useRef } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useDrag } from '@use-gesture/react';
|
||||
|
||||
import CirclesIcon from '@/material-icons/400-24px/account_circle-fill.svg?react';
|
||||
import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
|
||||
import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
|
||||
import ExploreActiveIcon from '@/material-icons/400-24px/explore-fill.svg?react';
|
||||
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
|
||||
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
|
||||
import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
|
||||
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
|
||||
import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
|
||||
import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react';
|
||||
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
||||
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
|
||||
import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||
import { fetchFollowRequests } from 'mastodon/actions/accounts';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { openNavigation, closeNavigation } from 'mastodon/actions/navigation';
|
||||
import { Account } from 'mastodon/components/account';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
|
||||
import { WordmarkLogo } from 'mastodon/components/logo';
|
||||
import { NavigationPortal } from 'mastodon/components/navigation_portal';
|
||||
import { useBreakpoint } from 'mastodon/features/ui/hooks/useBreakpoint';
|
||||
import { useIdentity } from 'mastodon/identity_context';
|
||||
import { me, enableDtlMenu, timelinePreview, trendsEnabled, dtlTag, enableLocalTimeline } from 'mastodon/initial_state';
|
||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||
import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifications';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { ColumnLink } from './column_link';
|
||||
import DisabledAccountBanner from './disabled_account_banner';
|
||||
import { ListPanel } from './list_panel';
|
||||
import { MoreLink } from './more_link';
|
||||
import SignInBanner from './sign_in_banner';
|
||||
|
||||
const messages = defineMessages({
|
||||
home: { id: 'tabs_bar.home', defaultMessage: 'Home' },
|
||||
notifications: {
|
||||
id: 'tabs_bar.notifications',
|
||||
defaultMessage: 'Notifications',
|
||||
},
|
||||
explore: { id: 'explore.title', defaultMessage: 'Explore' },
|
||||
firehose: { id: 'column.firehose', defaultMessage: 'Live feeds' },
|
||||
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
preferences: {
|
||||
id: 'navigation_bar.preferences',
|
||||
defaultMessage: 'Preferences',
|
||||
},
|
||||
followsAndFollowers: {
|
||||
id: 'navigation_bar.follows_and_followers',
|
||||
defaultMessage: 'Follows and followers',
|
||||
},
|
||||
about: { id: 'navigation_bar.about', defaultMessage: 'About' },
|
||||
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
||||
advancedInterface: {
|
||||
id: 'navigation_bar.advanced_interface',
|
||||
defaultMessage: 'Open in advanced web interface',
|
||||
},
|
||||
openedInClassicInterface: {
|
||||
id: 'navigation_bar.opened_in_classic_interface',
|
||||
defaultMessage:
|
||||
'Posts, accounts, and other specific pages are opened by default in the classic web interface.',
|
||||
},
|
||||
followRequests: {
|
||||
id: 'navigation_bar.follow_requests',
|
||||
defaultMessage: 'Follow requests',
|
||||
},
|
||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||
compose: { id: 'tabs_bar.publish', defaultMessage: 'New Post' },
|
||||
local: { id: 'column.local', defaultMessage: 'Local' },
|
||||
deepLocal: { id: 'column.deep_local', defaultMessage: 'Deep' },
|
||||
circles: { id: 'navigation_bar.circles', defaultMessage: 'Circles' },
|
||||
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
|
||||
const NotificationsLink = () => {
|
||||
const count = useAppSelector(selectUnreadNotificationGroupsCount);
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
key='notifications'
|
||||
transparent
|
||||
to='/notifications'
|
||||
icon={
|
||||
<IconWithBadge
|
||||
id='bell'
|
||||
icon={NotificationsIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
activeIcon={
|
||||
<IconWithBadge
|
||||
id='bell'
|
||||
icon={NotificationsActiveIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
text={intl.formatMessage(messages.notifications)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const FollowRequestsLink: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const count = useAppSelector(
|
||||
(state) =>
|
||||
(
|
||||
state.user_lists.getIn(['follow_requests', 'items']) as
|
||||
| ImmutableMap<string, unknown>
|
||||
| undefined
|
||||
)?.size ?? 0,
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchFollowRequests());
|
||||
}, [dispatch]);
|
||||
|
||||
if (count === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/follow_requests'
|
||||
icon={
|
||||
<IconWithBadge
|
||||
id='user-plus'
|
||||
icon={PersonAddIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
activeIcon={
|
||||
<IconWithBadge
|
||||
id='user-plus'
|
||||
icon={PersonAddActiveIcon}
|
||||
count={count}
|
||||
className='column-link__icon'
|
||||
/>
|
||||
}
|
||||
text={intl.formatMessage(messages.followRequests)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchLink: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const showAsSearch = useBreakpoint('full');
|
||||
|
||||
if (!trendsEnabled || showAsSearch) {
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={trendsEnabled ? '/explore' : '/search'}
|
||||
icon='search'
|
||||
iconComponent={SearchIcon}
|
||||
text={intl.formatMessage(messages.search)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/explore'
|
||||
icon='explore'
|
||||
iconComponent={ExploreIcon}
|
||||
activeIconComponent={ExploreActiveIcon}
|
||||
text={intl.formatMessage(messages.explore)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileCard: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleLogoutClick = useCallback(() => {
|
||||
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
|
||||
}, [dispatch]);
|
||||
|
||||
if (!me) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Account id={me} minimal size={36} />
|
||||
<IconButton
|
||||
icon='sign-out'
|
||||
iconComponent={LogoutIcon}
|
||||
title={intl.formatMessage(messages.logout)}
|
||||
onClick={handleLogoutClick}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MENU_WIDTH = 284;
|
||||
|
||||
export const NavigationPanel: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { signedIn, disabledAccountId } = useIdentity();
|
||||
const open = useAppSelector((state) => state.navigation.open);
|
||||
const dispatch = useAppDispatch();
|
||||
const openable = useBreakpoint('openable');
|
||||
const location = useLocation();
|
||||
const overlayRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(closeNavigation());
|
||||
}, [dispatch, location]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleDocumentClick = (e: MouseEvent) => {
|
||||
if (overlayRef.current && e.target === overlayRef.current) {
|
||||
dispatch(closeNavigation());
|
||||
}
|
||||
};
|
||||
|
||||
const handleDocumentKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
dispatch(closeNavigation());
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleDocumentClick);
|
||||
document.addEventListener('keyup', handleDocumentKeyUp);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick);
|
||||
document.removeEventListener('keyup', handleDocumentKeyUp);
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
const [{ x }, spring] = useSpring(
|
||||
() => ({
|
||||
x: open ? 0 : MENU_WIDTH,
|
||||
onRest: {
|
||||
x({ value }: { value: number }) {
|
||||
if (value === 0) {
|
||||
dispatch(openNavigation());
|
||||
} else if (value > 0) {
|
||||
dispatch(closeNavigation());
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
[open],
|
||||
);
|
||||
|
||||
const bind = useDrag(
|
||||
({ last, offset: [ox], velocity: [vx], direction: [dx], cancel }) => {
|
||||
if (ox < -70) {
|
||||
cancel();
|
||||
}
|
||||
|
||||
if (last) {
|
||||
if (ox > MENU_WIDTH / 2 || (vx > 0.5 && dx > 0)) {
|
||||
void spring.start({ x: MENU_WIDTH });
|
||||
} else {
|
||||
void spring.start({ x: 0 });
|
||||
}
|
||||
} else {
|
||||
void spring.start({ x: ox, immediate: true });
|
||||
}
|
||||
},
|
||||
{
|
||||
from: () => [x.get(), 0],
|
||||
filterTaps: true,
|
||||
bounds: { left: 0 },
|
||||
rubberband: true,
|
||||
},
|
||||
);
|
||||
|
||||
const isFirehoseActive = useCallback(
|
||||
(match: unknown, location: { pathname: string }): boolean => {
|
||||
if (location.pathname.startsWith('/public/local/fixed')) return false;
|
||||
return !!match || location.pathname.startsWith('/public');
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const previouslyFocusedElementRef = useRef<HTMLElement | null>();
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
const firstLink = document.querySelector<HTMLAnchorElement>(
|
||||
'.navigation-panel__menu .column-link',
|
||||
);
|
||||
previouslyFocusedElementRef.current =
|
||||
document.activeElement as HTMLElement;
|
||||
firstLink?.focus();
|
||||
} else {
|
||||
previouslyFocusedElementRef.current?.focus();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
let banner = undefined;
|
||||
|
||||
if (transientSingleColumn) {
|
||||
banner = (
|
||||
<div className='switch-to-advanced'>
|
||||
{intl.formatMessage(messages.openedInClassicInterface)}{' '}
|
||||
<a
|
||||
href={`/deck${location.pathname}`}
|
||||
className='switch-to-advanced__toggle'
|
||||
>
|
||||
{intl.formatMessage(messages.advancedInterface)}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleRefresh = useCallback(() => { window.location.reload(); }, []);
|
||||
|
||||
const showOverlay = openable && open;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'columns-area__panels__pane columns-area__panels__pane--start columns-area__panels__pane--navigational',
|
||||
{ 'columns-area__panels__pane--overlay': showOverlay },
|
||||
)}
|
||||
ref={overlayRef}
|
||||
>
|
||||
<animated.div
|
||||
className='columns-area__panels__pane__inner'
|
||||
{...bind()}
|
||||
style={openable ? { x } : undefined}
|
||||
>
|
||||
<div className='navigation-panel'>
|
||||
<div className='navigation-panel__logo'>
|
||||
<Link to='/' className='column-link column-link--logo'>
|
||||
<WordmarkLogo />
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ProfileCard />
|
||||
|
||||
{banner && <div className='navigation-panel__banner'>{banner}</div>}
|
||||
|
||||
<div className='navigation-panel__menu'>
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink
|
||||
to='/publish'
|
||||
icon='plus'
|
||||
iconComponent={AddIcon}
|
||||
activeIconComponent={AddIcon}
|
||||
text={intl.formatMessage(messages.compose)}
|
||||
className='button navigation-panel__compose-button'
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/home'
|
||||
icon='home'
|
||||
iconComponent={HomeIcon}
|
||||
activeIconComponent={HomeActiveIcon}
|
||||
text={intl.formatMessage(messages.home)}
|
||||
/>
|
||||
{enableLocalTimeline && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/public/local/fixed'
|
||||
icon='users'
|
||||
iconComponent={PeopleIcon}
|
||||
activeIconComponent={PeopleIcon}
|
||||
text={intl.formatMessage(messages.local)}
|
||||
/>
|
||||
)}
|
||||
{enableDtlMenu && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/tags/${dtlTag}`}
|
||||
icon='users'
|
||||
iconComponent={PeopleIcon}
|
||||
activeIconComponent={PeopleIcon}
|
||||
text={intl.formatMessage(messages.deepLocal)}
|
||||
/>
|
||||
)}
|
||||
<NotificationsLink />
|
||||
<FollowRequestsLink />
|
||||
</>
|
||||
)}
|
||||
|
||||
<ListPanel />
|
||||
|
||||
<SearchLink />
|
||||
|
||||
{(signedIn || timelinePreview) && (
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={((signedIn || !enableLocalTimeline) ? '/public' : '/public/local')}
|
||||
isActive={isFirehoseActive}
|
||||
icon='globe'
|
||||
iconComponent={PublicIcon}
|
||||
text={intl.formatMessage(messages.firehose)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!signedIn && (
|
||||
<div className='navigation-panel__sign-in-banner'>
|
||||
<hr />
|
||||
{disabledAccountId ? (
|
||||
<DisabledAccountBanner />
|
||||
) : (
|
||||
<SignInBanner />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{signedIn && (
|
||||
<>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/conversations'
|
||||
icon='at'
|
||||
iconComponent={AlternateEmailIcon}
|
||||
text={intl.formatMessage(messages.direct)}
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/circles'
|
||||
icon='user-circle'
|
||||
iconComponent={CirclesIcon}
|
||||
text={intl.formatMessage(messages.circles)}
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/bookmark_categories'
|
||||
icon='bookmarks'
|
||||
iconComponent={BookmarksIcon}
|
||||
activeIconComponent={BookmarksActiveIcon}
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/favourites'
|
||||
icon='star'
|
||||
iconComponent={StarIcon}
|
||||
activeIconComponent={StarActiveIcon}
|
||||
text={intl.formatMessage(messages.favourites)}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<ColumnLink
|
||||
transparent
|
||||
onClick={handleRefresh}
|
||||
icon='cog'
|
||||
iconComponent={RefreshIcon}
|
||||
text={intl.formatMessage(messages.refresh)}
|
||||
className='column-link column-link__transparent navigation-panel__refresh'
|
||||
/>
|
||||
<ColumnLink
|
||||
transparent
|
||||
href='/settings/preferences'
|
||||
icon='cog'
|
||||
iconComponent={SettingsIcon}
|
||||
text={intl.formatMessage(messages.preferences)}
|
||||
/>
|
||||
|
||||
<MoreLink />
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className='navigation-panel__legal'>
|
||||
<hr />
|
||||
|
||||
<ColumnLink
|
||||
transparent
|
||||
to='/about'
|
||||
icon='ellipsis-h'
|
||||
iconComponent={InfoIcon}
|
||||
text={intl.formatMessage(messages.about)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex-spacer' />
|
||||
|
||||
<NavigationPortal />
|
||||
</div>
|
||||
</animated.div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -4,8 +4,6 @@ import { FormattedMessage } from 'react-intl';
|
|||
|
||||
import { animated, config, useSpring } from '@react-spring/web';
|
||||
|
||||
import { reduceMotion } from 'mastodon/initial_state';
|
||||
|
||||
interface UploadAreaProps {
|
||||
active?: boolean;
|
||||
onClose: () => void;
|
||||
|
@ -39,7 +37,6 @@ export const UploadArea: React.FC<UploadAreaProps> = ({ active, onClose }) => {
|
|||
opacity: 1,
|
||||
},
|
||||
reverse: !active,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
const backgroundAnimStyles = useSpring({
|
||||
from: {
|
||||
|
@ -50,7 +47,6 @@ export const UploadArea: React.FC<UploadAreaProps> = ({ active, onClose }) => {
|
|||
},
|
||||
reverse: !active,
|
||||
config: config.wobbly,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
|
@ -17,19 +17,22 @@ const makeGetStatusIds = (pending = false) => createSelector([
|
|||
if (id === null || id === 'inline-follow-suggestions') return true;
|
||||
|
||||
const statusForId = statuses.get(id);
|
||||
let showStatus = true;
|
||||
|
||||
if (statusForId.get('account') === me) return true;
|
||||
|
||||
if (columnSettings.getIn(['shows', 'reblog']) === false) {
|
||||
showStatus = showStatus && statusForId.get('reblog') === null;
|
||||
if (columnSettings.getIn(['shows', 'reblog']) === false && statusForId.get('reblog') !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (columnSettings.getIn(['shows', 'reply']) === false) {
|
||||
showStatus = showStatus && (statusForId.get('in_reply_to_id') === null || statusForId.get('in_reply_to_account_id') === me);
|
||||
if (columnSettings.getIn(['shows', 'reply']) === false && statusForId.get('in_reply_to_id') !== null && statusForId.get('in_reply_to_account_id') !== me) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return showStatus;
|
||||
if (columnSettings.getIn(['shows', 'quote']) === false && statusForId.get('quote') !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
53
app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx
Normal file
53
app/javascript/mastodon/features/ui/hooks/useBreakpoint.tsx
Normal file
|
@ -0,0 +1,53 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
const breakpoints = {
|
||||
openable: 759, // Device width at which the sidebar becomes an openable hamburger menu
|
||||
full: 1174, // Device width at which all 3 columns can be displayed
|
||||
};
|
||||
|
||||
type Breakpoint = 'openable' | 'full';
|
||||
|
||||
export const useBreakpoint = (breakpoint: Breakpoint) => {
|
||||
const [isMatching, setIsMatching] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const mediaWatcher = window.matchMedia(
|
||||
`(max-width: ${breakpoints[breakpoint]}px)`,
|
||||
);
|
||||
|
||||
setIsMatching(mediaWatcher.matches);
|
||||
|
||||
const handleChange = (e: MediaQueryListEvent) => {
|
||||
setIsMatching(e.matches);
|
||||
};
|
||||
|
||||
mediaWatcher.addEventListener('change', handleChange);
|
||||
|
||||
return () => {
|
||||
mediaWatcher.removeEventListener('change', handleChange);
|
||||
};
|
||||
}, [breakpoint, setIsMatching]);
|
||||
|
||||
return isMatching;
|
||||
};
|
||||
|
||||
interface WithBreakpointType {
|
||||
matchesBreakpoint: boolean;
|
||||
}
|
||||
|
||||
export function withBreakpoint<P>(
|
||||
Component: React.ComponentType<P & WithBreakpointType>,
|
||||
breakpoint: Breakpoint = 'full',
|
||||
) {
|
||||
const displayName = `withMobileLayout(${Component.displayName ?? Component.name})`;
|
||||
|
||||
const ComponentWithBreakpoint = (props: P) => {
|
||||
const matchesBreakpoint = useBreakpoint(breakpoint);
|
||||
|
||||
return <Component matchesBreakpoint={matchesBreakpoint} {...props} />;
|
||||
};
|
||||
|
||||
ComponentWithBreakpoint.displayName = displayName;
|
||||
|
||||
return ComponentWithBreakpoint;
|
||||
}
|
|
@ -29,7 +29,7 @@ import { expandHomeTimeline } from '../../actions/timelines';
|
|||
import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards } from '../../initial_state';
|
||||
|
||||
import BundleColumnError from './components/bundle_column_error';
|
||||
import Header from './components/header';
|
||||
import { NavigationBar } from './components/navigation_bar';
|
||||
import { UploadArea } from './components/upload_area';
|
||||
import { HashtagMenuController } from './components/hashtag_menu_controller';
|
||||
import ColumnsAreaContainer from './containers/columns_area_container';
|
||||
|
@ -652,12 +652,11 @@ class UI extends PureComponent {
|
|||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
|
||||
<Header />
|
||||
|
||||
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'} forceOnboarding={firstLaunch && newAccount}>
|
||||
{children}
|
||||
</SwitchingColumnsArea>
|
||||
|
||||
<NavigationBar />
|
||||
{layout !== 'mobile' && <PictureInPicture />}
|
||||
<AlertsController />
|
||||
{!disableHoverCards && <HoverCardController />}
|
||||
|
|
|
@ -27,11 +27,7 @@ import {
|
|||
attachFullscreenListener,
|
||||
detachFullscreenListener,
|
||||
} from 'mastodon/features/ui/util/fullscreen';
|
||||
import {
|
||||
displayMedia,
|
||||
useBlurhash,
|
||||
reduceMotion,
|
||||
} from 'mastodon/initial_state';
|
||||
import { displayMedia, useBlurhash } from 'mastodon/initial_state';
|
||||
import { playerSettings } from 'mastodon/settings';
|
||||
|
||||
import { HotkeyIndicator } from './components/hotkey_indicator';
|
||||
|
@ -260,7 +256,6 @@ export const Video: React.FC<{
|
|||
setMuted(videoRef.current.muted);
|
||||
void api.start({
|
||||
volume: `${videoRef.current.volume * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -350,7 +345,6 @@ export const Video: React.FC<{
|
|||
videoRef.current.currentTime / videoRef.current.duration;
|
||||
void api.start({
|
||||
progress: isNaN(progress) ? '0%' : `${progress * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
config: config.stiff,
|
||||
});
|
||||
}
|
||||
|
@ -738,7 +732,6 @@ export const Video: React.FC<{
|
|||
if (lastTimeRange > -1) {
|
||||
void api.start({
|
||||
buffer: `${Math.ceil(videoRef.current.buffered.end(lastTimeRange) / videoRef.current.duration) * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
}
|
||||
}, [api]);
|
||||
|
@ -753,7 +746,6 @@ export const Video: React.FC<{
|
|||
|
||||
void api.start({
|
||||
volume: `${videoRef.current.muted ? 0 : videoRef.current.volume * 100}%`,
|
||||
immediate: reduceMotion,
|
||||
});
|
||||
|
||||
persistVolume(videoRef.current.volume, videoRef.current.muted);
|
||||
|
|
62
app/javascript/mastodon/hooks/useAudioContext.ts
Normal file
62
app/javascript/mastodon/hooks/useAudioContext.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
interface AudioContextOptions {
|
||||
audioElementRef: React.MutableRefObject<HTMLAudioElement | null>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and return an audio context instance for a given audio element [0].
|
||||
* Also returns an associated audio source, a gain node, and play and pause actions
|
||||
* which should be used instead of `audioElementRef.current.play/pause()`.
|
||||
*
|
||||
* [0] https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
|
||||
*/
|
||||
|
||||
export const useAudioContext = ({ audioElementRef }: AudioContextOptions) => {
|
||||
const audioContextRef = useRef<AudioContext>();
|
||||
const sourceRef = useRef<MediaElementAudioSourceNode>();
|
||||
const gainNodeRef = useRef<GainNode>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioElementRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = audioContextRef.current ?? new AudioContext();
|
||||
const source =
|
||||
sourceRef.current ??
|
||||
context.createMediaElementSource(audioElementRef.current);
|
||||
|
||||
const gainNode = context.createGain();
|
||||
gainNode.connect(context.destination);
|
||||
source.connect(gainNode);
|
||||
|
||||
audioContextRef.current = context;
|
||||
gainNodeRef.current = gainNode;
|
||||
sourceRef.current = source;
|
||||
|
||||
return () => {
|
||||
if (context.state !== 'closed') {
|
||||
void context.close();
|
||||
}
|
||||
};
|
||||
}, [audioElementRef]);
|
||||
|
||||
const playAudio = useCallback(() => {
|
||||
void audioElementRef.current?.play();
|
||||
void audioContextRef.current?.resume();
|
||||
}, [audioElementRef]);
|
||||
|
||||
const pauseAudio = useCallback(() => {
|
||||
audioElementRef.current?.pause();
|
||||
void audioContextRef.current?.suspend();
|
||||
}, [audioElementRef]);
|
||||
|
||||
return {
|
||||
audioContextRef,
|
||||
sourceRef,
|
||||
gainNodeRef,
|
||||
playAudio,
|
||||
pauseAudio,
|
||||
};
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
|
||||
const normalizeFrequencies = (arr: Float32Array): number[] => {
|
||||
return new Array(...arr).map((value: number) => {
|
||||
|
@ -10,12 +10,17 @@ const normalizeFrequencies = (arr: Float32Array): number[] => {
|
|||
});
|
||||
};
|
||||
|
||||
export const useAudioVisualizer = (
|
||||
ref: React.MutableRefObject<HTMLAudioElement | null>,
|
||||
numBands: number,
|
||||
) => {
|
||||
const audioContextRef = useRef<AudioContext>();
|
||||
const sourceRef = useRef<MediaElementAudioSourceNode>();
|
||||
interface AudioVisualiserOptions {
|
||||
audioContextRef: React.MutableRefObject<AudioContext | undefined>;
|
||||
sourceRef: React.MutableRefObject<MediaElementAudioSourceNode | undefined>;
|
||||
numBands: number;
|
||||
}
|
||||
|
||||
export const useAudioVisualizer = ({
|
||||
audioContextRef,
|
||||
sourceRef,
|
||||
numBands,
|
||||
}: AudioVisualiserOptions) => {
|
||||
const analyzerRef = useRef<AnalyserNode>();
|
||||
|
||||
const [frequencyBands, setFrequencyBands] = useState<number[]>(
|
||||
|
@ -23,47 +28,31 @@ export const useAudioVisualizer = (
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!audioContextRef.current) {
|
||||
audioContextRef.current = new AudioContext();
|
||||
if (audioContextRef.current) {
|
||||
analyzerRef.current = audioContextRef.current.createAnalyser();
|
||||
analyzerRef.current.smoothingTimeConstant = 0.6;
|
||||
analyzerRef.current.fftSize = 2048;
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (audioContextRef.current) {
|
||||
void audioContextRef.current.close();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [audioContextRef]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
audioContextRef.current &&
|
||||
analyzerRef.current &&
|
||||
!sourceRef.current &&
|
||||
ref.current
|
||||
) {
|
||||
sourceRef.current = audioContextRef.current.createMediaElementSource(
|
||||
ref.current,
|
||||
);
|
||||
if (analyzerRef.current && sourceRef.current) {
|
||||
sourceRef.current.connect(analyzerRef.current);
|
||||
sourceRef.current.connect(audioContextRef.current.destination);
|
||||
}
|
||||
const currentSource = sourceRef.current;
|
||||
|
||||
return () => {
|
||||
if (sourceRef.current) {
|
||||
sourceRef.current.disconnect();
|
||||
if (currentSource && analyzerRef.current) {
|
||||
currentSource.disconnect(analyzerRef.current);
|
||||
}
|
||||
};
|
||||
}, [ref]);
|
||||
}, [audioContextRef, sourceRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const source = sourceRef.current;
|
||||
const analyzer = analyzerRef.current;
|
||||
const context = audioContextRef.current;
|
||||
|
||||
if (!source || !analyzer || !context) {
|
||||
if (!analyzer || !context) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -94,19 +83,7 @@ export const useAudioVisualizer = (
|
|||
return () => {
|
||||
clearInterval(updateInterval);
|
||||
};
|
||||
}, [numBands]);
|
||||
}, [numBands, audioContextRef]);
|
||||
|
||||
const resume = useCallback(() => {
|
||||
if (audioContextRef.current) {
|
||||
void audioContextRef.current.resume();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const suspend = useCallback(() => {
|
||||
if (audioContextRef.current) {
|
||||
void audioContextRef.current.suspend();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return [resume, suspend, frequencyBands] as const;
|
||||
return frequencyBands;
|
||||
};
|
||||
|
|
13
app/javascript/mastodon/hooks/useLayout.ts
Normal file
13
app/javascript/mastodon/hooks/useLayout.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import type { LayoutType } from '../is_mobile';
|
||||
import { useAppSelector } from '../store';
|
||||
|
||||
export const useLayout = () => {
|
||||
const layout = useAppSelector(
|
||||
(state) => state.meta.get('layout') as LayoutType,
|
||||
);
|
||||
|
||||
return {
|
||||
singleColumn: layout === 'single-column' || layout === 'mobile',
|
||||
layout,
|
||||
};
|
||||
};
|
16
app/javascript/mastodon/hooks/usePrevious.ts
Normal file
16
app/javascript/mastodon/hooks/usePrevious.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { useRef, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Returns the previous state of the passed in value.
|
||||
* On first render, undefined is returned.
|
||||
*/
|
||||
|
||||
export function usePrevious<T>(value: T): T | undefined {
|
||||
const ref = useRef<T>();
|
||||
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
|
||||
return ref.current;
|
||||
}
|
|
@ -301,6 +301,7 @@
|
|||
"hashtag.follow": "Heuliañ ar ger-klik",
|
||||
"hashtag.unfollow": "Paouez heuliañ an hashtag",
|
||||
"hashtags.and_other": "…{count, plural, one {hag # all} other {ha # all}}",
|
||||
"home.column_settings.show_quotes": "Diskouez an arroudennoù",
|
||||
"home.column_settings.show_reblogs": "Diskouez ar skignadennoù",
|
||||
"home.column_settings.show_replies": "Diskouez ar respontoù",
|
||||
"home.hide_announcements": "Kuzhat ar c'hemennoù",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Vegeu més publicacions a {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Es poden haver perdut respostes d'altres servidors.",
|
||||
"hints.threads.see_more": "Vegeu més respostes a {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar les cites",
|
||||
"home.column_settings.show_reblogs": "Mostra els impulsos",
|
||||
"home.column_settings.show_replies": "Mostra les respostes",
|
||||
"home.hide_announcements": "Amaga els anuncis",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Zobrazit další příspěvky na {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Odpovědi z jiných serverů mohou chybět.",
|
||||
"hints.threads.see_more": "Zobrazit další odpovědi na {domain}",
|
||||
"home.column_settings.show_quotes": "Zobrazit citace",
|
||||
"home.column_settings.show_reblogs": "Zobrazit boosty",
|
||||
"home.column_settings.show_replies": "Zobrazit odpovědi",
|
||||
"home.hide_announcements": "Skrýt oznámení",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Gweinyddion a gyfyngir",
|
||||
"about.blocks": "Gweinyddion wedi'u cymedroli",
|
||||
"about.contact": "Cysylltwch â:",
|
||||
"about.default_locale": "Rhagosodedig",
|
||||
"about.disclaimer": "Mae Mastodon yn feddalwedd cod agored rhydd ac o dan hawlfraint Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Dyw'r rheswm ddim ar gael",
|
||||
"about.domain_blocks.preamble": "Fel rheol, mae Mastodon yn caniatáu i chi weld cynnwys gan unrhyw weinyddwr arall yn y ffedysawd a rhyngweithio â hi. Dyma'r eithriadau a wnaed ar y gweinydd penodol hwn.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Cyfyngedig",
|
||||
"about.domain_blocks.suspended.explanation": "Fydd data o'r gweinydd hwn ddim yn cael ei brosesu, ei gadw na'i gyfnewid, gan wneud unrhyw ryngweithio neu gyfathrebu gyda defnyddwyr o'r gweinydd hwn yn amhosibl.",
|
||||
"about.domain_blocks.suspended.title": "Wedi'i atal",
|
||||
"about.language_label": "Iaith",
|
||||
"about.not_available": "Dyw'r wybodaeth yma heb ei wneud ar gael ar y gweinydd hwn.",
|
||||
"about.powered_by": "Cyfrwng cymdeithasol datganoledig wedi ei yrru gan {mastodon}",
|
||||
"about.rules": "Rheolau'r gweinydd",
|
||||
|
@ -40,7 +42,7 @@
|
|||
"account.follow_back": "Dilyn nôl",
|
||||
"account.followers": "Dilynwyr",
|
||||
"account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} dilynwr} two {{counter} ddilynwr} other {{counter} dilynwyr}}",
|
||||
"account.followers_counter": "{count, plural, one {{counter} dilynwr} two {{counter} ddilynwr} other {{counter} dilynwr}}",
|
||||
"account.followers_you_know_counter": "{counter} rydych chi'n adnabod",
|
||||
"account.following": "Yn dilyn",
|
||||
"account.following_counter": "{count, plural, one {Yn dilyn {counter}} other {Yn dilyn {counter} arall}}",
|
||||
|
@ -127,7 +129,7 @@
|
|||
"annual_report.summary.thanks": "Diolch am fod yn rhan o Mastodon!",
|
||||
"attachments_list.unprocessed": "(heb eu prosesu)",
|
||||
"audio.hide": "Cuddio sain",
|
||||
"block_modal.remote_users_caveat": "Byddwn yn gofyn i'r gweinydd {domain} barchu eich penderfyniad. Fodd bynnag, nid yw cydymffurfiad wedi'i warantu gan y gall rhai gweinyddwyr drin rhwystro mewn ffyrdd gwahanol. Mae'n bosibl y bydd postiadau cyhoeddus yn dal i fod yn weladwy i ddefnyddwyr nad ydynt wedi mewngofnodi.",
|
||||
"block_modal.remote_users_caveat": "Byddwn yn gofyn i'r gweinydd {domain} barchu eich penderfyniad. Fodd bynnag, nid yw cydymffurfiad wedi'i warantu gan y gall rhai gweinyddwyr drin rhwystrau mewn ffyrdd gwahanol. Mae'n bosibl y bydd postiadau cyhoeddus yn dal i fod yn weladwy i ddefnyddwyr nad ydynt wedi mewngofnodi.",
|
||||
"block_modal.show_less": "Dangos llai",
|
||||
"block_modal.show_more": "Dangos rhagor",
|
||||
"block_modal.they_cant_mention": "Dydyn nhw ddim yn gallu eich crybwyll na'ch dilyn.",
|
||||
|
@ -236,8 +238,8 @@
|
|||
"confirmations.missing_alt_text.title": "Ychwanegu testun amgen?",
|
||||
"confirmations.mute.confirm": "Tewi",
|
||||
"confirmations.redraft.confirm": "Dileu ac ailddrafftio",
|
||||
"confirmations.redraft.message": "Ydych chi wir eisiau'r dileu'r postiad hwn a'i ailddrafftio? Bydd ffefrynnau a hybiau'n cael eu colli, a bydd atebion i'r post gwreiddiol yn mynd yn amddifad.",
|
||||
"confirmations.redraft.title": "Dileu ac ailddraftio'r postiad?",
|
||||
"confirmations.redraft.message": "Ydych chi wir eisiau'r dileu'r postiad hwn a'i ail lunio? Bydd ffefrynnau a hybiau'n cael eu colli, a bydd atebion i'r postiad gwreiddiol yn mynd yn amddifad.",
|
||||
"confirmations.redraft.title": "Dileu ac ail lunio'r postiad?",
|
||||
"confirmations.remove_from_followers.confirm": "Dileu dilynwr",
|
||||
"confirmations.remove_from_followers.message": "Bydd {name} yn rhoi'r gorau i'ch dilyn. A ydych yn siŵr eich bod am fwrw ymlaen?",
|
||||
"confirmations.remove_from_followers.title": "Tynnu dilynwr?",
|
||||
|
@ -286,8 +288,8 @@
|
|||
"domain_pill.their_username": "Eu dynodwr unigryw ar eu gweinydd. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.",
|
||||
"domain_pill.username": "Enw Defnyddiwr",
|
||||
"domain_pill.whats_in_a_handle": "Beth sydd mewn handlen?",
|
||||
"domain_pill.who_they_are": "Gan fod handlen yn dweud pwy yw rhywun a ble maen nhw, gallwch chi ryngweithio â phobl ar draws gwe gymdeithasol <button>llwyfannau wedi'u pweru gan ActivityPub</button> .",
|
||||
"domain_pill.who_you_are": "Oherwydd bod eich handlen yn dweud pwy ydych chi a ble rydych chi, gall pobl ryngweithio â chi ar draws gwe gymdeithasol <button>llwyfannau wedi'u pweru gan ActivityPub</button> .",
|
||||
"domain_pill.who_they_are": "Gan fod handlen yn dweud pwy yw rhywun a ble maen nhw, gallwch chi ryngweithio â phobl ar draws gwe gymdeithasol <button>llwyfannau wedi'u pweru gan ActivityPub</button>.",
|
||||
"domain_pill.who_you_are": "Oherwydd bod eich handlen yn dweud pwy ydych chi a ble rydych chi, gall pobl ryngweithio â chi ar draws gwe gymdeithasol <button>llwyfannau wedi'u pweru gan ActivityPub</button>.",
|
||||
"domain_pill.your_handle": "Eich handlen:",
|
||||
"domain_pill.your_server": "Eich cartref digidol, lle mae'ch holl bostiadau'n byw. Ddim yn hoffi'r un hon? Trosglwyddwch weinyddion ar unrhyw adeg a dewch â'ch dilynwyr hefyd.",
|
||||
"domain_pill.your_username": "Eich dynodwr unigryw ar y gweinydd hwn. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.",
|
||||
|
@ -314,26 +316,26 @@
|
|||
"empty_column.account_hides_collections": "Mae'r defnyddiwr wedi dewis i beidio rhannu'r wybodaeth yma",
|
||||
"empty_column.account_suspended": "Cyfrif wedi'i atal",
|
||||
"empty_column.account_timeline": "Dim postiadau yma!",
|
||||
"empty_column.account_unavailable": "Nid yw'r proffil ar gael",
|
||||
"empty_column.account_unavailable": "Dyw'r proffil ddim ar gael",
|
||||
"empty_column.blocks": "Dydych chi heb rwystro unrhyw ddefnyddwyr eto.",
|
||||
"empty_column.bookmarked_statuses": "Does gennych chi ddim unrhyw bostiad wedi'u cadw fel nod tudalen eto. Pan fyddwch yn gosod nod tudalen i un, mi fydd yn ymddangos yma.",
|
||||
"empty_column.community": "Mae'r ffrwd lleol yn wag. Beth am ysgrifennu rhywbeth cyhoeddus!",
|
||||
"empty_column.direct": "Does gennych chi unrhyw grybwylliadau preifat eto. Pan fyddwch chi'n anfon neu'n derbyn un, bydd yn ymddangos yma.",
|
||||
"empty_column.domain_blocks": "Nid oes unrhyw barthau wedi'u blocio eto.",
|
||||
"empty_column.domain_blocks": "Does dim parthau wedi'u rhwystro eto.",
|
||||
"empty_column.explore_statuses": "Does dim pynciau llosg ar hyn o bryd. Dewch nôl nes ymlaen!",
|
||||
"empty_column.favourited_statuses": "Rydych chi heb ffafrio unrhyw bostiadau eto. Pan byddwch chi'n ffafrio un, bydd yn ymddangos yma.",
|
||||
"empty_column.favourites": "Nid oes unrhyw un wedi ffafrio'r postiad hwn eto. Pan fydd rhywun yn gwneud hynny, byddan nhw'n ymddangos yma.",
|
||||
"empty_column.follow_requests": "Nid oes gennych unrhyw geisiadau dilyn eto. Pan fyddwch yn derbyn un, byddan nhw'n ymddangos yma.",
|
||||
"empty_column.followed_tags": "Nid ydych wedi dilyn unrhyw hashnodau eto. Pan fyddwch chi'n gwneud hynny, byddan nhw'n ymddangos yma.",
|
||||
"empty_column.hashtag": "Nid oes dim ar yr hashnod hwn eto.",
|
||||
"empty_column.home": "Mae eich ffrwd gartref yn wag! Dilynwch fwy o bobl i'w llenwi.",
|
||||
"empty_column.favourites": "Does neb wedi ffafrio'r postiad hwn eto. Pan fydd rhywun yn gwneud hynny, byddan nhw'n ymddangos yma.",
|
||||
"empty_column.follow_requests": "Does gennych chi ddim ceisiadau dilyn eto. Pan fyddwch yn derbyn un, byddan nhw'n ymddangos yma.",
|
||||
"empty_column.followed_tags": "Dydych chi heb ddilyn unrhyw hashnodau eto. Pan fyddwch chi'n gwneud hynny, byddan nhw'n ymddangos yma.",
|
||||
"empty_column.hashtag": "Does dim ar yr hashnod hwn eto.",
|
||||
"empty_column.home": "Mae eich ffrwd gartref yn wag! Dilynwch ragor o bobl i'w llenwi.",
|
||||
"empty_column.list": "Does dim yn y rhestr yma eto. Pan fydd aelodau'r rhestr yn cyhoeddi postiad newydd, mi fydd yn ymddangos yma.",
|
||||
"empty_column.mutes": "Nid ydych wedi tewi unrhyw ddefnyddwyr eto.",
|
||||
"empty_column.mutes": "Dydych chi heb dewi unrhyw ddefnyddwyr eto.",
|
||||
"empty_column.notification_requests": "Dim i boeni amdano! Does dim byd yma. Pan fyddwch yn derbyn hysbysiadau newydd, byddan nhw'n ymddangos yma yn ôl eich gosodiadau.",
|
||||
"empty_column.notifications": "Nid oes gennych unrhyw hysbysiadau eto. Rhyngweithiwch ag eraill i ddechrau'r sgwrs.",
|
||||
"empty_column.notifications": "Does gennych chi ddim hysbysiadau eto. Pan fyddwch chi'n rhyngweithio ag eraill, byddwch yn ei weld yma.",
|
||||
"empty_column.public": "Does dim byd yma! Ysgrifennwch rywbeth cyhoeddus, neu dilynwch ddefnyddwyr o weinyddion eraill i'w lanw",
|
||||
"error.unexpected_crash.explanation": "Oherwydd gwall yn ein cod neu oherwydd problem cysondeb porwr, nid oedd y dudalen hon gallu cael ei dangos yn gywir.",
|
||||
"error.unexpected_crash.explanation_addons": "Nid oes modd dangos y dudalen hon yn gywir. Mae'r gwall hwn yn debygol o gael ei achosi gan ategyn porwr neu offer cyfieithu awtomatig.",
|
||||
"error.unexpected_crash.explanation_addons": "Does dim modd dangos y dudalen hon yn gywir. Mae'r gwall hwn yn debygol o gael ei achosi gan ategyn porwr neu offer cyfieithu awtomatig.",
|
||||
"error.unexpected_crash.next_steps": "Ceisiwch ail-lwytho'r dudalen. Os nad yw hyn yn eich helpu, efallai gallwch ddefnyddio Mastodon trwy borwr neu ap brodorol gwahanol.",
|
||||
"error.unexpected_crash.next_steps_addons": "Ceisiwch eu hanalluogi ac adnewyddu'r dudalen. Os nad yw hynny'n helpu, efallai y byddwch yn dal i allu defnyddio Mastodon trwy borwr neu ap cynhenid arall.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Copïo'r olrhain stac i'r clipfwrdd",
|
||||
|
@ -343,11 +345,12 @@
|
|||
"explore.trending_links": "Newyddion",
|
||||
"explore.trending_statuses": "Postiadau",
|
||||
"explore.trending_tags": "Hashnodau",
|
||||
"featured_carousel.header": "{count, plural, one {Postiad wedi'i binio} other {Postiadau wedi'u pinio}}",
|
||||
"featured_carousel.next": "Nesaf",
|
||||
"featured_carousel.post": "Postiad",
|
||||
"featured_carousel.previous": "Blaenorol",
|
||||
"featured_carousel.slide": "{index} o {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Nid yw'r categori hidlo hwn yn berthnasol i'r cyd-destun yr ydych wedi cyrchu'r postiad hwn ynddo. Os ydych chi am i'r postiad gael ei hidlo yn y cyd-destun hwn hefyd, bydd yn rhaid i chi olygu'r hidlydd.",
|
||||
"filter_modal.added.context_mismatch_explanation": "Dyw'r categori hidlo hwn ddim yn berthnasol i'r cyd-destun yr ydych wedi cyrchu'r postiad hwn ynddo. Os ydych chi am i'r postiad gael ei hidlo yn y cyd-destun hwn hefyd, bydd yn rhaid i chi olygu'r hidlydd.",
|
||||
"filter_modal.added.context_mismatch_title": "Diffyg cyfatebiaeth cyd-destun!",
|
||||
"filter_modal.added.expired_explanation": "Mae'r categori hidlydd hwn wedi dod i ben, bydd angen i chi newid y dyddiad dod i ben er mwyn iddo fod yn berthnasol.",
|
||||
"filter_modal.added.expired_title": "Hidlydd wedi dod i ben!",
|
||||
|
@ -367,11 +370,11 @@
|
|||
"filtered_notifications_banner.pending_requests": "Oddi wrth {count, plural, =0 {no one} one {un person} two {# berson} few {# pherson} other {# person}} efallai eich bod yn eu hadnabod",
|
||||
"filtered_notifications_banner.title": "Hysbysiadau wedi'u hidlo",
|
||||
"firehose.all": "Popeth",
|
||||
"firehose.local": "Gweinydd hwn",
|
||||
"firehose.local": "Y gweinydd hwn",
|
||||
"firehose.remote": "Gweinyddion eraill",
|
||||
"follow_request.authorize": "Awdurdodi",
|
||||
"follow_request.reject": "Gwrthod",
|
||||
"follow_requests.unlocked_explanation": "Er nid yw eich cyfrif wedi'i gloi, roedd y staff {domain} yn meddwl efallai hoffech adolygu ceisiadau dilyn o'r cyfrifau rhain wrth law.",
|
||||
"follow_requests.unlocked_explanation": "Er nad yw eich cyfrif wedi'i gloi, roedd y staff {domain} yn meddwl efallai hoffech adolygu ceisiadau dilyn o'r cyfrifau rhain wrth law.",
|
||||
"follow_suggestions.curated_suggestion": "Dewis staff",
|
||||
"follow_suggestions.dismiss": "Peidio â dangos hwn eto",
|
||||
"follow_suggestions.featured_longer": "Wedi'i ddewis â llaw gan dîm {domain}",
|
||||
|
@ -384,32 +387,32 @@
|
|||
"follow_suggestions.personalized_suggestion": "Awgrym personol",
|
||||
"follow_suggestions.popular_suggestion": "Awgrym poblogaidd",
|
||||
"follow_suggestions.popular_suggestion_longer": "Yn boblogaidd ar {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Yn debyg i broffiliau y gwnaethoch chi eu dilyn yn ddiweddar",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Yn debyg i broffiliau rydych wedi'u dilyn yn ddiweddar",
|
||||
"follow_suggestions.view_all": "Gweld y cyfan",
|
||||
"follow_suggestions.who_to_follow": "Pwy i ddilyn",
|
||||
"followed_tags": "Hashnodau rydych yn eu dilyn",
|
||||
"footer.about": "Ynghylch",
|
||||
"footer.directory": "Cyfeiriadur proffiliau",
|
||||
"footer.get_app": "Lawrlwytho'r ap",
|
||||
"footer.get_app": "Llwytho'r ap i lawr",
|
||||
"footer.keyboard_shortcuts": "Bysellau brys",
|
||||
"footer.privacy_policy": "Polisi preifatrwydd",
|
||||
"footer.source_code": "Gweld y cod ffynhonnell",
|
||||
"footer.status": "Statws",
|
||||
"footer.terms_of_service": "Telerau gwasanaeth",
|
||||
"generic.saved": "Wedi'i Gadw",
|
||||
"getting_started.heading": "Dechrau",
|
||||
"getting_started.heading": "Dechrau arni",
|
||||
"hashtag.admin_moderation": "Agor rhyngwyneb cymedroli #{name}",
|
||||
"hashtag.browse": "Pori postiadau yn #{hashtag}",
|
||||
"hashtag.browse_from_account": "Pori postiadau gan @{name} yn #{hashtag}",
|
||||
"hashtag.column_header.tag_mode.all": "a {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "neu {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "heb {additional}",
|
||||
"hashtag.column_settings.select.no_options_message": "Dim awgrymiadau i'w weld",
|
||||
"hashtag.column_settings.select.no_options_message": "Dim awgrymiadau i'w gweld",
|
||||
"hashtag.column_settings.select.placeholder": "Mewnbynnu hashnodau…",
|
||||
"hashtag.column_settings.tag_mode.all": "Pob un o'r rhain",
|
||||
"hashtag.column_settings.tag_mode.any": "Unrhyw un o'r rhain",
|
||||
"hashtag.column_settings.tag_mode.none": "Dim o'r rhain",
|
||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||
"hashtag.column_settings.tag_toggle": "Cynnwys tagiau ychwanegol ar gyfer y golofn hon",
|
||||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} cyfranogwr} other {{counter} cyfranogwr}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {postiad {counter}} other {postiad {counter}}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} postiad} other {{counter} postiad}} heddiw",
|
||||
|
@ -434,7 +437,7 @@
|
|||
"home.pending_critical_update.link": "Gweld diweddariadau",
|
||||
"home.pending_critical_update.title": "Mae diweddariad diogelwch hanfodol ar gael!",
|
||||
"home.show_announcements": "Dangos cyhoeddiadau",
|
||||
"ignore_notifications_modal.disclaimer": "Ni all Mastodon hysbysu defnyddwyr eich bod wedi anwybyddu eu hysbysiadau. Ni fydd anwybyddu hysbysiadau yn atal y negeseuon eu hunain rhag cael eu hanfon.",
|
||||
"ignore_notifications_modal.disclaimer": "Dyw Mastodon ddim yn gallu hysbysu defnyddwyr eich bod wedi anwybyddu eu hysbysiadau. Bydd anwybyddu hysbysiadau ddim yn atal y negeseuon eu hunain rhag cael eu hanfon.",
|
||||
"ignore_notifications_modal.filter_instead": "Hidlo yn lle hynny",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Byddwch yn dal i allu derbyn, gwrthod neu adrodd ar ddefnyddwyr",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Mae hidlo yn helpu i osgoi dryswch posibl",
|
||||
|
@ -461,12 +464,12 @@
|
|||
"interaction_modal.title.reblog": "Hybu postiad {name}",
|
||||
"interaction_modal.title.reply": "Ymateb i bostiad {name}",
|
||||
"interaction_modal.title.vote": "Pleidleisiwch ym mhleidlais {name}",
|
||||
"interaction_modal.username_prompt": "E.e. {example}",
|
||||
"interaction_modal.username_prompt": "e.e. {example}",
|
||||
"intervals.full.days": "{number, plural, one {# diwrnod} two {# ddiwrnod} other {# diwrnod}}",
|
||||
"intervals.full.hours": "{number, plural, one {# awr} other {# o oriau}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# funud} other {# o funudau}}",
|
||||
"keyboard_shortcuts.back": "Llywio nôl",
|
||||
"keyboard_shortcuts.blocked": "Agor rhestr defnyddwyr a flociwyd",
|
||||
"intervals.full.hours": "{number, plural, one {# awr} other {# awr}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# funud} other {# munud}}",
|
||||
"keyboard_shortcuts.back": "Symud nôl",
|
||||
"keyboard_shortcuts.blocked": "Agor rhestr defnyddwyr sydd wedi'i rwystro",
|
||||
"keyboard_shortcuts.boost": "Hybu postiad",
|
||||
"keyboard_shortcuts.column": "Ffocysu colofn",
|
||||
"keyboard_shortcuts.compose": "Ffocysu ar ardal cyfansoddi testun",
|
||||
|
@ -480,7 +483,7 @@
|
|||
"keyboard_shortcuts.heading": "Bysellau brys",
|
||||
"keyboard_shortcuts.home": "Agor ffrwd gartref",
|
||||
"keyboard_shortcuts.hotkey": "Bysell boeth",
|
||||
"keyboard_shortcuts.legend": "Dangos y rhestr hon",
|
||||
"keyboard_shortcuts.legend": "Dangos yr allwedd hon",
|
||||
"keyboard_shortcuts.local": "Agor ffrwd lleol",
|
||||
"keyboard_shortcuts.mention": "Crybwyll yr awdur",
|
||||
"keyboard_shortcuts.muted": "Agor rhestr defnyddwyr rydych wedi'u tewi",
|
||||
|
@ -489,7 +492,7 @@
|
|||
"keyboard_shortcuts.open_media": "Agor cyfryngau",
|
||||
"keyboard_shortcuts.pinned": "Agor rhestr postiadau wedi'u pinio",
|
||||
"keyboard_shortcuts.profile": "Agor proffil yr awdur",
|
||||
"keyboard_shortcuts.reply": "Ymateb i bostiad",
|
||||
"keyboard_shortcuts.reply": "Ateb postiad",
|
||||
"keyboard_shortcuts.requests": "Agor rhestr ceisiadau dilyn",
|
||||
"keyboard_shortcuts.search": "Ffocysu ar y bar chwilio",
|
||||
"keyboard_shortcuts.spoilers": "Dangos/cuddio'r maes CW",
|
||||
|
@ -557,7 +560,7 @@
|
|||
"navigation_bar.compose": "Cyfansoddi post newydd",
|
||||
"navigation_bar.direct": "Crybwylliadau preifat",
|
||||
"navigation_bar.discover": "Darganfod",
|
||||
"navigation_bar.domain_blocks": "Parthau wedi'u blocio",
|
||||
"navigation_bar.domain_blocks": "Parthau wedi'u rhwystro",
|
||||
"navigation_bar.explore": "Darganfod",
|
||||
"navigation_bar.favourites": "Ffefrynnau",
|
||||
"navigation_bar.filters": "Geiriau wedi'u tewi",
|
||||
|
@ -609,7 +612,7 @@
|
|||
"notification.moderation_warning.action_silence": "Mae eich cyfrif wedi'i gyfyngu.",
|
||||
"notification.moderation_warning.action_suspend": "Mae eich cyfrif wedi'i atal.",
|
||||
"notification.own_poll": "Mae eich pleidlais wedi dod i ben",
|
||||
"notification.poll": "Mae arolwg y gwnaethoch bleidleisio ynddo wedi dod i ben",
|
||||
"notification.poll": "Mae arolwg rydych wedi pleidleisio ynddo wedi dod i ben",
|
||||
"notification.reblog": "Hybodd {name} eich post",
|
||||
"notification.reblog.name_and_others_with_link": "Mae {name} a <a>{count, plural, one {# arall} other {# arall}}</a> wedi hybu eich postiad",
|
||||
"notification.relationships_severance_event": "Wedi colli cysylltiad â {name}",
|
||||
|
@ -652,7 +655,7 @@
|
|||
"notifications.column_settings.group": "Grŵp",
|
||||
"notifications.column_settings.mention": "Crybwylliadau:",
|
||||
"notifications.column_settings.poll": "Canlyniadau pleidlais:",
|
||||
"notifications.column_settings.push": "Hysbysiadau gwthiadwy",
|
||||
"notifications.column_settings.push": "Hysbysiadau gwthio",
|
||||
"notifications.column_settings.reblog": "Hybiau:",
|
||||
"notifications.column_settings.show": "Dangos yn y golofn",
|
||||
"notifications.column_settings.sound": "Chwarae sain",
|
||||
|
@ -665,25 +668,25 @@
|
|||
"notifications.filter.favourites": "Ffefrynnau",
|
||||
"notifications.filter.follows": "Yn dilyn",
|
||||
"notifications.filter.mentions": "Crybwylliadau",
|
||||
"notifications.filter.polls": "Canlyniadau polau",
|
||||
"notifications.filter.polls": "Canlyniadau pleidleisio",
|
||||
"notifications.filter.statuses": "Diweddariadau gan bobl rydych chi'n eu dilyn",
|
||||
"notifications.grant_permission": "Caniatáu.",
|
||||
"notifications.group": "{count} hysbysiad",
|
||||
"notifications.mark_as_read": "Marciwch bob hysbysiad wedi'i ddarllen",
|
||||
"notifications.permission_denied": "Nid oes hysbysiadau bwrdd gwaith ar gael oherwydd cais am ganiatâd porwr a wrthodwyd yn flaenorol",
|
||||
"notifications.permission_denied_alert": "Nid oes modd galluogi hysbysiadau bwrdd gwaith, gan fod caniatâd porwr wedi'i wrthod o'r blaen",
|
||||
"notifications.permission_required": "Nid oes hysbysiadau bwrdd gwaith ar gael oherwydd na roddwyd y caniatâd gofynnol.",
|
||||
"notifications.permission_denied": "Does dim hysbysiadau bwrdd gwaith ar gael oherwydd cais am ganiatâd porwr a wrthodwyd yn flaenorol",
|
||||
"notifications.permission_denied_alert": "Does dim modd galluogi hysbysiadau bwrdd gwaith, gan fod caniatâd porwr wedi'i wrthod o'r blaen",
|
||||
"notifications.permission_required": "Does dim hysbysiadau bwrdd gwaith ar gael oherwydd na roddwyd y caniatâd gofynnol.",
|
||||
"notifications.policy.accept": "Derbyn",
|
||||
"notifications.policy.accept_hint": "Dangos mewn hysbysiadau",
|
||||
"notifications.policy.drop": "Anwybyddu",
|
||||
"notifications.policy.drop_hint": "Anfon i'r gwagle, byth i'w gweld eto",
|
||||
"notifications.policy.filter": "Hidlo",
|
||||
"notifications.policy.filter_hint": "Anfon i flwch derbyn hysbysiadau wedi'u hidlo",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Cyfyngedig gan gymedrolwyr gweinydd",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Cyfyngwyd gan gymedrolwyr gweinydd",
|
||||
"notifications.policy.filter_limited_accounts_title": "Cyfrifon wedi'u cymedroli",
|
||||
"notifications.policy.filter_new_accounts.hint": "Crëwyd o fewn {days, lluosog, un {yr un diwrnod} arall {y # diwrnod}} diwethaf",
|
||||
"notifications.policy.filter_new_accounts.hint": "Crëwyd o fewn {days, plural, one {yr un diwrnod} other {y # diwrnod}} diwethaf",
|
||||
"notifications.policy.filter_new_accounts_title": "Cyfrifon newydd",
|
||||
"notifications.policy.filter_not_followers_hint": "Gan gynnwys pobl sydd wedi bod yn eich dilyn am llai {days, plural, un {nag un diwrnod} arall {na # diwrnod}}",
|
||||
"notifications.policy.filter_not_followers_hint": "Gan gynnwys pobl sydd wedi bod yn eich dilyn am llai {days, plural, one {nag un diwrnod} other {na # diwrnod}}",
|
||||
"notifications.policy.filter_not_followers_title": "Pobl sydd ddim yn eich dilyn",
|
||||
"notifications.policy.filter_not_following_hint": "Hyd nes i chi eu cymeradwyo â llaw",
|
||||
"notifications.policy.filter_not_following_title": "Pobl nad ydych yn eu dilyn",
|
||||
|
@ -699,7 +702,7 @@
|
|||
"onboarding.follows.search": "Chwilio",
|
||||
"onboarding.follows.title": "Dilynwch bobl i gychwyn arni",
|
||||
"onboarding.profile.discoverable": "Gwnewch fy mhroffil yn un y gellir ei ddarganfod",
|
||||
"onboarding.profile.discoverable_hint": "Pan fyddwch yn optio i mewn i ddarganfodadwyedd ar Mastodon, gall eich postiadau ymddangos mewn canlyniadau chwilio a threndiau, ac efallai y bydd eich proffil yn cael ei awgrymu i bobl sydd â diddordebau tebyg i chi.",
|
||||
"onboarding.profile.discoverable_hint": "Pan fyddwch yn dewis ymuno â darganfod ar Mastodon, gall eich postiadau ymddangos mewn canlyniadau chwilio a threndiau, ac efallai y bydd eich proffil yn cael ei awgrymu i bobl sydd â diddordebau tebyg i chi.",
|
||||
"onboarding.profile.display_name": "Enw dangos",
|
||||
"onboarding.profile.display_name_hint": "Eich enw llawn neu'ch enw hwyl…",
|
||||
"onboarding.profile.note": "Bywgraffiad",
|
||||
|
@ -710,7 +713,7 @@
|
|||
"onboarding.profile.upload_header": "Llwytho pennyn proffil",
|
||||
"password_confirmation.exceeds_maxlength": "Mae'r cadarnhad cyfrinair yn fwy nag uchafswm hyd y cyfrinair",
|
||||
"password_confirmation.mismatching": "Nid yw'r cadarnhad cyfrinair yn cyfateb",
|
||||
"picture_in_picture.restore": "Rhowch ef yn ôl",
|
||||
"picture_in_picture.restore": "Rhowch e nôl",
|
||||
"poll.closed": "Ar gau",
|
||||
"poll.refresh": "Adnewyddu",
|
||||
"poll.reveal": "Gweld y canlyniadau",
|
||||
|
@ -724,9 +727,9 @@
|
|||
"privacy.change": "Addasu preifatrwdd y post",
|
||||
"privacy.direct.long": "Pawb sydd â sôn amdanyn nhw yn y postiad",
|
||||
"privacy.direct.short": "Crybwylliad preifat",
|
||||
"privacy.private.long": "Eich dilynwyr yn unig",
|
||||
"privacy.private.long": "Dim ond eich dilynwyr",
|
||||
"privacy.private.short": "Dilynwyr",
|
||||
"privacy.public.long": "Unrhyw ar ac oddi ar Mastodon",
|
||||
"privacy.public.long": "Unrhyw un ar ac oddi ar Mastodon",
|
||||
"privacy.public.short": "Cyhoeddus",
|
||||
"privacy.unlisted.additional": "Mae hwn yn ymddwyn yn union fel y cyhoeddus, ac eithrio na fydd y postiad yn ymddangos mewn ffrydiau byw neu hashnodau, archwilio, neu chwiliad Mastodon, hyd yn oed os ydych wedi eich cynnwys ar draws y cyfrif.",
|
||||
"privacy.unlisted.long": "Llai o ddathliadau algorithmig",
|
||||
|
@ -736,7 +739,7 @@
|
|||
"recommended": "Argymhellwyd",
|
||||
"refresh": "Adnewyddu",
|
||||
"regeneration_indicator.please_stand_by": "Arhoswch am dipyn.",
|
||||
"regeneration_indicator.preparing_your_home_feed": "Paratoi eich llif cartref…",
|
||||
"regeneration_indicator.preparing_your_home_feed": "Yn paratoi eich ffrwd gartref…",
|
||||
"relative_time.days": "{number}d",
|
||||
"relative_time.full.days": "{number, plural, one {# diwrnod} other {# diwrnod}} yn ôl",
|
||||
"relative_time.full.hours": "{number, plural, one {# awr} other {# awr}} yn ôl",
|
||||
|
@ -749,16 +752,16 @@
|
|||
"relative_time.seconds": "{number} eiliad",
|
||||
"relative_time.today": "heddiw",
|
||||
"reply_indicator.attachments": "{count, plural, one {# atodiad} other {# atodiad}}",
|
||||
"reply_indicator.cancel": "Canslo",
|
||||
"reply_indicator.poll": "Arolwg",
|
||||
"report.block": "Blocio",
|
||||
"reply_indicator.cancel": "Diddymu",
|
||||
"reply_indicator.poll": "Pleidlais",
|
||||
"report.block": "Rhwystro",
|
||||
"report.block_explanation": "Ni welwch chi eu postiadau. Ni allan nhw weld eich postiadau na'ch dilyn. Byddan nhw'n gallu gweld eu bod nhw wedi'u rhwystro.",
|
||||
"report.categories.legal": "Cyfreithiol",
|
||||
"report.categories.other": "Arall",
|
||||
"report.categories.spam": "Sbam",
|
||||
"report.categories.violation": "Mae cynnwys yn torri un neu fwy o reolau'r gweinydd",
|
||||
"report.category.subtitle": "Dewiswch yr ateb gorau",
|
||||
"report.category.title": "Beth sy'n digwydd gyda'r {type} yma?",
|
||||
"report.category.title": "Beth sy'n digwydd gyda'r {type} yma",
|
||||
"report.category.title_account": "proffil",
|
||||
"report.category.title_status": "post",
|
||||
"report.close": "Iawn",
|
||||
|
@ -770,11 +773,11 @@
|
|||
"report.next": "Nesaf",
|
||||
"report.placeholder": "Sylwadau ychwanegol",
|
||||
"report.reasons.dislike": "Dydw i ddim yn ei hoffi",
|
||||
"report.reasons.dislike_description": "Nid yw'n rhywbeth yr ydych am ei weld",
|
||||
"report.reasons.dislike_description": "Dyw e ddim yn rhywbeth rydych am ei weld",
|
||||
"report.reasons.legal": "Mae'n anghyfreithlon",
|
||||
"report.reasons.legal_description": "Rydych chi'n credu ei fod yn torri cyfraith eich gwlad chi neu wlad y gweinydd",
|
||||
"report.reasons.other": "Mae'n rhywbeth arall",
|
||||
"report.reasons.other_description": "Nid yw'r mater yn ffitio i gategorïau eraill",
|
||||
"report.reasons.other_description": "Dyw'r mater ddim yn ffitio i gategorïau eraill",
|
||||
"report.reasons.spam": "Sbam yw e",
|
||||
"report.reasons.spam_description": "Dolenni maleisus, ymgysylltu ffug, neu ymatebion ailadroddus",
|
||||
"report.reasons.violation": "Mae'n torri rheolau'r gweinydd",
|
||||
|
@ -801,7 +804,7 @@
|
|||
"report_notification.categories.violation": "Torri rheol",
|
||||
"report_notification.categories.violation_sentence": "torri rheolau",
|
||||
"report_notification.open": "Agor adroddiad",
|
||||
"search.no_recent_searches": "Does dim chwiliadau diweddar",
|
||||
"search.no_recent_searches": "Does dim chwilio diweddar",
|
||||
"search.placeholder": "Chwilio",
|
||||
"search.quick_action.account_search": "Proffiliau sy'n cyfateb i {x}",
|
||||
"search.quick_action.go_to_account": "Mynd i broffil {x}",
|
||||
|
@ -835,13 +838,13 @@
|
|||
"sign_in_banner.mastodon_is": "Mastodon yw'r ffordd orau o gadw i fyny â'r hyn sy'n digwydd.",
|
||||
"sign_in_banner.sign_in": "Mewngofnodi",
|
||||
"sign_in_banner.sso_redirect": "Mewngofnodi neu Gofrestru",
|
||||
"status.admin_account": "Agor rhyngwyneb cymedroli ar gyfer @{name}",
|
||||
"status.admin_account": "Agor rhyngwyneb cymedroli @{name}",
|
||||
"status.admin_domain": "Agor rhyngwyneb cymedroli {domain}",
|
||||
"status.admin_status": "Agor y postiad hwn yn y rhyngwyneb cymedroli",
|
||||
"status.block": "Blocio @{name}",
|
||||
"status.bookmark": "Llyfrnodi",
|
||||
"status.block": "Rhwystro @{name}",
|
||||
"status.bookmark": "Nod tudalen",
|
||||
"status.cancel_reblog_private": "Dadhybu",
|
||||
"status.cannot_reblog": "Nid oes modd hybu'r postiad hwn",
|
||||
"status.cannot_reblog": "Does dim modd hybu'r postiad hwn",
|
||||
"status.continued_thread": "Edefyn parhaus",
|
||||
"status.copy": "Copïo dolen i'r post",
|
||||
"status.delete": "Dileu",
|
||||
|
@ -850,7 +853,7 @@
|
|||
"status.direct_indicator": "Crybwyll preifat",
|
||||
"status.edit": "Golygu",
|
||||
"status.edited": "Golygwyd ddiwethaf {date}",
|
||||
"status.edited_x_times": "Golygwyd {count, plural, one {count} two {count} other {{count} gwaith}}",
|
||||
"status.edited_x_times": "Golygwyd {count, plural, one {{count} gwaith} other {{count} gwaith}}",
|
||||
"status.embed": "Cael y cod mewnblannu",
|
||||
"status.favourite": "Ffafrio",
|
||||
"status.favourites": "{count, plural, one {ffefryn} other {ffefryn}}",
|
||||
|
@ -880,13 +883,13 @@
|
|||
"status.reblogged_by": "Hybodd {name}",
|
||||
"status.reblogs": "{count, plural, one {# hwb} other {# hwb}}",
|
||||
"status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.",
|
||||
"status.redraft": "Dileu ac ailddrafftio",
|
||||
"status.redraft": "Dileu ac ail lunio",
|
||||
"status.remove_bookmark": "Tynnu nod tudalen",
|
||||
"status.remove_favourite": "Tynnu o'r ffefrynnau",
|
||||
"status.replied_in_thread": "Atebodd mewn edefyn",
|
||||
"status.replied_in_thread": "Wedi ateb mewn edefyn",
|
||||
"status.replied_to": "Wedi ymateb i {name}",
|
||||
"status.reply": "Ymateb",
|
||||
"status.replyAll": "Ymateb i edefyn",
|
||||
"status.replyAll": "Ateb edefyn",
|
||||
"status.report": "Adrodd ar @{name}",
|
||||
"status.sensitive_warning": "Cynnwys sensitif",
|
||||
"status.share": "Rhannu",
|
||||
|
@ -910,23 +913,23 @@
|
|||
"time_remaining.days": "{number, plural, one {# diwrnod} other {# diwrnod}} ar ôl",
|
||||
"time_remaining.hours": "{number, plural, one {# awr} other {# awr}} ar ôl",
|
||||
"time_remaining.minutes": "{number, plural, one {# munud} other {# munud}} ar ôl",
|
||||
"time_remaining.moments": "Munudau yn weddill",
|
||||
"time_remaining.moments": "Munudau'n weddill",
|
||||
"time_remaining.seconds": "{number, plural, one {# eiliad} other {# eiliad}} ar ôl",
|
||||
"trends.counter_by_accounts": "{count, plural, zero {neb} one {{counter} person} two {{counter} berson} few {{counter} pherson} other {{counter} o bobl}} yn y {days, plural, one {diwrnod diwethaf} two {ddeuddydd diwethaf} other {{days} diwrnod diwethaf}}",
|
||||
"trends.trending_now": "Pynciau llosg",
|
||||
"trends.trending_now": "Wrthi'n trendio",
|
||||
"ui.beforeunload": "Byddwch yn colli eich drafft os byddwch yn gadael Mastodon.",
|
||||
"units.short.billion": "{count}biliwn",
|
||||
"units.short.million": "{count}miliwn",
|
||||
"units.short.thousand": "{count}mil",
|
||||
"upload_area.title": "Llusgwch a gollwng i lwytho",
|
||||
"upload_button.label": "Ychwanegwch gyfryngau (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "Wedi pasio'r uchafswm llwytho.",
|
||||
"upload_error.poll": "Nid oes modd llwytho ffeiliau â phleidleisiau.",
|
||||
"upload_button.label": "Ychwanegwch delweddau, fideo neu ffeil sain",
|
||||
"upload_error.limit": "Wedi mynd heibio'r uchafswm llwytho.",
|
||||
"upload_error.poll": "Does dim modd llwytho ffeiliau â phleidleisiau.",
|
||||
"upload_form.drag_and_drop.instructions": "I godi atodiad cyfryngau, pwyswch y space neu enter. Wrth lusgo, defnyddiwch y bysellau saeth i symud yr atodiad cyfryngau i unrhyw gyfeiriad penodol. Pwyswch space neu enter eto i ollwng yr atodiad cyfryngau yn ei safle newydd, neu pwyswch escape i ddiddymu.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodiad cyfryngau {item} ei ollwng.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Cafodd atodiad cyfryngau {item} ei ollwng.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodi cyfryngau {item} ei ollwng.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Cafodd atodi cyfryngau {item} ei ollwng.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "Symudwyd atodiad cyfryngau {item}.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Atodiad cyfryngau godwyd {item}.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Wedi codi atodiad cyfryngau {item}.",
|
||||
"upload_form.edit": "Golygu",
|
||||
"upload_progress.label": "Yn llwytho...",
|
||||
"upload_progress.processing": "Wrthi'n prosesu…",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Se flere indlæg på {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Der kan mangle svar fra andre servere.",
|
||||
"hints.threads.see_more": "Se flere svar på {domain}",
|
||||
"home.column_settings.show_quotes": "Vis citater",
|
||||
"home.column_settings.show_reblogs": "Vis fremhævelser",
|
||||
"home.column_settings.show_replies": "Vis svar",
|
||||
"home.hide_announcements": "Skjul bekendtgørelser",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Weitere Beiträge auf {domain} ansehen",
|
||||
"hints.threads.replies_may_be_missing": "Möglicherweise werden nicht alle Antworten von anderen Servern angezeigt.",
|
||||
"hints.threads.see_more": "Weitere Antworten auf {domain} ansehen",
|
||||
"home.column_settings.show_quotes": "Zitierte Beiträge anzeigen",
|
||||
"home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
|
||||
"home.column_settings.show_replies": "Antworten anzeigen",
|
||||
"home.hide_announcements": "Ankündigungen ausblenden",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Moderated servers",
|
||||
"about.contact": "Contact:",
|
||||
"about.default_locale": "Default",
|
||||
"about.disclaimer": "Mastodon is free, open-source software, and a trademark of Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Reason not available",
|
||||
"about.domain_blocks.preamble": "Mastodon generally allows you to view content from and interact with users from any other server in the Fediverse. These are the exceptions that have been made on this particular server.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Limited",
|
||||
"about.domain_blocks.suspended.explanation": "No data from this server will be processed, stored or exchanged, making any interaction or communication with users from this server impossible.",
|
||||
"about.domain_blocks.suspended.title": "Suspended",
|
||||
"about.language_label": "Language",
|
||||
"about.not_available": "This information has not been made available on this server.",
|
||||
"about.powered_by": "Decentralised social media powered by {mastodon}",
|
||||
"about.rules": "Server rules",
|
||||
|
@ -19,13 +21,21 @@
|
|||
"account.block_domain": "Block domain {domain}",
|
||||
"account.block_short": "Block",
|
||||
"account.blocked": "Blocked",
|
||||
"account.blocking": "Blocking",
|
||||
"account.cancel_follow_request": "Cancel follow",
|
||||
"account.copy": "Copy link to profile",
|
||||
"account.direct": "Privately mention @{name}",
|
||||
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
||||
"account.domain_blocking": "Blocking domain",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.enable_notifications": "Notify me when @{name} posts",
|
||||
"account.endorse": "Feature on profile",
|
||||
"account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}",
|
||||
"account.familiar_followers_one": "Followed by {name1}",
|
||||
"account.familiar_followers_two": "Followed by {name1} and {name2}",
|
||||
"account.featured": "Featured",
|
||||
"account.featured.accounts": "Profiles",
|
||||
"account.featured.hashtags": "Hashtags",
|
||||
"account.featured_tags.last_status_at": "Last post on {date}",
|
||||
"account.featured_tags.last_status_never": "No posts",
|
||||
"account.follow": "Follow",
|
||||
|
@ -33,9 +43,11 @@
|
|||
"account.followers": "Followers",
|
||||
"account.followers.empty": "No one follows this user yet.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}",
|
||||
"account.followers_you_know_counter": "{counter} you know",
|
||||
"account.following": "Following",
|
||||
"account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}",
|
||||
"account.follows.empty": "This user doesn't follow anyone yet.",
|
||||
"account.follows_you": "Follows you",
|
||||
"account.go_to_profile": "Go to profile",
|
||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
|
@ -50,18 +62,23 @@
|
|||
"account.mute_notifications_short": "Mute notifications",
|
||||
"account.mute_short": "Mute",
|
||||
"account.muted": "Muted",
|
||||
"account.muting": "Muting",
|
||||
"account.mutual": "You follow each other",
|
||||
"account.no_bio": "No description provided.",
|
||||
"account.open_original_page": "Open original page",
|
||||
"account.posts": "Posts",
|
||||
"account.posts_with_replies": "Posts and replies",
|
||||
"account.remove_from_followers": "Remove {name} from followers",
|
||||
"account.report": "Report @{name}",
|
||||
"account.requested": "Awaiting approval. Click to cancel follow request",
|
||||
"account.requested_follow": "{name} has requested to follow you",
|
||||
"account.requests_to_follow_you": "Requests to follow you",
|
||||
"account.share": "Share @{name}'s profile",
|
||||
"account.show_reblogs": "Show boosts from @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||
"account.unblock": "Unblock @{name}",
|
||||
"account.unblock_domain": "Unblock domain {domain}",
|
||||
"account.unblock_domain_short": "Unblock",
|
||||
"account.unblock_short": "Unblock",
|
||||
"account.unendorse": "Don't feature on profile",
|
||||
"account.unfollow": "Unfollow",
|
||||
|
@ -223,6 +240,9 @@
|
|||
"confirmations.redraft.confirm": "Delete & redraft",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||
"confirmations.redraft.title": "Delete & redraft post?",
|
||||
"confirmations.remove_from_followers.confirm": "Remove follower",
|
||||
"confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?",
|
||||
"confirmations.remove_from_followers.title": "Remove follower?",
|
||||
"confirmations.reply.confirm": "Reply",
|
||||
"confirmations.reply.message": "Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||
"confirmations.reply.title": "Overwrite post?",
|
||||
|
@ -290,6 +310,9 @@
|
|||
"emoji_button.search_results": "Search results",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.account_featured.me": "You have not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?",
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet. Did you know that you can feature your hashtags you use the most, and even your friend’s accounts on your profile?",
|
||||
"empty_column.account_featured_other.unknown": "This account has not featured anything yet.",
|
||||
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
|
||||
"empty_column.account_suspended": "Account suspended",
|
||||
"empty_column.account_timeline": "No posts here!",
|
||||
|
@ -322,6 +345,11 @@
|
|||
"explore.trending_links": "News",
|
||||
"explore.trending_statuses": "Posts",
|
||||
"explore.trending_tags": "Hashtags",
|
||||
"featured_carousel.header": "{count, plural, one {Pinned Post} other {Pinned Posts}}",
|
||||
"featured_carousel.next": "Next",
|
||||
"featured_carousel.post": "Post",
|
||||
"featured_carousel.previous": "Previous",
|
||||
"featured_carousel.slide": "{index} of {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
|
||||
"filter_modal.added.context_mismatch_title": "Context mismatch!",
|
||||
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
|
||||
|
@ -374,6 +402,8 @@
|
|||
"generic.saved": "Saved",
|
||||
"getting_started.heading": "Getting started",
|
||||
"hashtag.admin_moderation": "Open moderation interface for #{name}",
|
||||
"hashtag.browse": "Browse posts in #{hashtag}",
|
||||
"hashtag.browse_from_account": "Browse posts from @{name} in #{hashtag}",
|
||||
"hashtag.column_header.tag_mode.all": "and {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "or {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "without {additional}",
|
||||
|
@ -386,7 +416,10 @@
|
|||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} Following} other {{counter} Following}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} post} other {{counter} posts}} today",
|
||||
"hashtag.feature": "Feature on profile",
|
||||
"hashtag.follow": "Follow hashtag",
|
||||
"hashtag.mute": "Mute #{hashtag}",
|
||||
"hashtag.unfeature": "Don't feature on profile",
|
||||
"hashtag.unfollow": "Unfollow hashtag",
|
||||
"hashtags.and_other": "…and {count, plural, one {one more} other {# more}}",
|
||||
"hints.profiles.followers_may_be_missing": "Followers for this profile may be missing.",
|
||||
|
@ -397,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "See more posts on {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Replies from other servers may be missing.",
|
||||
"hints.threads.see_more": "See more replies on {domain}",
|
||||
"home.column_settings.show_quotes": "Show quotes",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.hide_announcements": "Hide announcements",
|
||||
|
@ -837,6 +871,13 @@
|
|||
"status.mute_conversation": "Mute conversation",
|
||||
"status.open": "Expand this post",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.quote_error.filtered": "Hidden due to one of your filters",
|
||||
"status.quote_error.not_found": "This post cannot be displayed.",
|
||||
"status.quote_error.pending_approval": "This post is pending approval from the original author.",
|
||||
"status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.",
|
||||
"status.quote_error.removed": "This post was removed by its author.",
|
||||
"status.quote_error.unauthorized": "This post cannot be displayed as you are not authorised",
|
||||
"status.quote_post_author": "Post by {name}",
|
||||
"status.read_more": "Read more",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblog_private": "Boost with original visibility",
|
||||
|
@ -867,7 +908,9 @@
|
|||
"subscribed_languages.target": "Change subscribed languages for {target}",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"terms_of_service.effective_as_of": "Effective as of {date}",
|
||||
"terms_of_service.title": "Terms of Service",
|
||||
"terms_of_service.upcoming_changes_on": "Upcoming changes on {date}",
|
||||
"time_remaining.days": "{number, plural, one {# day} other {# days}} left",
|
||||
"time_remaining.hours": "{number, plural, one {# hour} other {# hours}} left",
|
||||
"time_remaining.minutes": "{number, plural, one {# minute} other {# minutes}} left",
|
||||
|
@ -898,6 +941,12 @@
|
|||
"video.expand": "Expand video",
|
||||
"video.fullscreen": "Full screen",
|
||||
"video.hide": "Hide video",
|
||||
"video.mute": "Mute",
|
||||
"video.pause": "Pause",
|
||||
"video.play": "Play"
|
||||
"video.play": "Play",
|
||||
"video.skip_backward": "Skip backward",
|
||||
"video.skip_forward": "Skip forward",
|
||||
"video.unmute": "Unmute",
|
||||
"video.volume_down": "Volume down",
|
||||
"video.volume_up": "Volume up"
|
||||
}
|
||||
|
|
|
@ -336,7 +336,6 @@
|
|||
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
||||
"compose_form.poll.type": "Style",
|
||||
"compose_form.publish": "Post",
|
||||
"compose_form.publish_form": "New post",
|
||||
"compose_form.reply": "Reply",
|
||||
"compose_form.save_changes": "Update",
|
||||
"compose_form.searchability_warning": "Self only searchability is not available other mastodon servers. Others can search your post.",
|
||||
|
@ -575,6 +574,7 @@
|
|||
"hints.profiles.see_more_posts": "See more posts on {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Replies from other servers may be missing.",
|
||||
"hints.threads.see_more": "See more replies on {domain}",
|
||||
"home.column_settings.show_quotes": "Show quotes",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"home.hide_announcements": "Hide announcements",
|
||||
|
@ -705,9 +705,11 @@
|
|||
"mute_modal.you_wont_see_mentions": "You won't see posts that mention them.",
|
||||
"mute_modal.you_wont_see_posts": "They can still see your posts, but you won't see theirs.",
|
||||
"navigation_bar.about": "About",
|
||||
"navigation_bar.account_settings": "Password and security",
|
||||
"navigation_bar.administration": "Administration",
|
||||
"navigation_bar.advanced_interface": "Open in advanced web interface",
|
||||
"navigation_bar.antennas": "Antenna",
|
||||
"navigation_bar.automated_deletion": "Automated post deletion",
|
||||
"navigation_bar.blocks": "Blocked users",
|
||||
"navigation_bar.bookmarks": "Bookmarks",
|
||||
"navigation_bar.circles": "Circles",
|
||||
|
@ -717,26 +719,30 @@
|
|||
"navigation_bar.direct": "Private mentions",
|
||||
"navigation_bar.discover": "Discover",
|
||||
"navigation_bar.domain_blocks": "Blocked domains",
|
||||
"navigation_bar.emoji_reactions": "Stamps",
|
||||
"navigation_bar.emoji_reactions": "Emoji reactions",
|
||||
"navigation_bar.explore": "Explore",
|
||||
"navigation_bar.favourites": "Favorites",
|
||||
"navigation_bar.filters": "Muted words",
|
||||
"navigation_bar.follow_requests": "Follow requests",
|
||||
"navigation_bar.followed_tags": "Followed hashtags",
|
||||
"navigation_bar.follows_and_followers": "Follows and followers",
|
||||
"navigation_bar.import_export": "Import and export",
|
||||
"navigation_bar.lists": "Lists",
|
||||
"navigation_bar.logout": "Logout",
|
||||
"navigation_bar.moderation": "Moderation",
|
||||
"navigation_bar.more": "More",
|
||||
"navigation_bar.mutes": "Muted users",
|
||||
"navigation_bar.opened_in_classic_interface": "Posts, accounts, and other specific pages are opened by default in the classic web interface.",
|
||||
"navigation_bar.personal": "Personal",
|
||||
"navigation_bar.pins": "Pinned posts",
|
||||
"navigation_bar.preferences": "Preferences",
|
||||
"navigation_bar.privacy_and_reach": "Privacy and reach",
|
||||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"navigation_bar.reaction_deck": "Reaction deck",
|
||||
"navigation_bar.refresh": "Refresh",
|
||||
"navigation_bar.search": "Search",
|
||||
"navigation_bar.security": "Security",
|
||||
"navigation_panel.collapse_lists": "Collapse list menu",
|
||||
"navigation_panel.expand_lists": "Expand list menu",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to login to access this resource.",
|
||||
"notification.admin.report": "{name} reported {target}",
|
||||
"notification.admin.report_account": "{name} reported {count, plural, one {one post} other {# posts}} from {target} for {category}",
|
||||
|
@ -1121,7 +1127,10 @@
|
|||
"subscribed_languages.save": "Save changes",
|
||||
"subscribed_languages.target": "Change subscribed languages for {target}",
|
||||
"tabs_bar.home": "Home",
|
||||
"tabs_bar.menu": "Menu",
|
||||
"tabs_bar.notifications": "Notifications",
|
||||
"tabs_bar.publish": "New Post",
|
||||
"tabs_bar.search": "Search",
|
||||
"terms_of_service.effective_as_of": "Effective as of {date}",
|
||||
"terms_of_service.title": "Terms of Service",
|
||||
"terms_of_service.upcoming_changes_on": "Upcoming changes on {date}",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Ver más mensajes en {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Es posible que falten respuestas de otros servidores.",
|
||||
"hints.threads.see_more": "Ver más respuestas en {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citas",
|
||||
"home.column_settings.show_reblogs": "Mostrar adhesiones",
|
||||
"home.column_settings.show_replies": "Mostrar respuestas",
|
||||
"home.hide_announcements": "Ocultar anuncios",
|
||||
|
|
|
@ -345,9 +345,9 @@
|
|||
"explore.trending_links": "Noticias",
|
||||
"explore.trending_statuses": "Publicaciones",
|
||||
"explore.trending_tags": "Etiquetas",
|
||||
"featured_carousel.header": "{count, plural,one {Publicación fijada} other {Publicaciones fijada}}",
|
||||
"featured_carousel.header": "{count, plural,one {Publicación fijada} other {Publicaciones fijadas}}",
|
||||
"featured_carousel.next": "Siguiente",
|
||||
"featured_carousel.post": "Publicar",
|
||||
"featured_carousel.post": "Publicación",
|
||||
"featured_carousel.previous": "Anterior",
|
||||
"featured_carousel.slide": "{index} de {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que has accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.",
|
||||
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Ver más publicaciones en {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Puede que no se muestren algunas respuestas de otros servidores.",
|
||||
"hints.threads.see_more": "Ver más respuestas en {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citas",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respuestas",
|
||||
"home.hide_announcements": "Ocultar anuncios",
|
||||
|
|
|
@ -345,9 +345,9 @@
|
|||
"explore.trending_links": "Noticias",
|
||||
"explore.trending_statuses": "Publicaciones",
|
||||
"explore.trending_tags": "Etiquetas",
|
||||
"featured_carousel.header": "{count, plural,one {Publicación fijada} other {Publicaciones fijada}}",
|
||||
"featured_carousel.header": "{count, plural,one {Publicación fijada} other {Publicaciones fijadas}}",
|
||||
"featured_carousel.next": "Siguiente",
|
||||
"featured_carousel.post": "Publicar",
|
||||
"featured_carousel.post": "Publicación",
|
||||
"featured_carousel.previous": "Anterior",
|
||||
"featured_carousel.slide": "{index} de {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Esta categoría de filtro no se aplica al contexto en el que ha accedido a esta publlicación. Si quieres que la publicación sea filtrada también en este contexto, tendrás que editar el filtro.",
|
||||
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Ver más publicaciones en {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Puede que no se muestren algunas respuestas de otros servidores.",
|
||||
"hints.threads.see_more": "Ver más respuestas en {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citas",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respuestas",
|
||||
"home.hide_announcements": "Ocultar comunicaciones",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Modereeritavad serverid",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Vaikimisi",
|
||||
"about.disclaimer": "Mastodon on tasuta ja vaba tarkvara ning Mastodon gGmbH kaubamärk.",
|
||||
"about.domain_blocks.no_reason_available": "Põhjus teadmata",
|
||||
"about.domain_blocks.preamble": "Mastodon lubab tavaliselt vaadata sisu ning suhelda kasutajatega ükskõik millisest teisest fediversumi serverist. Need on erandid, mis on paika pandud sellel kindlal serveril.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Piiratud",
|
||||
"about.domain_blocks.suspended.explanation": "Mitte mingeid andmeid sellelt serverilt ei töödelda, salvestata ega vahetata, tehes igasuguse interaktsiooni või kirjavahetuse selle serveri kasutajatega võimatuks.",
|
||||
"about.domain_blocks.suspended.title": "Peatatud",
|
||||
"about.language_label": "Keel",
|
||||
"about.not_available": "See info ei ole sellel serveril saadavaks tehtud.",
|
||||
"about.powered_by": "Hajutatud sotsiaalmeedia, mille taga on {mastodon}",
|
||||
"about.rules": "Serveri reeglid",
|
||||
|
@ -26,6 +28,12 @@
|
|||
"account.edit_profile": "Muuda profiili",
|
||||
"account.enable_notifications": "Teavita mind @{name} postitustest",
|
||||
"account.endorse": "Too profiilil esile",
|
||||
"account.familiar_followers_many": "Jälgijateks {name1}, {name2} ja veel {othersCount, plural, one {üks kasutaja, keda tead} other {# kasutajat, keda tead}}",
|
||||
"account.familiar_followers_one": "Jälgijaks {name1}",
|
||||
"account.familiar_followers_two": "Jälgijateks {name1} ja {name2}",
|
||||
"account.featured": "Esiletõstetud",
|
||||
"account.featured.accounts": "Profiilid",
|
||||
"account.featured.hashtags": "Sildid",
|
||||
"account.featured_tags.last_status_at": "Viimane postitus {date}",
|
||||
"account.featured_tags.last_status_never": "Postitusi pole",
|
||||
"account.follow": "Jälgi",
|
||||
|
@ -36,6 +44,7 @@
|
|||
"account.following": "Jälgib",
|
||||
"account.following_counter": "{count, plural, one {{counter} jälgib} other {{counter} jälgib}}",
|
||||
"account.follows.empty": "See kasutaja ei jälgi veel kedagi.",
|
||||
"account.follows_you": "Jälgib sind",
|
||||
"account.go_to_profile": "Mine profiilile",
|
||||
"account.hide_reblogs": "Peida @{name} jagamised",
|
||||
"account.in_memoriam": "In Memoriam.",
|
||||
|
@ -50,18 +59,22 @@
|
|||
"account.mute_notifications_short": "Vaigista teavitused",
|
||||
"account.mute_short": "Vaigista",
|
||||
"account.muted": "Vaigistatud",
|
||||
"account.mutual": "Te jälgite teineteist",
|
||||
"account.no_bio": "Kirjeldust pole lisatud.",
|
||||
"account.open_original_page": "Ava algne leht",
|
||||
"account.posts": "Postitused",
|
||||
"account.posts_with_replies": "Postitused ja vastused",
|
||||
"account.remove_from_followers": "Eemalda {name} jälgijate seast",
|
||||
"account.report": "Raporteeri @{name}",
|
||||
"account.requested": "Ootab kinnitust. Klõpsa jälgimise soovi tühistamiseks",
|
||||
"account.requested_follow": "{name} on taodelnud sinu jälgimist",
|
||||
"account.requests_to_follow_you": "soovib sind jälgida",
|
||||
"account.share": "Jaga @{name} profiili",
|
||||
"account.show_reblogs": "Näita @{name} jagamisi",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} postitus} other {{counter} postitust}}",
|
||||
"account.unblock": "Eemalda blokeering @{name}",
|
||||
"account.unblock_domain": "Tee {domain} nähtavaks",
|
||||
"account.unblock_domain_short": "Lõpeta blokeerimine",
|
||||
"account.unblock_short": "Eemalda blokeering",
|
||||
"account.unendorse": "Ära kuva profiilil",
|
||||
"account.unfollow": "Jälgid",
|
||||
|
@ -223,6 +236,9 @@
|
|||
"confirmations.redraft.confirm": "Kustuta & taasalusta",
|
||||
"confirmations.redraft.message": "Kindel, et soovid postituse kustutada ja võtta uue aluseks? Lemmikuks märkimised ja jagamised lähevad kaotsi ning vastused jäävad ilma algse postituseta.",
|
||||
"confirmations.redraft.title": "Kustudada ja luua postituse mustand?",
|
||||
"confirmations.remove_from_followers.confirm": "Eemalda jälgija",
|
||||
"confirmations.remove_from_followers.message": "{name} lõpetab sellega sinu jälgimise. Kas oled kindel, et soovid jätkata?",
|
||||
"confirmations.remove_from_followers.title": "Kas eemaldame jälgija?",
|
||||
"confirmations.reply.confirm": "Vasta",
|
||||
"confirmations.reply.message": "Praegu vastamine kirjutab hetkel koostatava sõnumi üle. Oled kindel, et soovid jätkata?",
|
||||
"confirmations.reply.title": "Kirjutada postitus üle?",
|
||||
|
@ -290,6 +306,9 @@
|
|||
"emoji_button.search_results": "Otsitulemused",
|
||||
"emoji_button.symbols": "Sümbolid",
|
||||
"emoji_button.travel": "Reisimine & kohad",
|
||||
"empty_column.account_featured.me": "Sa pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid silte või või sõbra kasutajakontot?",
|
||||
"empty_column.account_featured.other": "{acct} pole veel midagi esile tõstnud. Kas sa teadsid, et oma profiilis saad esile tõsta enamkasutatavaid silte või või sõbra kasutajakontot?",
|
||||
"empty_column.account_featured_other.unknown": "See kasutajakonto pole veel midagi esile tõstnud.",
|
||||
"empty_column.account_hides_collections": "See kasutaja otsustas mitte teha seda infot saadavaks",
|
||||
"empty_column.account_suspended": "Konto kustutatud",
|
||||
"empty_column.account_timeline": "Siin postitusi ei ole!",
|
||||
|
@ -322,6 +341,10 @@
|
|||
"explore.trending_links": "Uudised",
|
||||
"explore.trending_statuses": "Postitused",
|
||||
"explore.trending_tags": "Sildid",
|
||||
"featured_carousel.header": "{count, plural, one {Esiletõstetud postitus} other {Esiletõstetud postitust}}",
|
||||
"featured_carousel.next": "Järgmine",
|
||||
"featured_carousel.previous": "Eelmine",
|
||||
"featured_carousel.slide": "{index} / {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "See filtrikategooria ei rakendu kontekstis, kuidas postituseni jõudsid. Kui tahad postitust ka selles kontekstis filtreerida, pead muutma filtrit.",
|
||||
"filter_modal.added.context_mismatch_title": "Konteksti mittesobivus!",
|
||||
"filter_modal.added.expired_explanation": "Selle filtri kategooria on aegunud. pead muutma aegumiskuupäeva, kui tahad, et filter kehtiks.",
|
||||
|
@ -374,6 +397,8 @@
|
|||
"generic.saved": "Salvestatud",
|
||||
"getting_started.heading": "Alustamine",
|
||||
"hashtag.admin_moderation": "Ava modereerimisliides #{name} jaoks",
|
||||
"hashtag.browse": "Sirvi #{hashtag} sildiga postitusi",
|
||||
"hashtag.browse_from_account": "Sirvi @{name} kasutaja #{hashtag} sildiga postitusi",
|
||||
"hashtag.column_header.tag_mode.all": "ja {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "või {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "ilma {additional}",
|
||||
|
@ -386,7 +411,10 @@
|
|||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} osalejaga} other {{counter} osalejaga}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} postitusega} other {{counter} postitusega}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} postitust} other {{counter} postitust}} täna",
|
||||
"hashtag.feature": "Tõsta profiilis esile",
|
||||
"hashtag.follow": "Jälgi silti",
|
||||
"hashtag.mute": "Vaigista @#{hashtag}",
|
||||
"hashtag.unfeature": "Ära tõsta profiilis esile",
|
||||
"hashtag.unfollow": "Lõpeta sildi jälgimine",
|
||||
"hashtags.and_other": "…ja {count, plural, one {}other {# veel}}",
|
||||
"hints.profiles.followers_may_be_missing": "Selle profiili jälgijaid võib olla puudu.",
|
||||
|
@ -397,6 +425,7 @@
|
|||
"hints.profiles.see_more_posts": "Vaata rohkem postitusi kohas {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Vastuseid teistest serveritest võib olla puudu.",
|
||||
"hints.threads.see_more": "Vaata rohkem vastuseid kohas {domain}",
|
||||
"home.column_settings.show_quotes": "Näita tsiteeritut",
|
||||
"home.column_settings.show_reblogs": "Näita jagamisi",
|
||||
"home.column_settings.show_replies": "Näita vastuseid",
|
||||
"home.hide_announcements": "Peida teadaanded",
|
||||
|
@ -705,6 +734,8 @@
|
|||
"privacy_policy.title": "Isikuandmete kaitse",
|
||||
"recommended": "Soovitatud",
|
||||
"refresh": "Värskenda",
|
||||
"regeneration_indicator.please_stand_by": "Palun oota.",
|
||||
"regeneration_indicator.preparing_your_home_feed": "Valmistan ette sinu avalehe lõime…",
|
||||
"relative_time.days": "{number}p",
|
||||
"relative_time.full.days": "{number, plural, one {# päev} other {# päeva}} tagasi",
|
||||
"relative_time.full.hours": "{number, plural, one {# tund} other {# tundi}} tagasi",
|
||||
|
@ -758,7 +789,7 @@
|
|||
"report.thanks.title": "Ei taha seda näha?",
|
||||
"report.thanks.title_actionable": "Täname teavitamise eest, uurime seda.",
|
||||
"report.unfollow": "Lõpeta @{name} jälgimine",
|
||||
"report.unfollow_explanation": "Jälgid seda kontot. Et mitte näha tema postitusi oma koduvoos, lõpeta ta jälgimine.",
|
||||
"report.unfollow_explanation": "Jälgid seda kontot. Et mitte näha tema postitusi oma avalehe lõimes, lõpeta ta jälgimine.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {{count} postitus} other {{count} postitust}} listatud",
|
||||
"report_notification.categories.legal": "Õiguslik",
|
||||
"report_notification.categories.legal_sentence": "ebaseaduslik sisu",
|
||||
|
@ -788,8 +819,11 @@
|
|||
"search_results.accounts": "Profiilid",
|
||||
"search_results.all": "Kõik",
|
||||
"search_results.hashtags": "Sildid",
|
||||
"search_results.no_results": "Tulemusi pole.",
|
||||
"search_results.no_search_yet": "Proovi otsida postitusi, profiile või silte.",
|
||||
"search_results.see_all": "Vaata kõiki",
|
||||
"search_results.statuses": "Postitused",
|
||||
"search_results.title": "Otsi märksõna: {q}",
|
||||
"server_banner.about_active_users": "Inimesed, kes kasutavad seda serverit viimase 30 päeva jooksul (kuu aktiivsed kasutajad)",
|
||||
"server_banner.active_users": "aktiivsed kasutajad",
|
||||
"server_banner.administered_by": "Administraator:",
|
||||
|
@ -832,6 +866,13 @@
|
|||
"status.mute_conversation": "Vaigista vestlus",
|
||||
"status.open": "Laienda postitus",
|
||||
"status.pin": "Kinnita profiilile",
|
||||
"status.quote_error.filtered": "Peidetud mõne kasutatud filtri tõttu",
|
||||
"status.quote_error.not_found": "Seda postitust ei saa näidata.",
|
||||
"status.quote_error.pending_approval": "See postitus on algse autori kinnituse ootel.",
|
||||
"status.quote_error.rejected": "Seda postitust ei saa näidata, kuina algne autor ei luba teda tsiteerida.",
|
||||
"status.quote_error.removed": "Autor kustutas selle postituse.",
|
||||
"status.quote_error.unauthorized": "Kuna sul pole luba selle postituse nägemiseks, siis seda ei saa kuvada.",
|
||||
"status.quote_post_author": "Postitajaks {name}",
|
||||
"status.read_more": "Loe veel",
|
||||
"status.reblog": "Jaga",
|
||||
"status.reblog_private": "Jaga algse nähtavusega",
|
||||
|
@ -840,6 +881,7 @@
|
|||
"status.reblogs.empty": "Keegi pole seda postitust veel jaganud. Kui keegi seda teeb, näeb seda siin.",
|
||||
"status.redraft": "Kustuta & alga uuesti",
|
||||
"status.remove_bookmark": "Eemalda järjehoidja",
|
||||
"status.remove_favourite": "Eemalda lemmikute seast",
|
||||
"status.replied_in_thread": "Vastatud lõimes",
|
||||
"status.replied_to": "Vastas kasutajale {name}",
|
||||
"status.reply": "Vasta",
|
||||
|
@ -861,7 +903,9 @@
|
|||
"subscribed_languages.target": "Muuda tellitud keeli {target} jaoks",
|
||||
"tabs_bar.home": "Kodu",
|
||||
"tabs_bar.notifications": "Teated",
|
||||
"terms_of_service.effective_as_of": "Kehtib alates {date}",
|
||||
"terms_of_service.title": "Teenuse tingimused",
|
||||
"terms_of_service.upcoming_changes_on": "Muudatused alates {date}",
|
||||
"time_remaining.days": "{number, plural, one {# päev} other {# päeva}} jäänud",
|
||||
"time_remaining.hours": "{number, plural, one {# tund} other {# tundi}} jäänud",
|
||||
"time_remaining.minutes": "{number, plural, one {# minut} other {# minutit}} jäänud",
|
||||
|
@ -892,6 +936,12 @@
|
|||
"video.expand": "Suurenda video",
|
||||
"video.fullscreen": "Täisekraan",
|
||||
"video.hide": "Peida video",
|
||||
"video.mute": "Vaigista",
|
||||
"video.pause": "Paus",
|
||||
"video.play": "Mängi"
|
||||
"video.play": "Mängi",
|
||||
"video.skip_backward": "Keri tagasi",
|
||||
"video.skip_forward": "Keri edasi",
|
||||
"video.unmute": "Lõpeta vaigistamine",
|
||||
"video.volume_down": "Heli vaiksemaks",
|
||||
"video.volume_up": "Heli valjemaks"
|
||||
}
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Näytä lisää julkaisuja palvelimella {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Muiden palvelinten vastauksia saattaa puuttua.",
|
||||
"hints.threads.see_more": "Näytä lisää vastauksia palvelimella {domain}",
|
||||
"home.column_settings.show_quotes": "Näytä lainaukset",
|
||||
"home.column_settings.show_reblogs": "Näytä tehostukset",
|
||||
"home.column_settings.show_replies": "Näytä vastaukset",
|
||||
"home.hide_announcements": "Piilota tiedotteet",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Sí fleiri postar á {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Svar frá øðrum ambætarum mangla møguliga.",
|
||||
"hints.threads.see_more": "Sí fleiri svar á {domain}",
|
||||
"home.column_settings.show_quotes": "Vís siteringar",
|
||||
"home.column_settings.show_reblogs": "Vís lyft",
|
||||
"home.column_settings.show_replies": "Vís svar",
|
||||
"home.hide_announcements": "Fjal kunngerðir",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Freastalaithe faoi stiúir",
|
||||
"about.contact": "Teagmháil:",
|
||||
"about.default_locale": "Réamhshocrú",
|
||||
"about.disclaimer": "Bogearra foinse oscailte saor in aisce is ea Mastodon, agus is le Mastodon gGmbH an trádmharc.",
|
||||
"about.domain_blocks.no_reason_available": "Níl an fáth ar fáil",
|
||||
"about.domain_blocks.preamble": "Go hiondúil, tugann Mastadán cead duit a bheith ag plé le húsáideoirí as freastalaí ar bith eile sa chomhchruinne agus a gcuid inneachair a fheiceáil. Seo iad na heisceachtaí a rinneadh ar an bhfreastalaí áirithe seo.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Teoranta",
|
||||
"about.domain_blocks.suspended.explanation": "Ní dhéanfar aon sonra ón fhreastalaí seo a phróiseáil, a stóráil ná a mhalartú, rud a fhágann nach féidir aon teagmháil ná aon chumarsáid a dhéanamh le húsáideoirí ón fhreastalaí seo.",
|
||||
"about.domain_blocks.suspended.title": "Ar fionraí",
|
||||
"about.language_label": "Teanga",
|
||||
"about.not_available": "Níor cuireadh an t-eolas seo ar fáil ar an bhfreastalaí seo.",
|
||||
"about.powered_by": "Meáin shóisialta díláraithe faoi chumhacht {mastodon}",
|
||||
"about.rules": "Rialacha an fhreastalaí",
|
||||
|
@ -308,6 +310,8 @@
|
|||
"emoji_button.search_results": "Torthaí cuardaigh",
|
||||
"emoji_button.symbols": "Comharthaí",
|
||||
"emoji_button.travel": "Taisteal ⁊ Áiteanna",
|
||||
"empty_column.account_featured.me": "Níl aon rud curtha i láthair agat go fóill. An raibh a fhios agat gur féidir leat na haischlibeanna is mó a úsáideann tú, agus fiú cuntais do chairde, a chur i láthair ar do phróifíl?",
|
||||
"empty_column.account_featured.other": "Níl aon rud feicthe ag {acct} go fóill. An raibh a fhios agat gur féidir leat na hashtags is mó a úsáideann tú, agus fiú cuntais do chairde, a chur ar do phróifíl?",
|
||||
"empty_column.account_featured_other.unknown": "Níl aon rud le feiceáil sa chuntas seo go fóill.",
|
||||
"empty_column.account_hides_collections": "Roghnaigh an t-úsáideoir seo gan an fhaisnéis seo a chur ar fáil",
|
||||
"empty_column.account_suspended": "Cuntas ar fionraí",
|
||||
|
@ -341,6 +345,11 @@
|
|||
"explore.trending_links": "Nuacht",
|
||||
"explore.trending_statuses": "Postálacha",
|
||||
"explore.trending_tags": "Haischlibeanna",
|
||||
"featured_carousel.header": "{count, plural, one {Postáil phinnáilte} two {Poist Phionáilte} few {Poist Phionáilte} many {Poist Phionáilte} other {Poist Phionáilte}}",
|
||||
"featured_carousel.next": "Ar Aghaidh",
|
||||
"featured_carousel.post": "Post",
|
||||
"featured_carousel.previous": "Roimhe Seo",
|
||||
"featured_carousel.slide": "{index} de {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Ní bhaineann an chatagóir scagaire seo leis an gcomhthéacs ina bhfuair tú rochtain ar an bpostáil seo. Más mian leat an postáil a scagadh sa chomhthéacs seo freisin, beidh ort an scagaire a chur in eagar.",
|
||||
"filter_modal.added.context_mismatch_title": "Neamhréir comhthéacs!",
|
||||
"filter_modal.added.expired_explanation": "Tá an chatagóir scagaire seo imithe in éag, beidh ort an dáta éaga a athrú chun é a chur i bhfeidhm.",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Frithealaichean fo mhaorsainneachd",
|
||||
"about.contact": "Fios thugainn:",
|
||||
"about.default_locale": "Bun-roghainn",
|
||||
"about.disclaimer": "’S e bathar-bog saor le bun-tùs fosgailte a th’ ann am Mastodon agus ’na chomharra-mhalairt aig Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Chan eil an t-adhbhar ga thoirt seachad",
|
||||
"about.domain_blocks.preamble": "San fharsaingeachd, leigidh Mastodon leat susbaint o fhrithealaiche sam bith sa cho-shaoghal a shealltainn agus eadar-ghìomh a ghabhail leis na cleachdaichean uapa-san. Seo na h-easgaidhean a tha an sàs air an fhrithealaiche shònraichte seo.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Cuingichte",
|
||||
"about.domain_blocks.suspended.explanation": "Cha dèid dàta sam bith on fhrithealaiche seo a phròiseasadh, a stòradh no iomlaid agus chan urrainn do na cleachdaichean on fhrithealaiche sin conaltradh no eadar-ghnìomh a ghabhail an-seo.",
|
||||
"about.domain_blocks.suspended.title": "À rèim",
|
||||
"about.language_label": "Cànan",
|
||||
"about.not_available": "Cha deach am fiosrachadh seo a sholar air an fhrithealaiche seo.",
|
||||
"about.powered_by": "Lìonra sòisealta sgaoilte le cumhachd {mastodon}",
|
||||
"about.rules": "Riaghailtean an fhrithealaiche",
|
||||
|
@ -28,6 +30,9 @@
|
|||
"account.edit_profile": "Deasaich a’ phròifil",
|
||||
"account.enable_notifications": "Cuir brath thugam nuair a chuireas @{name} post ris",
|
||||
"account.endorse": "Brosnaich air a’ phròifil",
|
||||
"account.familiar_followers_many": "’Ga leantainn le {name1}, {name2}, and {othersCount, plural, one {# eile air a bheil thu eòlach} other {# eile air a bheil thu eòlach}}",
|
||||
"account.familiar_followers_one": "’Ga leantainn le {name1}",
|
||||
"account.familiar_followers_two": "’Ga leantainn le {name1} ’s {name2}",
|
||||
"account.featured": "’Ga bhrosnachadh",
|
||||
"account.featured.accounts": "Pròifilean",
|
||||
"account.featured.hashtags": "Tagaichean hais",
|
||||
|
@ -38,6 +43,7 @@
|
|||
"account.followers": "Luchd-leantainn",
|
||||
"account.followers.empty": "Chan eil neach sam bith a’ leantainn air a’ chleachdaiche seo fhathast.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} neach-leantainn} other {{counter} luchd-leantainn}}",
|
||||
"account.followers_you_know_counter": "{counter} air a bheil thu eòlach",
|
||||
"account.following": "A’ leantainn",
|
||||
"account.following_counter": "{count, plural, one {A’ leantainn {counter}} other {A’ leantainn {counter}}}",
|
||||
"account.follows.empty": "Chan eil an cleachdaiche seo a’ leantainn neach sam bith fhathast.",
|
||||
|
@ -304,6 +310,8 @@
|
|||
"emoji_button.search_results": "Toraidhean an luirg",
|
||||
"emoji_button.symbols": "Samhlaidhean",
|
||||
"emoji_button.travel": "Siubhal ⁊ àitichean",
|
||||
"empty_column.account_featured.me": "Chan eil thu a’ brosnachadh dad fhathast. An robh fios agad gur urrainn dhut na tagaichean hais a chleachdas tu as trice agus fiù ’s cunntasan do charaidean a bhrosnachadh air a’ phròifil agad?",
|
||||
"empty_column.account_featured.other": "Chan eil {acct} a’ brosnachadh dad fhathast. An robh fios agad gur urrainn dhut na tagaichean hais a chleachdas tu as trice agus fiù ’s cunntasan do charaidean a bhrosnachadh air a’ phròifil agad?",
|
||||
"empty_column.account_featured_other.unknown": "Chan eil an cunntas seo a’ brosnachadh dad fhathast.",
|
||||
"empty_column.account_hides_collections": "Chuir an cleachdaiche seo roimhe nach eil am fiosrachadh seo ri fhaighinn",
|
||||
"empty_column.account_suspended": "Chaidh an cunntas a chur à rèim",
|
||||
|
@ -337,6 +345,11 @@
|
|||
"explore.trending_links": "Naidheachdan",
|
||||
"explore.trending_statuses": "Postaichean",
|
||||
"explore.trending_tags": "Tagaichean hais",
|
||||
"featured_carousel.header": "{count, plural, one {Post prìnichte} two {Postaichean prìnichte} few {Postaichean prìnichte} other {Postaichean prìnichte}}",
|
||||
"featured_carousel.next": "Air adhart",
|
||||
"featured_carousel.post": "Post",
|
||||
"featured_carousel.previous": "Air ais",
|
||||
"featured_carousel.slide": "{index} à {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Chan eil an roinn-seòrsa criathraidh iom seo chaidh dhan cho-theacs san do dh’inntrig thu am post seo. Ma tha thu airson am post a chriathradh sa cho-theacs seo cuideachd, feumaidh tu a’ chriathrag a dheasachadh.",
|
||||
"filter_modal.added.context_mismatch_title": "Co-theacsa neo-iomchaidh!",
|
||||
"filter_modal.added.expired_explanation": "Dh’fhalbh an ùine air an roinn-seòrsa criathraidh seo agus feumaidh tu an ceann-là crìochnachaidh atharrachadh mus cuir thu an sàs i.",
|
||||
|
@ -856,6 +869,13 @@
|
|||
"status.mute_conversation": "Mùch an còmhradh",
|
||||
"status.open": "Leudaich am post seo",
|
||||
"status.pin": "Prìnich ris a’ phròifil",
|
||||
"status.quote_error.filtered": "Falaichte le criathrag a th’ agad",
|
||||
"status.quote_error.not_found": "Chan urrainn dhuinn am post seo a shealltainn.",
|
||||
"status.quote_error.pending_approval": "Tha am post seo a’ feitheamh air aontachadh leis an ùghdar tùsail.",
|
||||
"status.quote_error.rejected": "Chan urrainn dhuinn am post seo a shealltainn air sgàth ’s nach ceadaich an t-ùghdar tùsail aige gun dèid a luaidh.",
|
||||
"status.quote_error.removed": "Chaidh am post seo a thoirt air falbh le ùghdar.",
|
||||
"status.quote_error.unauthorized": "Chan urrainn dhuinn am post seo a shealltainn air sgàth ’s nach eil cead agad fhaicinn.",
|
||||
"status.quote_post_author": "Post le {name}",
|
||||
"status.read_more": "Leugh an còrr",
|
||||
"status.reblog": "Brosnaich",
|
||||
"status.reblog_private": "Brosnaich leis an t-so-fhaicsinneachd tùsail",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Servidores suxeitos a moderación",
|
||||
"about.contact": "Contacto:",
|
||||
"about.default_locale": "Por defecto",
|
||||
"about.disclaimer": "Mastodon é software libre, de código aberto, e unha marca comercial de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo non indicado",
|
||||
"about.domain_blocks.preamble": "Mastodon de xeito xeral permíteche ver contidos doutros servidores do fediverso e interactuar coas súas usuarias. Estas son as excepcións que se estabeleceron neste servidor en particular.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Limitado",
|
||||
"about.domain_blocks.suspended.explanation": "Non se procesarán, almacenarán nin intercambiarán datos con este servidor, o que fai imposible calquera interacción ou comunicación coas usuarias deste servidor.",
|
||||
"about.domain_blocks.suspended.title": "Suspendido",
|
||||
"about.language_label": "Idioma",
|
||||
"about.not_available": "Esta información non está dispoñible neste servidor.",
|
||||
"about.powered_by": "Comunicación social descentralizada grazas a {mastodon}",
|
||||
"about.rules": "Regras do servidor",
|
||||
|
@ -428,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Mira máis publicacións en {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Poderían faltar respostas desde outros servidores.",
|
||||
"hints.threads.see_more": "Mira máis respostas en {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citas",
|
||||
"home.column_settings.show_reblogs": "Amosar compartidos",
|
||||
"home.column_settings.show_replies": "Amosar respostas",
|
||||
"home.hide_announcements": "Agochar anuncios",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "שרתים תחת פיקוח תוכן",
|
||||
"about.contact": "יצירת קשר:",
|
||||
"about.default_locale": "ברירת המחדל",
|
||||
"about.disclaimer": "מסטודון היא תוכנת קוד פתוח חינמית וסימן מסחרי של Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "הסיבה אינה זמינה",
|
||||
"about.domain_blocks.preamble": "ככלל מסטודון מאפשרת לך לצפות בתוכן ולתקשר עם משתמשים מכל שרת בפדיברס. אלו הם היוצאים מן הכלל שהוגדרו עבור השרת המסוים הזה.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "מוגבלים",
|
||||
"about.domain_blocks.suspended.explanation": "שום מידע משרת זה לא יעובד, יישמר או יוחלף, מה שהופך כל תקשורת עם משתמשים משרת זה לבלתי אפשרית.",
|
||||
"about.domain_blocks.suspended.title": "מושעים",
|
||||
"about.language_label": "שפה",
|
||||
"about.not_available": "המידע אינו זמין על שרת זה.",
|
||||
"about.powered_by": "רשת חברתית מבוזרת המופעלת על ידי {mastodon}",
|
||||
"about.rules": "כללי השרת",
|
||||
|
@ -308,6 +310,8 @@
|
|||
"emoji_button.search_results": "תוצאות חיפוש",
|
||||
"emoji_button.symbols": "סמלים",
|
||||
"emoji_button.travel": "טיולים ואתרים",
|
||||
"empty_column.account_featured.me": "עוד לא קידמת תכנים. הידעת שניתן לקדם תגיות שבשימושך התדיר או אפילו את החשבונות של חבריםות בפרופיל שלך?",
|
||||
"empty_column.account_featured.other": "{acct} עוד לא קידם תכנים. הידעת שניתן לקדם תגיות שבשימושך התדיר או אפילו את החשבונות של חבריםות בפרופיל שלך?",
|
||||
"empty_column.account_featured_other.unknown": "חשבון זה עוד לא קידם תכנים.",
|
||||
"empty_column.account_hides_collections": "המשתמש.ת בחר.ה להסתיר מידע זה",
|
||||
"empty_column.account_suspended": "חשבון מושעה",
|
||||
|
@ -341,6 +345,11 @@
|
|||
"explore.trending_links": "חדשות",
|
||||
"explore.trending_statuses": "הודעות",
|
||||
"explore.trending_tags": "תגיות",
|
||||
"featured_carousel.header": "{count, plural, one {הודעה אחת נעוצה} two {הודעותיים נעוצות} many {הודעות נעוצות} other {הודעות נעוצות}}",
|
||||
"featured_carousel.next": "הבא",
|
||||
"featured_carousel.post": "הודעה",
|
||||
"featured_carousel.previous": "הקודם",
|
||||
"featured_carousel.slide": "{index} מתוך {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "קטגוריית המסנן הזאת לא חלה על ההקשר שממנו הגעת אל ההודעה הזו. אם תרצה/י שההודעה תסונן גם בהקשר זה, תצטרך/י לערוך את הסנן.",
|
||||
"filter_modal.added.context_mismatch_title": "אין התאמה להקשר!",
|
||||
"filter_modal.added.expired_explanation": "פג תוקפה של קטגוריית הסינון הזו, יש צורך לשנות את תאריך התפוגה כדי שהסינון יוחל.",
|
||||
|
@ -421,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "צפיה בעוד פרסומים בשרת {domain}",
|
||||
"hints.threads.replies_may_be_missing": "תגובות משרתים אחרים עלולות להיות חסרות.",
|
||||
"hints.threads.see_more": "צפיה בעוד תגובות משרת {domain}",
|
||||
"home.column_settings.show_quotes": "הצגת ציטוטים",
|
||||
"home.column_settings.show_reblogs": "הצגת הדהודים",
|
||||
"home.column_settings.show_replies": "הצגת תגובות",
|
||||
"home.hide_announcements": "הסתר הכרזות",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Moderált kiszolgálók",
|
||||
"about.contact": "Kapcsolat:",
|
||||
"about.default_locale": "Alapértelmezett",
|
||||
"about.disclaimer": "A Mastodon ingyenes, nyílt forráskódú szoftver, a Mastodon gGmbH védjegye.",
|
||||
"about.domain_blocks.no_reason_available": "Nem áll rendelkezésre indoklás",
|
||||
"about.domain_blocks.preamble": "A Mastodon általában mindenféle tartalomcserét és interakciót lehetővé tesz bármelyik másik kiszolgálóval a födiverzumban. Ezek azok a kivételek, amelyek a mi kiszolgálónkon érvényben vannak.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Korlátozott",
|
||||
"about.domain_blocks.suspended.explanation": "A kiszolgáló adatai nem lesznek feldolgozva, tárolva vagy megosztva, lehetetlenné téve mindennemű interakciót és kommunikációt a kiszolgáló felhasználóival.",
|
||||
"about.domain_blocks.suspended.title": "Felfüggesztett",
|
||||
"about.language_label": "Nyelv",
|
||||
"about.not_available": "Ez az információ nem lett közzétéve ezen a kiszolgálón.",
|
||||
"about.powered_by": "Decentralizált közösségi média a {mastodon} segítségével",
|
||||
"about.rules": "Kiszolgáló szabályai",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Server moderati",
|
||||
"about.contact": "Contatti:",
|
||||
"about.default_locale": "Predefinito",
|
||||
"about.disclaimer": "Mastodon è un software libero e open-source e un marchio di Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo non disponibile",
|
||||
"about.domain_blocks.preamble": "Mastodon, generalmente, ti consente di visualizzare i contenuti e interagire con gli utenti da qualsiasi altro server nel fediverso. Queste sono le eccezioni che sono state fatte su questo particolare server.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Limitato",
|
||||
"about.domain_blocks.suspended.explanation": "Nessun dato proveniente da questo server verrà elaborato, conservato o scambiato, rendendo impossibile qualsiasi interazione o comunicazione con gli utenti da questo server.",
|
||||
"about.domain_blocks.suspended.title": "Sospeso",
|
||||
"about.language_label": "Lingua",
|
||||
"about.not_available": "Queste informazioni non sono state rese disponibili su questo server.",
|
||||
"about.powered_by": "Social media decentralizzato alimentato da {mastodon}",
|
||||
"about.rules": "Regole del server",
|
||||
|
@ -308,6 +310,8 @@
|
|||
"emoji_button.search_results": "Risultati della ricerca",
|
||||
"emoji_button.symbols": "Simboli",
|
||||
"emoji_button.travel": "Viaggi & Luoghi",
|
||||
"empty_column.account_featured.me": "Non hai ancora messo in evidenza nulla. Sapevi che puoi mettere in evidenza gli hashtag che usi più spesso e persino gli account dei tuoi amici sul tuo profilo?",
|
||||
"empty_column.account_featured.other": "{acct} non ha ancora messo in evidenza nulla. Sapevi che puoi mettere in evidenza gli hashtag che usi più spesso e persino gli account dei tuoi amici sul tuo profilo?",
|
||||
"empty_column.account_featured_other.unknown": "Questo account non ha ancora pubblicato nulla.",
|
||||
"empty_column.account_hides_collections": "Questo utente ha scelto di non rendere disponibili queste informazioni",
|
||||
"empty_column.account_suspended": "Profilo sospeso",
|
||||
|
@ -341,6 +345,11 @@
|
|||
"explore.trending_links": "Notizie",
|
||||
"explore.trending_statuses": "Post",
|
||||
"explore.trending_tags": "Hashtag",
|
||||
"featured_carousel.header": "{count, plural, one {Post appuntato} other {Post appuntati}}",
|
||||
"featured_carousel.next": "Successivo",
|
||||
"featured_carousel.post": "Post",
|
||||
"featured_carousel.previous": "Precedente",
|
||||
"featured_carousel.slide": "{index} di {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "La categoria di questo filtro non si applica al contesto in cui hai acceduto a questo post. Se desideri che il post sia filtrato anche in questo contesto, dovrai modificare il filtro.",
|
||||
"filter_modal.added.context_mismatch_title": "Contesto non corrispondente!",
|
||||
"filter_modal.added.expired_explanation": "La categoria di questo filtro è scaduta, dovrvai modificarne la data di scadenza per applicarlo.",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"about.blocks": "制限中のサーバー",
|
||||
"about.contact": "連絡先",
|
||||
"about.disabled": "無効",
|
||||
"about.default_locale": "デフォルト",
|
||||
"about.disclaimer": "Mastodonは自由なオープンソースソフトウェアであり、Mastodon gGmbHの商標です。",
|
||||
"about.domain_blocks.no_reason_available": "理由未記載",
|
||||
"about.domain_blocks.preamble": "Mastodonでは原則的にあらゆるサーバー同士で交流したり、互いの投稿を読んだりできますが、当サーバーでは例外的に次のような制限を設けています。",
|
||||
|
@ -15,6 +16,7 @@
|
|||
"about.full_text_search": "全文検索",
|
||||
"about.kmyblue_capabilities": "このサーバーで利用可能な機能",
|
||||
"about.kmyblue_capability": "このサーバーは、kmyblueというMastodonフォークを利用しています。kmyblue独自機能の一部は、サーバー管理者によって有効・無効を切り替えることができます。",
|
||||
"about.language_label": "言語",
|
||||
"about.not_available": "この情報はこのサーバーでは利用できません。",
|
||||
"about.powered_by": "{mastodon}による分散型ソーシャルメディア",
|
||||
"about.public_visibility": "公開投稿を許可",
|
||||
|
@ -39,7 +41,11 @@
|
|||
"account.edit_profile": "プロフィール編集",
|
||||
"account.enable_notifications": "@{name}さんの投稿時に通知",
|
||||
"account.endorse": "プロフィールで紹介する",
|
||||
"account.familiar_followers_many": "{name1}、{name2}、他{othersCount, plural, one {one other you know} other {# others you know}}人のユーザーにフォローされています",
|
||||
"account.familiar_followers_one": "{name1} さんがフォローしています",
|
||||
"account.familiar_followers_two": "{name1} さんと {name2} さんもフォローしています",
|
||||
"account.featured": "注目",
|
||||
"account.featured.accounts": "プロフィール",
|
||||
"account.featured.hashtags": "ハッシュタグ",
|
||||
"account.featured_tags.last_status_at": "最終投稿 {date}",
|
||||
"account.featured_tags.last_status_never": "投稿がありません",
|
||||
|
@ -48,6 +54,7 @@
|
|||
"account.followers": "フォロワー",
|
||||
"account.followers.empty": "まだ誰もフォローしていません。",
|
||||
"account.followers_counter": "{count, plural, other {{counter} フォロワー}}",
|
||||
"account.followers_you_know_counter": "あなたと知り合いの{counter}人",
|
||||
"account.following": "フォロー中",
|
||||
"account.following_counter": "{count, plural, other {{counter} フォロー}}",
|
||||
"account.follows.empty": "まだ誰もフォローしていません。",
|
||||
|
@ -422,6 +429,8 @@
|
|||
"emoji_button.search_results": "検索結果",
|
||||
"emoji_button.symbols": "記号",
|
||||
"emoji_button.travel": "旅行と場所",
|
||||
"empty_column.account_featured.me": "まだ何もフィーチャーしていません。最もよく使うハッシュタグや、更には友達のアカウントまでプロフィール上でフィーチャーできると知っていましたか?",
|
||||
"empty_column.account_featured.other": "{acct}ではまだ何もフィーチャーされていません。最もよく使うハッシュタグや、更には友達のアカウントまでプロフィール上でフィーチャーできると知っていましたか?",
|
||||
"empty_column.account_featured_other.unknown": "このアカウントにはまだ何も投稿されていません。",
|
||||
"empty_column.account_hides_collections": "このユーザーはこの情報を開示しないことにしています。",
|
||||
"empty_column.account_suspended": "アカウントは停止されています",
|
||||
|
@ -462,6 +471,10 @@
|
|||
"explore.trending_links": "ニュース",
|
||||
"explore.trending_statuses": "投稿",
|
||||
"explore.trending_tags": "ハッシュタグ",
|
||||
"featured_carousel.next": "次へ",
|
||||
"featured_carousel.post": "投稿",
|
||||
"featured_carousel.previous": "前へ",
|
||||
"featured_carousel.slide": "{index} / {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "このフィルターカテゴリーはあなたがアクセスした投稿のコンテキストには適用されません。この投稿のコンテキストでもフィルターを適用するにはフィルターを編集する必要があります。",
|
||||
"filter_modal.added.context_mismatch_title": "コンテキストが一致しません!",
|
||||
"filter_modal.added.expired_explanation": "このフィルターカテゴリーは有効期限が切れています。適用するには有効期限を更新してください。",
|
||||
|
@ -528,8 +541,10 @@
|
|||
"hashtag.counter_by_accounts": "{count, plural, other {{counter}人投稿}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, other {{counter}件}}",
|
||||
"hashtag.counter_by_uses_today": "本日{count, plural, other {#件}}",
|
||||
"hashtag.feature": "プロフィールで紹介する",
|
||||
"hashtag.follow": "ハッシュタグをフォローする",
|
||||
"hashtag.mute": "#{hashtag}をミュート",
|
||||
"hashtag.unfeature": "プロフィールから外す",
|
||||
"hashtag.unfollow": "ハッシュタグのフォローを解除",
|
||||
"hashtags.and_other": "ほか{count, plural, other {#個}}",
|
||||
"hints.profiles.followers_may_be_missing": "フォロワーの一覧は不正確な場合があります。",
|
||||
|
@ -1046,6 +1061,13 @@
|
|||
"status.pin": "プロフィールに固定表示",
|
||||
"status.quote": "リンク",
|
||||
"status.quote_link": "引用リンクを挿入",
|
||||
"status.quote_error.filtered": "あなたのフィルター設定によって非表示になっています",
|
||||
"status.quote_error.not_found": "この投稿は表示できません。",
|
||||
"status.quote_error.pending_approval": "この投稿は投稿者の承認待ちです。",
|
||||
"status.quote_error.rejected": "この投稿は、オリジナルの投稿者が引用することを許可していないため、表示できません。",
|
||||
"status.quote_error.removed": "この投稿は投稿者によって削除されました。",
|
||||
"status.quote_error.unauthorized": "この投稿を表示する権限がないため、表示できません。",
|
||||
"status.quote_post_author": "{name} の投稿",
|
||||
"status.read_more": "もっと見る",
|
||||
"status.reblog": "ブースト",
|
||||
"status.reblog_private": "ブースト",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "제한된 서버들",
|
||||
"about.contact": "연락처:",
|
||||
"about.default_locale": "기본",
|
||||
"about.disclaimer": "Mastodon은 자유 오픈소스 소프트웨어이며, Mastodon gGmbH의 상표입니다",
|
||||
"about.domain_blocks.no_reason_available": "사유를 밝히지 않음",
|
||||
"about.domain_blocks.preamble": "마스토돈은 일반적으로 연합우주에 있는 어떤 서버의 사용자와도 게시물을 보고 응답을 할 수 있도록 허용합니다. 다음 항목들은 특정한 서버에 대해 만들어 진 예외사항입니다.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "제한됨",
|
||||
"about.domain_blocks.suspended.explanation": "이 서버의 어떤 데이터도 처리되거나, 저장 되거나 공유되지 않고, 이 서버의 어떤 유저와도 상호작용 하거나 대화할 수 없습니다.",
|
||||
"about.domain_blocks.suspended.title": "정지됨",
|
||||
"about.language_label": "언어",
|
||||
"about.not_available": "이 정보는 이 서버에서 사용할 수 없습니다.",
|
||||
"about.powered_by": "{mastodon}으로 구동되는 분산 소셜 미디어",
|
||||
"about.rules": "서버 규칙",
|
||||
|
@ -41,6 +43,7 @@
|
|||
"account.followers": "팔로워",
|
||||
"account.followers.empty": "아직 아무도 이 사용자를 팔로우하고 있지 않습니다.",
|
||||
"account.followers_counter": "{count, plural, other {팔로워 {counter}명}}",
|
||||
"account.followers_you_know_counter": "내가 아는 {counter} 명",
|
||||
"account.following": "팔로잉",
|
||||
"account.following_counter": "{count, plural, other {팔로잉 {counter}명}}",
|
||||
"account.follows.empty": "이 사용자는 아직 아무도 팔로우하고 있지 않습니다.",
|
||||
|
@ -307,6 +310,8 @@
|
|||
"emoji_button.search_results": "검색 결과",
|
||||
"emoji_button.symbols": "기호",
|
||||
"emoji_button.travel": "여행과 장소",
|
||||
"empty_column.account_featured.me": "아직 아무 것도 추천하지 않았습니다. 자주 사용하는 해시태그, 친구의 계정까지 내 계정에서 추천할 수 있다는 것을 알고 계셨나요?",
|
||||
"empty_column.account_featured.other": "{acct} 님은 아직 아무 것도 추천하지 않았습니다. 자주 사용하는 해시태그, 친구의 계정까지 내 계정에서 추천할 수 있다는 것을 알고 계셨나요?",
|
||||
"empty_column.account_featured_other.unknown": "이 계정은 아직 아무 것도 추천하지 않았습니다.",
|
||||
"empty_column.account_hides_collections": "이 사용자는 이 정보를 사용할 수 없도록 설정했습니다",
|
||||
"empty_column.account_suspended": "계정 정지됨",
|
||||
|
@ -340,6 +345,11 @@
|
|||
"explore.trending_links": "소식",
|
||||
"explore.trending_statuses": "게시물",
|
||||
"explore.trending_tags": "해시태그",
|
||||
"featured_carousel.header": "{count, plural, other {고정된 게시물}}",
|
||||
"featured_carousel.next": "다음",
|
||||
"featured_carousel.post": "게시물",
|
||||
"featured_carousel.previous": "이전",
|
||||
"featured_carousel.slide": "{total} 중 {index}",
|
||||
"filter_modal.added.context_mismatch_explanation": "이 필터 카테고리는 당신이 이 게시물에 접근한 문맥에 적용되지 않습니다. 만약 이 문맥에서도 필터되길 원한다면, 필터를 수정해야 합니다.",
|
||||
"filter_modal.added.context_mismatch_title": "문맥 불일치!",
|
||||
"filter_modal.added.expired_explanation": "이 필터 카테고리는 만료되었습니다, 적용하려면 만료 일자를 변경할 필요가 있습니다.",
|
||||
|
@ -860,11 +870,13 @@
|
|||
"status.mute_conversation": "대화 뮤트",
|
||||
"status.open": "상세 정보 표시",
|
||||
"status.pin": "고정",
|
||||
"status.quote_error.filtered": "필터에 의해 가려짐",
|
||||
"status.quote_error.not_found": "이 게시물은 표시할 수 없습니다.",
|
||||
"status.quote_error.pending_approval": "이 게시물은 원작자의 승인을 기다리고 있습니다.",
|
||||
"status.quote_error.rejected": "이 게시물은 원작자가 인용을 허용하지 않았기 때문에 표시할 수 없습니다.",
|
||||
"status.quote_error.removed": "이 게시물은 작성자에 의해 삭제되었습니다.",
|
||||
"status.quote_error.unauthorized": "이 게시물은 권한이 없기 때문에 볼 수 없습니다.",
|
||||
"status.quote_post_author": "{name} 님의 게시물",
|
||||
"status.read_more": "더 보기",
|
||||
"status.reblog": "부스트",
|
||||
"status.reblog_private": "원래의 수신자들에게 부스트",
|
||||
|
|
|
@ -160,7 +160,7 @@
|
|||
"column.directory": "Pārlūkot profilus",
|
||||
"column.domain_blocks": "Bloķētie domēni",
|
||||
"column.edit_list": "Labot sarakstu",
|
||||
"column.favourites": "Iecienītie",
|
||||
"column.favourites": "Izlase",
|
||||
"column.firehose": "Tiešraides plūsmas",
|
||||
"column.follow_requests": "Sekošanas pieprasījumi",
|
||||
"column.home": "Sākums",
|
||||
|
@ -262,6 +262,7 @@
|
|||
"dismissable_banner.community_timeline": "Šie ir jaunākie publiskie ieraksti no cilvēkiem, kuru konti ir mitināti {domain}.",
|
||||
"dismissable_banner.dismiss": "Atcelt",
|
||||
"dismissable_banner.explore_links": "Šie jaunumi šodien Fediversā tiek visvairāk kopīgoti. Jaunākas ziņas, kuras pievienoši vairāki dažādi cilvēki, tiek novietotas augstāk.",
|
||||
"dismissable_banner.explore_statuses": "Šie ieraksti šodien gūst uzmanību fediversā. Jaunāki ieraksti ar vairāk pastirpinājumiem un pievienošanām izlasē tiek kārtoti augstāk.",
|
||||
"dismissable_banner.public_timeline": "Šie ir jaunākie Fediverse lietotāju publiskie ieraksti, kuriem {domain} seko cilvēki.",
|
||||
"domain_block_modal.block": "Bloķēt serveri",
|
||||
"domain_block_modal.block_account_instead": "Tā vietā liegt @{name}",
|
||||
|
@ -306,7 +307,7 @@
|
|||
"empty_column.domain_blocks": "Vēl nav neviena bloķēta domēna.",
|
||||
"empty_column.explore_statuses": "Pašlaik nav nekā aktuāla. Ieskaties šeit vēlāk!",
|
||||
"empty_column.favourited_statuses": "Tev vēl nav izlasei pievienotu ierakstu. Kad pievienosi kādu, tas tiks parādīts šeit.",
|
||||
"empty_column.favourites": "Šo ierakstu vēl neviens nav pievienojis izlasei. Kad kāds to izdarīs, šeit parādīsies ieraksti.",
|
||||
"empty_column.favourites": "Šo ierakstu vēl neviens nav pievienojis izlasei. Kad kāds to izdarīs, tas parādīsies šeit.",
|
||||
"empty_column.follow_requests": "Šobrīd Tev nav sekošanas pieprasījumu. Kad saņemsi kādu, tas parādīsies šeit.",
|
||||
"empty_column.followed_tags": "Tu vēl neseko nevienam tēmturim. Kad to izdarīsi, tie tiks parādīti šeit.",
|
||||
"empty_column.hashtag": "Ar šo tēmturi nekas nav atrodams.",
|
||||
|
@ -398,6 +399,7 @@
|
|||
"hints.profiles.see_more_posts": "Skatīt vairāk ierakstu {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Var trūkt atbilžu no citiem serveriem.",
|
||||
"hints.threads.see_more": "Skatīt vairāk atbilžu {domain}",
|
||||
"home.column_settings.show_quotes": "Rādīt citātus",
|
||||
"home.column_settings.show_reblogs": "Rādīt pastiprinātos ierakstus",
|
||||
"home.column_settings.show_replies": "Rādīt atbildes",
|
||||
"home.hide_announcements": "Slēpt paziņojumus",
|
||||
|
@ -435,7 +437,7 @@
|
|||
"keyboard_shortcuts.down": "Pārvietoties lejup sarakstā",
|
||||
"keyboard_shortcuts.enter": "Atvērt ierakstu",
|
||||
"keyboard_shortcuts.favourite": "Pievienot ierakstu izlasei",
|
||||
"keyboard_shortcuts.favourites": "Atvērt izlašu sarakstu",
|
||||
"keyboard_shortcuts.favourites": "Atvērt izlases sarakstu",
|
||||
"keyboard_shortcuts.federated": "Atvērt apvienoto laika līniju",
|
||||
"keyboard_shortcuts.heading": "Īsinājumtaustiņi",
|
||||
"keyboard_shortcuts.home": "Atvērt mājas laika līniju",
|
||||
|
@ -525,6 +527,9 @@
|
|||
"notification.admin.report_statuses": "{name} ziņoja par {target} ar iemeslu: {category}",
|
||||
"notification.admin.sign_up": "{name} pierakstījās",
|
||||
"notification.favourite": "{name} pievienoja izlasei Tavu ierakstu",
|
||||
"notification.favourite.name_and_others_with_link": "{name} un <a>{count, plural, one {# cits} other {# citi}}</a> pievienoja Tavu ierakstu izlasē",
|
||||
"notification.favourite_pm": "{name} pievienoja izlasē Tavu privāto pieminējumu",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} un <a>{count, plural, one {# cits} other {# citi}}</a> pievienoja Tavu privāto pieminējumu izlasē",
|
||||
"notification.follow": "{name} uzsāka Tev sekot",
|
||||
"notification.follow_request": "{name} nosūtīja Tev sekošanas pieprasījumu",
|
||||
"notification.mentioned_you": "{name} pieminēja jūs",
|
||||
|
@ -573,7 +578,7 @@
|
|||
"notifications.column_settings.update": "Labojumi:",
|
||||
"notifications.filter.all": "Visi",
|
||||
"notifications.filter.boosts": "Pastiprinātie ieraksti",
|
||||
"notifications.filter.favourites": "Izlases",
|
||||
"notifications.filter.favourites": "Izlase",
|
||||
"notifications.filter.follows": "Seko",
|
||||
"notifications.filter.mentions": "Pieminēšanas",
|
||||
"notifications.filter.polls": "Aptaujas rezultāti",
|
||||
|
@ -768,6 +773,7 @@
|
|||
"status.reblogs.empty": "Neviens vēl nav pastiprinājis šo ierakstu. Kad kāds to izdarīs, šeit tiks parādīti lietotāji.",
|
||||
"status.redraft": "Dzēst un pārrakstīt",
|
||||
"status.remove_bookmark": "Noņemt grāmatzīmi",
|
||||
"status.remove_favourite": "Noņemt no izlases",
|
||||
"status.replied_to": "Atbildēja {name}",
|
||||
"status.reply": "Atbildēt",
|
||||
"status.replyAll": "Atbildēt uz tematu",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Siū 管制 ê 服侍器",
|
||||
"about.contact": "聯絡lâng:",
|
||||
"about.default_locale": "預設",
|
||||
"about.disclaimer": "Mastodon是自由、開放原始碼ê軟體,mā是Mastodon gGmbH ê商標。",
|
||||
"about.domain_blocks.no_reason_available": "原因bē-tàng用",
|
||||
"about.domain_blocks.preamble": "Mastodon一般ē允准lí看別ê fediverse 服侍器來ê聯絡人kap hām用者交流。Tsiah ê 是本服侍器建立ê例外。",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "有限制",
|
||||
"about.domain_blocks.suspended.explanation": "Uì tsit ê服侍器來ê資料lóng bē處理、儲存á是交換,無可能kap tsit ê服侍器ê用者互動á是溝通。.",
|
||||
"about.domain_blocks.suspended.title": "權限中止",
|
||||
"about.language_label": "言語",
|
||||
"about.not_available": "Tsit ê資訊bē-tàng tī tsit ê服侍器使用。",
|
||||
"about.powered_by": "由 {mastodon} 提供ê非中心化社群媒體",
|
||||
"about.rules": "服侍器ê規則",
|
||||
|
@ -308,6 +310,8 @@
|
|||
"emoji_button.search_results": "Tshiau-tshuē ê結果",
|
||||
"emoji_button.symbols": "符號",
|
||||
"emoji_button.travel": "旅行kap地點",
|
||||
"empty_column.account_featured.me": "Lí iáu無任何ê特色內容。Lí kám知影lí ē當kā lí tsia̍p-tsia̍p用ê hashtag,甚至是朋友ê口座揀做特色ê內容,khǹg佇lí ê個人資料內底?",
|
||||
"empty_column.account_featured.other": "{acct} iáu無任何ê特色內容。Lí kám知影lí ē當kā lí tsia̍p-tsia̍p用ê hashtag,甚至是朋友ê口座揀做特色ê內容,khǹg佇lí ê個人資料內底?",
|
||||
"empty_column.account_featured_other.unknown": "Tsit ê口座iáu無任何ê特色內容。",
|
||||
"empty_column.account_hides_collections": "Tsit位用者選擇無愛公開tsit ê資訊",
|
||||
"empty_column.account_suspended": "口座已經受停止",
|
||||
|
@ -341,6 +345,11 @@
|
|||
"explore.trending_links": "新聞",
|
||||
"explore.trending_statuses": "PO文",
|
||||
"explore.trending_tags": "Hashtag",
|
||||
"featured_carousel.header": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 釘起來ê PO文",
|
||||
"featured_carousel.next": "下tsi̍t ê",
|
||||
"featured_carousel.post": "PO文",
|
||||
"featured_carousel.previous": "頂tsi̍t ê",
|
||||
"featured_carousel.slide": "{total} 內底ê {index}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Tsit ê過濾器類別bē當適用佇lí所接近使用ê PO文ê情境。若是lí mā beh佇tsit ê情境過濾tsit ê PO文,lí著編輯過濾器。.",
|
||||
"filter_modal.added.context_mismatch_title": "本文無sio合!",
|
||||
"filter_modal.added.expired_explanation": "Tsit ê過濾器類別過期ah,lí需要改到期ê日期來繼續用。",
|
||||
|
@ -421,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "佇 {domain} 看koh khah tsē ê PO文",
|
||||
"hints.threads.replies_may_be_missing": "Tuì其他ê服侍器來ê回應可能有phah m̄見。",
|
||||
"hints.threads.see_more": "佇 {domain} 看koh khah tsē ê回應",
|
||||
"home.column_settings.show_quotes": "顯示引用",
|
||||
"home.column_settings.show_reblogs": "顯示轉PO",
|
||||
"home.column_settings.show_replies": "顯示回應",
|
||||
"home.hide_announcements": "Khàm掉公告",
|
||||
|
@ -601,13 +611,13 @@
|
|||
"notification.moderation_warning.action_none": "Lí ê口座有收著審核ê警告。",
|
||||
"notification.moderation_warning.action_sensitive": "Tuì tsit-má開始,lí êPO文ē標做敏感ê內容。",
|
||||
"notification.moderation_warning.action_silence": "Lí ê口座hōo lâng限制ah。",
|
||||
"notification.moderation_warning.action_suspend": "Lí ê口座已經受停權。",
|
||||
"notification.moderation_warning.action_suspend": "Lí ê口座ê權限已經停止ah。",
|
||||
"notification.own_poll": "Lí ê投票結束ah",
|
||||
"notification.poll": "Lí bat投ê投票結束ah",
|
||||
"notification.reblog": "{name} 轉送lí ê PO文",
|
||||
"notification.reblog.name_and_others_with_link": "{name} kap<a>{count, plural, other {另外 # ê lâng}}</a>轉送lí ê PO文",
|
||||
"notification.relationships_severance_event": "Kap {name} ê結連無去",
|
||||
"notification.relationships_severance_event.account_suspension": "{from} ê管理員kā {target} 停權ah,意思是lí bē koh再接受tuì in 來ê更新,á是hām in互動。",
|
||||
"notification.relationships_severance_event.account_suspension": "{from} ê管理員kā {target} 停止權限ah,意思是lí bē koh再接受tuì in 來ê更新,á是hām in互動。",
|
||||
"notification.relationships_severance_event.domain_block": "{from} ê 管理員kā {target} 封鎖ah,包含 {followersCount} 位跟tuè lí ê lâng,kap {followingCount, plural, other {#}} 位lí跟tuè ê口座。",
|
||||
"notification.relationships_severance_event.learn_more": "看詳細",
|
||||
"notification.relationships_severance_event.user_domain_block": "Lí已經kā {target} 封鎖ah,ē suá走 {followersCount} 位跟tuè lí ê lâng,kap {followingCount, plural, other {#}} 位lí跟tuè ê口座。",
|
||||
|
@ -891,12 +901,34 @@
|
|||
"status.translated_from_with": "用 {provider} 翻譯 {lang}",
|
||||
"status.uncached_media_warning": "Bē當先看māi",
|
||||
"status.unmute_conversation": "Kā對話取消消音",
|
||||
"subscribed_languages.lead": "Tī改變了後,kan-ta所揀ê語言ê PO文tsiah ē顯示佇lí ê厝ê時間線kap列單。揀「無」來接受所有語言êPO文。",
|
||||
"subscribed_languages.save": "儲存改變",
|
||||
"subscribed_languages.target": "改 {target} ê訂ê語言",
|
||||
"tabs_bar.home": "頭頁",
|
||||
"tabs_bar.notifications": "通知",
|
||||
"terms_of_service.effective_as_of": "{date} 起實施",
|
||||
"terms_of_service.title": "服務規定",
|
||||
"terms_of_service.upcoming_changes_on": "Ē tī {date} 改變",
|
||||
"time_remaining.days": "Tshun {number, plural, other {# kang}}",
|
||||
"time_remaining.hours": "Tshun {number, plural, other {# 點鐘}}",
|
||||
"time_remaining.minutes": "Tshun {number, plural, other {# 分鐘}}",
|
||||
"time_remaining.moments": "Tshun ê時間",
|
||||
"time_remaining.seconds": "Tshun {number, plural, other {# 秒}}",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} ê} other {{counter} ê}} lâng tī過去 {days, plural, one {kang} other {{days} kang}}內底",
|
||||
"trends.trending_now": "Tsit-má ê趨勢",
|
||||
"ui.beforeunload": "Nā離開Mastodon,lí ê草稿ē無去。",
|
||||
"units.short.billion": "{count}B",
|
||||
"units.short.million": "{count}M",
|
||||
"units.short.thousand": "{count}K",
|
||||
"upload_area.title": "Giú放來傳起去",
|
||||
"upload_button.label": "加圖片、影片á是聲音檔",
|
||||
"upload_error.limit": "超過檔案傳起去ê限制",
|
||||
"upload_error.poll": "Bô允準佇投票ê時kā檔案傳起去。",
|
||||
"upload_form.drag_and_drop.instructions": "Nā beh選媒體附件,請tshi̍h空白key á是Enter key。Giú ê時,請用方向key照指定ê方向suá媒體附件。Beh khǹg媒體附件佇伊ê新位置,請koh tshi̍h空白key á是Enter key,或者tshi̍h Esc key來取消。",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Suá位取消ah,媒體附件 {item} khǹg落來ah。",
|
||||
"upload_form.drag_and_drop.on_drag_end": "媒體附件 {item} khǹg落來ah。",
|
||||
"upload_form.drag_and_drop.on_drag_over": "媒體附件 {item} suá tín動ah。",
|
||||
"upload_form.drag_and_drop.on_drag_start": "媒體附件 {item} 揀起來ah。",
|
||||
"upload_form.edit": "編輯",
|
||||
"upload_progress.label": "Teh傳起去……",
|
||||
"upload_progress.processing": "Teh處理……",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Bekijk meer berichten op {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Antwoorden van andere servers kunnen ontbreken.",
|
||||
"hints.threads.see_more": "Bekijk meer reacties op {domain}",
|
||||
"home.column_settings.show_quotes": "Citaten tonen",
|
||||
"home.column_settings.show_reblogs": "Boosts tonen",
|
||||
"home.column_settings.show_replies": "Reacties tonen",
|
||||
"home.hide_announcements": "Mededelingen verbergen",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Modererte tenarar",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Standard",
|
||||
"about.disclaimer": "Mastodon er gratis programvare med open kjeldekode, og eit varemerke frå Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Årsaka er ikkje tilgjengeleg",
|
||||
"about.domain_blocks.preamble": "Mastodon gjev deg som regel lov til å sjå innhald og samhandla med brukarar frå alle andre tenarar i allheimen. Dette er unntaka som er valde for akkurat denne tenaren.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Avgrensa",
|
||||
"about.domain_blocks.suspended.explanation": "Ingen data frå denne tenaren vert handsama, lagra eller sende til andre, noko som gjer det umogeleg å samhandla eller kommunisera med brukarar på denne tenaren.",
|
||||
"about.domain_blocks.suspended.title": "Utestengd",
|
||||
"about.language_label": "Språk",
|
||||
"about.not_available": "Denne informasjonen er ikkje gjort tilgjengeleg på denne tenaren.",
|
||||
"about.powered_by": "Desentraliserte sosiale medium drive av {mastodon}",
|
||||
"about.rules": "Tenarreglar",
|
||||
|
@ -308,6 +310,8 @@
|
|||
"emoji_button.search_results": "Søkeresultat",
|
||||
"emoji_button.symbols": "Symbol",
|
||||
"emoji_button.travel": "Reise & stader",
|
||||
"empty_column.account_featured.me": "Du har ikkje valt ut noko enno. Visste du at du kan velja ut merkelappar du bruker mykje, og til og med venekontoar på profilen din?",
|
||||
"empty_column.account_featured.other": "{acct} har ikkje valt ut noko enno. Visste du at du kan velja ut merkelappar du bruker mykje, og til og med venekontoar på profilen din?",
|
||||
"empty_column.account_featured_other.unknown": "Denne kontoen har ikkje valt ut noko enno.",
|
||||
"empty_column.account_hides_collections": "Denne brukaren har valt å ikkje gjere denne informasjonen tilgjengeleg",
|
||||
"empty_column.account_suspended": "Kontoen er utestengd",
|
||||
|
@ -341,6 +345,11 @@
|
|||
"explore.trending_links": "Nytt",
|
||||
"explore.trending_statuses": "Innlegg",
|
||||
"explore.trending_tags": "Emneknaggar",
|
||||
"featured_carousel.header": "{count, plural, one {Festa innlegg} other {Festa innlegg}}",
|
||||
"featured_carousel.next": "Neste",
|
||||
"featured_carousel.post": "Innlegg",
|
||||
"featured_carousel.previous": "Førre",
|
||||
"featured_carousel.slide": "{index} av {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Denne filterkategorien gjeld ikkje i den samanhengen du har lese dette innlegget. Viss du vil at innlegget skal filtrerast i denne samanhengen òg, må du endra filteret.",
|
||||
"filter_modal.added.context_mismatch_title": "Konteksten passar ikkje!",
|
||||
"filter_modal.added.expired_explanation": "Denne filterkategorien har gått ut på dato. Du må endre best før datoen for at den skal gjelde.",
|
||||
|
@ -421,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Sjå fleire innlegg på {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Svar frå andre tenarar manglar kanskje.",
|
||||
"hints.threads.see_more": "Sjå fleire svar på {domain}",
|
||||
"home.column_settings.show_quotes": "Vis sitat",
|
||||
"home.column_settings.show_reblogs": "Vis framhevingar",
|
||||
"home.column_settings.show_replies": "Vis svar",
|
||||
"home.hide_announcements": "Skjul kunngjeringar",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Modererte servere",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Standard",
|
||||
"about.disclaimer": "Mastodon er gratis, åpen kildekode-programvare og et varemerke fra Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Årsak ikke tilgjengelig",
|
||||
"about.domain_blocks.preamble": "Mastodon lar deg normalt sett se innholdet fra og samhandle med brukere fra enhver annen tjener i fødiverset. Dette er unntakene som har blitt lagt inn på denne tjeneren.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "Begrenset",
|
||||
"about.domain_blocks.suspended.explanation": "Ikke noe innhold fra denne tjeneren vil bli behandlet, lagret eller utvekslet. Det gjør det umulig å samhandle eller kommunisere med brukere fra denne tjeneren.",
|
||||
"about.domain_blocks.suspended.title": "Suspendert",
|
||||
"about.language_label": "Språk",
|
||||
"about.not_available": "Denne informasjonen er ikke gjort tilgjengelig på denne tjeneren.",
|
||||
"about.powered_by": "Desentraliserte sosiale medier drevet av {mastodon}",
|
||||
"about.rules": "Regler for serveren",
|
||||
|
@ -200,6 +202,7 @@
|
|||
"compose_form.poll.duration": "Avstemningens varighet",
|
||||
"compose_form.poll.multiple": "Flervalg",
|
||||
"compose_form.poll.option_placeholder": "Valg {number}",
|
||||
"compose_form.poll.single": "Enkelt valg",
|
||||
"compose_form.poll.switch_to_multiple": "Endre avstemning til å tillate flere valg",
|
||||
"compose_form.poll.switch_to_single": "Endre avstemning til å tillate ett valg",
|
||||
"compose_form.poll.type": "Stil",
|
||||
|
@ -223,14 +226,23 @@
|
|||
"confirmations.edit.confirm": "Redigér",
|
||||
"confirmations.edit.message": "Å redigere nå vil overskrive meldingen du skriver for øyeblikket. Er du sikker på at du vil fortsette?",
|
||||
"confirmations.edit.title": "Overskriv innlegg?",
|
||||
"confirmations.follow_to_list.confirm": "Følg og legg til i liste",
|
||||
"confirmations.follow_to_list.message": "Du må følge {name} for å kunne legge vedkommende til i en liste.",
|
||||
"confirmations.follow_to_list.title": "Følg bruker?",
|
||||
"confirmations.logout.confirm": "Logg ut",
|
||||
"confirmations.logout.message": "Er du sikker på at du vil logge ut?",
|
||||
"confirmations.logout.title": "Logg ut?",
|
||||
"confirmations.missing_alt_text.confirm": "Legg til bildebeskrivelse",
|
||||
"confirmations.missing_alt_text.message": "Innlegget ditt mangler bildebeskrivelse. Legg til en tekst for å gjøre innholdet ditt tilgjengelig for flere brukere.",
|
||||
"confirmations.missing_alt_text.secondary": "Legg ut likevel",
|
||||
"confirmations.missing_alt_text.title": "Legg til bildebeskrivelse?",
|
||||
"confirmations.mute.confirm": "Demp",
|
||||
"confirmations.redraft.confirm": "Slett og skriv på nytt",
|
||||
"confirmations.redraft.message": "Er du sikker på at du vil slette dette innlegget og lagre det på nytt? Favoritter og fremhevinger vil gå tapt, og svar til det originale innlegget vil bli foreldreløse.",
|
||||
"confirmations.redraft.title": "Slett og skriv på nytt?",
|
||||
"confirmations.remove_from_followers.confirm": "Fjern følger",
|
||||
"confirmations.remove_from_followers.message": "{name} vil ikke lenger følge deg. Er du sikker på at du vil fortsette?",
|
||||
"confirmations.remove_from_followers.title": "Fjern følger?",
|
||||
"confirmations.reply.confirm": "Svar",
|
||||
"confirmations.reply.message": "Å svare nå vil overskrive meldingen du skriver for øyeblikket. Er du sikker på at du vil fortsette?",
|
||||
"confirmations.reply.title": "Overskriv innlegg?",
|
||||
|
@ -247,7 +259,7 @@
|
|||
"copy_icon_button.copied": "Kopiert til utklippstavlen",
|
||||
"copypaste.copied": "Kopiert",
|
||||
"copypaste.copy_to_clipboard": "Kopier til utklippstavle",
|
||||
"directory.federated": "Fra det kjente strømiverset",
|
||||
"directory.federated": "Fra det kjente fødiverset",
|
||||
"directory.local": "Kun fra {domain}",
|
||||
"directory.new_arrivals": "Nye ankomster",
|
||||
"directory.recently_active": "Nylig aktiv",
|
||||
|
@ -255,12 +267,18 @@
|
|||
"disabled_account_banner.text": "Din konto {disabledAccount} er for øyeblikket deaktivert.",
|
||||
"dismissable_banner.community_timeline": "Dette er de nyeste offentlige innleggene fra personer med kontoer på {domain}.",
|
||||
"dismissable_banner.dismiss": "Avvis",
|
||||
"dismissable_banner.explore_links": "Disse nyhetene snakker folk om akkurat nå på denne og andre servere i det desentraliserte nettverket.",
|
||||
"dismissable_banner.explore_statuses": "Disse innleggene fra denne og andre servere i det desentraliserte nettverket får økt oppmerksomhet på denne serveren akkurat nå. Nyere innlegg med flere fremhevinger og favoritter er rangert høyere.",
|
||||
"dismissable_banner.explore_tags": "Disse emneknaggene snakker folk om akkurat nå på det desentraliserte nettverket. Emneknagger brukt av flere rangeres høyere.",
|
||||
"dismissable_banner.public_timeline": "Dette er de nyeste offentlige innleggene fra folk brukerne av {domain} følger på det desentraliserte nettverket.",
|
||||
"domain_block_modal.block": "Blokker server",
|
||||
"domain_block_modal.block_account_instead": "Blokker @{name} i stedet",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Personer fra denne serveren kan samhandle med dine gamle innlegg.",
|
||||
"domain_block_modal.they_cant_follow": "Ingen fra denne serveren kan følge deg.",
|
||||
"domain_block_modal.they_wont_know": "De kommer ikke til å få vite at du har valgt å blokkere dem.",
|
||||
"domain_block_modal.title": "Blokker domenet?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "Du kommer til å miste {followersCount, plural, one {{followersCountDisplay} følger} other {{followersCountDisplay} følgere}} og {followingCount, plural, one {{followingCountDisplay} du følger} other {{followingCountDisplay} du følger}}.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Du vil miste alle følgere og folk du følger fra denne serveren.",
|
||||
"domain_block_modal.you_wont_see_posts": "Du vil ikke se innlegg eller få varsler fra brukere på denne serveren.",
|
||||
"domain_pill.activitypub_lets_connect": "Den lar deg koble til og samhandle med folk ikke bare på Mastodon, men også på tvers av forskjellige sosiale apper.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub er liksom språket Mastodon snakker med andre sosiale nettverk.",
|
||||
|
@ -292,6 +310,9 @@
|
|||
"emoji_button.search_results": "Søkeresultat",
|
||||
"emoji_button.symbols": "Symboler",
|
||||
"emoji_button.travel": "Reise & steder",
|
||||
"empty_column.account_featured.me": "Du har ikke fremhevet noe ennå. Visste du at du kan fremheve emneknaggene du bruker mest, eller til og med dine venners profilsider?",
|
||||
"empty_column.account_featured.other": "{acct} har ikke fremhevet noe ennå. Visste du at du kan fremheve emneknaggene du bruker mest, eller til og med dine venners profilsider?",
|
||||
"empty_column.account_featured_other.unknown": "Denne kontoen har ikke fremhevet noe ennå.",
|
||||
"empty_column.account_hides_collections": "Denne brukeren har valgt å ikke gjøre denne informasjonen tilgjengelig",
|
||||
"empty_column.account_suspended": "Kontoen er suspendert",
|
||||
"empty_column.account_timeline": "Ingen innlegg her!",
|
||||
|
@ -324,6 +345,11 @@
|
|||
"explore.trending_links": "Nyheter",
|
||||
"explore.trending_statuses": "Innlegg",
|
||||
"explore.trending_tags": "Emneknagger",
|
||||
"featured_carousel.header": "{count, plural, one {{counter} festet innlegg} other {{counter} festede innlegg}}",
|
||||
"featured_carousel.next": "Neste",
|
||||
"featured_carousel.post": "Innlegg",
|
||||
"featured_carousel.previous": "Forrige",
|
||||
"featured_carousel.slide": "{index} av {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Denne filterkategorien gjelder ikke for den konteksten du har åpnet dette innlegget i. Hvis du vil at innlegget skal filtreres i denne konteksten også, må du redigere filteret.",
|
||||
"filter_modal.added.context_mismatch_title": "Feil sammenheng!",
|
||||
"filter_modal.added.expired_explanation": "Denne filterkategorien er utløpt, du må endre utløpsdato for at den skal gjelde.",
|
||||
|
@ -340,6 +366,7 @@
|
|||
"filter_modal.select_filter.subtitle": "Bruk en eksisterende kategori eller opprett en ny",
|
||||
"filter_modal.select_filter.title": "Filtrer dette innlegget",
|
||||
"filter_modal.title.status": "Filtrer et innlegg",
|
||||
"filter_warning.matches_filter": "Treff på filter \"<span>{title}</span>\"",
|
||||
"filtered_notifications_banner.pending_requests": "Fra {count, plural, =0 {ingen} one {en person} other {# folk}} du kanskje kjenner",
|
||||
"filtered_notifications_banner.title": "Filtrerte varsler",
|
||||
"firehose.all": "Alt",
|
||||
|
@ -373,6 +400,9 @@
|
|||
"footer.status": "Status",
|
||||
"generic.saved": "Lagret",
|
||||
"getting_started.heading": "Kom i gang",
|
||||
"hashtag.admin_moderation": "Åpne modereringsgrensesnitt for #{name}",
|
||||
"hashtag.browse": "Utforsk poster i #{hashtag}",
|
||||
"hashtag.browse_from_account": "Utforsk poster av @{name} i #{hashtag}",
|
||||
"hashtag.column_header.tag_mode.all": "og {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "eller {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "uten {additional}",
|
||||
|
@ -385,7 +415,10 @@
|
|||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} deltaker} other {{counter} deltakere}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {ett innlegg} other {{counter} innlegg}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {ett innlegg} other {{counter} innlegg}} i dag",
|
||||
"hashtag.feature": "Fremhev på din profil",
|
||||
"hashtag.follow": "Følg emneknagg",
|
||||
"hashtag.mute": "Demp #{hashtag}",
|
||||
"hashtag.unfeature": "Ikke fremhev på din profil",
|
||||
"hashtag.unfollow": "Slutt å følge emneknagg",
|
||||
"hashtags.and_other": "…og {count, plural, one{en til} other {# til}}",
|
||||
"hints.profiles.followers_may_be_missing": "Følgere for denne profilen mangler kanskje.",
|
||||
|
@ -396,6 +429,7 @@
|
|||
"hints.profiles.see_more_posts": "Se flere innlegg på {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Svar fra andre servere mangler kanskje.",
|
||||
"hints.threads.see_more": "Se flere svar på {domain}",
|
||||
"home.column_settings.show_quotes": "Vis sitater",
|
||||
"home.column_settings.show_reblogs": "Vis fremhevinger",
|
||||
"home.column_settings.show_replies": "Vis svar",
|
||||
"home.hide_announcements": "Skjul kunngjøring",
|
||||
|
@ -415,6 +449,11 @@
|
|||
"ignore_notifications_modal.not_following_title": "Overse varsler fra folk du ikke følger?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Overse varsler fra uoppfordrede private omtaler?",
|
||||
"info_button.label": "Hjelp",
|
||||
"info_button.what_is_alt_text": "<h1>Hva er alternativ tekst?</h1> <p>Alternativ tekst gir en bildebeskrivelse til folk med nedsatt syn, lav båndbredde eller de som vil ha mer kontekst.</p> <p>Du kan forbedre tilgjengeligheten og forståelsen for alle ved å skive en tydelig, konsis og objektiv alternativ tekst.</p> <ul> <li>Få med deg viktige elementer</li> <li>Summér tekst i bilder</li> <li>Bruk vanlige setningsstrukturer</li> <li>Unngå overflødig informasjon</li> <li>Fokuser på trender og viktige funn i komplekse grafiske fremstillinger (som diagram og kart)</li> </ul>",
|
||||
"interaction_modal.action.favourite": "Favoriser fra din konto for å fortsette.",
|
||||
"interaction_modal.action.follow": "Følg fra din konto for å fortsette.",
|
||||
"interaction_modal.action.reply": "Svar fra kontoen din for å fortsette.",
|
||||
"interaction_modal.action.vote": "Stem fra kontoen din for å fortsette.",
|
||||
"interaction_modal.go": "Gå",
|
||||
"interaction_modal.no_account_yet": "Har du ikke en konto ennå?",
|
||||
"interaction_modal.on_another_server": "På en annen server",
|
||||
|
@ -423,6 +462,8 @@
|
|||
"interaction_modal.title.follow": "Følg {name}",
|
||||
"interaction_modal.title.reblog": "Fremhev {name} sitt innlegg",
|
||||
"interaction_modal.title.reply": "Svar på {name} sitt innlegg",
|
||||
"interaction_modal.title.vote": "Stem på {name}s avstemning",
|
||||
"interaction_modal.username_prompt": "For eksempel {example}",
|
||||
"intervals.full.days": "{number, plural,one {# dag} other {# dager}}",
|
||||
"intervals.full.hours": "{number, plural, one {# time} other {# timer}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minutt} other {# minutter}}",
|
||||
|
@ -458,6 +499,7 @@
|
|||
"keyboard_shortcuts.toggle_hidden": "Vis/skjul tekst bak innholdsvarsel",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "Vis/skjul media",
|
||||
"keyboard_shortcuts.toot": "Start et nytt innlegg",
|
||||
"keyboard_shortcuts.translate": "for å oversette et innlegg",
|
||||
"keyboard_shortcuts.unfocus": "Fjern fokus fra komponerings-/søkefeltet",
|
||||
"keyboard_shortcuts.up": "Flytt oppover i listen",
|
||||
"lightbox.close": "Lukk",
|
||||
|
@ -470,10 +512,18 @@
|
|||
"link_preview.shares": "{count, plural, one {{counter} innlegg} other {{counter} innlegg}}",
|
||||
"lists.add_member": "Legg til",
|
||||
"lists.add_to_list": "Legg til i listen",
|
||||
"lists.add_to_lists": "Legg til {name} i liste",
|
||||
"lists.create": "Opprett",
|
||||
"lists.create_a_list_to_organize": "Lag en ny liste for å organisere hjemmetidslinjen",
|
||||
"lists.create_list": "Lag liste",
|
||||
"lists.delete": "Slett listen",
|
||||
"lists.done": "Ferdig",
|
||||
"lists.edit": "Rediger listen",
|
||||
"lists.find_users_to_add": "Fin brukere å legge til",
|
||||
"lists.list_name": "Listenavn",
|
||||
"lists.new_list_name": "Nytt listenavn",
|
||||
"lists.no_lists_yet": "Ingen lister ennå.",
|
||||
"lists.no_members_yet": "Ingen medlemmer ennå.",
|
||||
"lists.no_results_found": "Ingen treff.",
|
||||
"lists.remove_member": "Fjern",
|
||||
"lists.replies_policy.followed": "Enhver fulgt bruker",
|
||||
|
@ -524,15 +574,24 @@
|
|||
"not_signed_in_indicator.not_signed_in": "Du må logge inn for å få tilgang til denne ressursen.",
|
||||
"notification.admin.report": "{name} rapporterte {target}",
|
||||
"notification.admin.report_account": "{name} rapporterte {count, plural, one {et innlegg} other {# innlegg}} fra {target} for {category}",
|
||||
"notification.admin.report_statuses": "{name} rapporterte {target} for {category}",
|
||||
"notification.admin.report_statuses_other": "{name} rapporterte {target}",
|
||||
"notification.admin.sign_up": "{name} registrerte seg",
|
||||
"notification.favourite": "{name} favorittmarkerte innlegget ditt",
|
||||
"notification.follow": "{name} fulgte deg",
|
||||
"notification.follow_request": "{name} har bedt om å få følge deg",
|
||||
"notification.label.mention": "Nevn",
|
||||
"notification.label.private_mention": "Private omtaler",
|
||||
"notification.label.private_reply": "Private svar",
|
||||
"notification.label.reply": "Svar",
|
||||
"notification.mention": "Nevn",
|
||||
"notification.mentioned_you": "{name} nevnte deg",
|
||||
"notification.moderation-warning.learn_more": "Lær mer",
|
||||
"notification.moderation_warning": "Du har fått en advarsel fra en moderator",
|
||||
"notification.moderation_warning.action_delete_statuses": "Noen av innleggene dine har blitt fjernet.",
|
||||
"notification.moderation_warning.action_disable": "Kontoen din har blitt deaktivert.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Noen av innleggene dine har blitt markert som sensitive.",
|
||||
"notification.moderation_warning.action_none": "Kontoen din har fått en advarsel fra en moderator.",
|
||||
"notification.own_poll": "Avstemningen din er ferdig",
|
||||
"notification.reblog": "{name} fremhevet ditt innlegg",
|
||||
"notification.relationships_severance_event.learn_more": "Lær mer",
|
||||
|
@ -542,7 +601,11 @@
|
|||
"notification_requests.dismiss": "Lukk",
|
||||
"notification_requests.edit_selection": "Redigér",
|
||||
"notification_requests.exit_selection": "Ferdig",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Varsler fra denne kontoen har blitt filtrert bort fordi kontoen eller serveren den er på har blitt sensurert av en moderator.",
|
||||
"notification_requests.maximize": "Maksimer",
|
||||
"notification_requests.minimize_banner": "Minimer banneret for filtrerte varsler",
|
||||
"notification_requests.notifications_from": "Varsler fra {name}",
|
||||
"notification_requests.title": "Filtrerte varlser",
|
||||
"notification_requests.view": "Vis varsler",
|
||||
"notifications.clear": "Fjern varsler",
|
||||
"notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler permanent?",
|
||||
|
@ -602,6 +665,7 @@
|
|||
"onboarding.follows.done": "Ferdig",
|
||||
"onboarding.follows.empty": "Dessverre kan ingen resultater vises akkurat nå. Du kan prøve å bruke søk eller bla gjennom utforske-siden for å finne folk å følge, eller prøve igjen senere.",
|
||||
"onboarding.follows.search": "Søk",
|
||||
"onboarding.follows.title": "Følg folk for å komme i gang",
|
||||
"onboarding.profile.discoverable": "Gjør min profil synlig",
|
||||
"onboarding.profile.display_name": "Visningsnavn",
|
||||
"onboarding.profile.display_name_hint": "Ditt fulle navn eller ditt morsomme navn…",
|
||||
|
@ -695,6 +759,7 @@
|
|||
"report_notification.categories.other": "Annet",
|
||||
"report_notification.categories.other_sentence": "annet",
|
||||
"report_notification.categories.spam": "Søppelpost",
|
||||
"report_notification.categories.spam_sentence": "spam",
|
||||
"report_notification.categories.violation": "Regelbrudd",
|
||||
"report_notification.open": "Åpne rapport",
|
||||
"search.no_recent_searches": "Ingen søk nylig",
|
||||
|
@ -719,11 +784,14 @@
|
|||
"search_results.no_results": "Ingen resultater.",
|
||||
"search_results.see_all": "Se alle",
|
||||
"search_results.statuses": "Innlegg",
|
||||
"search_results.title": "Søk etter \"{q}\"",
|
||||
"server_banner.about_active_users": "Personer som har brukt denne serveren i løpet av de siste 30 dagene (aktive brukere månedlig)",
|
||||
"server_banner.active_users": "aktive brukere",
|
||||
"server_banner.administered_by": "Administrert av:",
|
||||
"server_banner.is_one_of_many": "{domain} er en av mange uavhengige Mastodon-servere du kan bruke for å delta i det desentraliserte sosiale nettet.",
|
||||
"server_banner.server_stats": "Serverstatistikk:",
|
||||
"sign_in_banner.create_account": "Opprett konto",
|
||||
"sign_in_banner.follow_anyone": "Følg hvem som helst på tvers av det desentraliserte sosiale nettet. Ingen algoritmer, reklamer eller clickbait.",
|
||||
"sign_in_banner.sign_in": "Logg inn",
|
||||
"sign_in_banner.sso_redirect": "Logg inn eller registrer deg",
|
||||
"status.admin_account": "Åpne moderatorgrensesnittet for @{name}",
|
||||
|
@ -733,12 +801,14 @@
|
|||
"status.bookmark": "Bokmerke",
|
||||
"status.cancel_reblog_private": "Fjern fremheving",
|
||||
"status.cannot_reblog": "Denne posten kan ikke fremheves",
|
||||
"status.continued_thread": "Fortsettelse av samtale",
|
||||
"status.copy": "Kopier lenken til innlegget",
|
||||
"status.delete": "Slett",
|
||||
"status.detailed_status": "Detaljert samtalevisning",
|
||||
"status.direct": "Nevn @{name} privat",
|
||||
"status.direct_indicator": "Privat omtale",
|
||||
"status.edit": "Rediger",
|
||||
"status.edited": "Sist endret {date}",
|
||||
"status.edited_x_times": "Redigert {count, plural,one {{count} gang} other {{count} ganger}}",
|
||||
"status.favourite": "Favoritt",
|
||||
"status.filter": "Filtrer dette innlegget",
|
||||
|
@ -754,6 +824,10 @@
|
|||
"status.mute_conversation": "Demp samtale",
|
||||
"status.open": "Utvid dette innlegget",
|
||||
"status.pin": "Fest på profilen",
|
||||
"status.quote_error.filtered": "Skjult på grunn av et av filterne dine",
|
||||
"status.quote_error.not_found": "Dette innlegget kan ikke vises.",
|
||||
"status.quote_error.pending_approval": "Dette innlegget venter på godkjenning fra den opprinnelige forfatteren.",
|
||||
"status.quote_error.rejected": "Dette innlegget kan ikke vises fordi den opprinnelige forfatteren ikke har tillatt at det blir sitert.",
|
||||
"status.read_more": "Les mer",
|
||||
"status.reblog": "Fremhev",
|
||||
"status.reblog_private": "Fremhev til det opprinnelige publikummet",
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
{
|
||||
"about.blocks": "Servidores moderados",
|
||||
"about.contact": "Contacto:",
|
||||
"about.disclaimer": "O Mastodon é um software livre, de código aberto e uma marca registada de Mastodon gGmbH.",
|
||||
"about.default_locale": "Padrão",
|
||||
"about.disclaimer": "O Mastodon é um ‘software’ livre, de código aberto e uma marca registada de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Motivo não disponível",
|
||||
"about.domain_blocks.preamble": "O Mastodon geralmente permite ver e interagir com o conteúdo de utilizadores de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico.",
|
||||
"about.domain_blocks.preamble": "O Mastodon ver e interagir com o conteúdo de utilizadores de qualquer outra instância no fediverso. Estas são as exceções desta instância em específico.",
|
||||
"about.domain_blocks.silenced.explanation": "Normalmente não verás perfis e conteúdos deste servidor, a não ser que os procures explicitamente ou optes por segui-los.",
|
||||
"about.domain_blocks.silenced.title": "Limitados",
|
||||
"about.domain_blocks.suspended.explanation": "Nenhum dado deste servidor será processado, armazenado ou trocado, tornando impossível qualquer interação ou comunicação com os utilizadores a partir deste servidor.",
|
||||
"about.domain_blocks.suspended.title": "Suspensos",
|
||||
"about.language_label": "Idioma",
|
||||
"about.not_available": "Esta informação não foi disponibilizada neste servidor.",
|
||||
"about.powered_by": "Rede social descentralizada baseada no {mastodon}",
|
||||
"about.rules": "Regras do servidor",
|
||||
|
@ -130,8 +132,8 @@
|
|||
"block_modal.remote_users_caveat": "Vamos pedir ao servidor {domain} para respeitar a tua decisão. No entanto, não é garantido o seu cumprimento, uma vez que alguns servidores podem tratar os bloqueios de forma diferente. As publicações públicas podem continuar a ser visíveis para utilizadores não autenticados.",
|
||||
"block_modal.show_less": "Mostrar menos",
|
||||
"block_modal.show_more": "Mostrar mais",
|
||||
"block_modal.they_cant_mention": "Ele não te pode mencionar nem seguir.",
|
||||
"block_modal.they_cant_see_posts": "Não verás as publicações dele e ele não poderá ver as tuas publicações.",
|
||||
"block_modal.they_cant_mention": "Ele não o pode mencionar nem seguir.",
|
||||
"block_modal.they_cant_see_posts": "Eles não podem ver as suas publicações e você não verá as deles.",
|
||||
"block_modal.they_will_know": "Ele pode ver que o bloqueaste.",
|
||||
"block_modal.title": "Bloquear utilizador?",
|
||||
"block_modal.you_wont_see_mentions": "Não verás publicações que mencionem este utilizador.",
|
||||
|
@ -308,6 +310,8 @@
|
|||
"emoji_button.search_results": "Resultados da pesquisa",
|
||||
"emoji_button.symbols": "Símbolos",
|
||||
"emoji_button.travel": "Viagens e lugares",
|
||||
"empty_column.account_featured.me": "Ainda não colocou nada em destaque. Sabia que pode destacar as etiquetas que mais utiliza e até as contas dos seus amigos no seu perfil?",
|
||||
"empty_column.account_featured.other": "{acct} ainda não colocou nada em destaque. Sabia que pode destacar as etiquetas que mais utiliza e até as contas dos seus amigos no seu perfil?",
|
||||
"empty_column.account_featured_other.unknown": "Esta conta ainda não colocou nada em destaque.",
|
||||
"empty_column.account_hides_collections": "Este utilizador escolheu não disponibilizar esta informação",
|
||||
"empty_column.account_suspended": "Conta suspensa",
|
||||
|
@ -341,6 +345,11 @@
|
|||
"explore.trending_links": "Notícias",
|
||||
"explore.trending_statuses": "Publicações",
|
||||
"explore.trending_tags": "#Etiquetas",
|
||||
"featured_carousel.header": "{count, plural, one {Publicação Afixada} other {Publicações Afixadas}}",
|
||||
"featured_carousel.next": "Seguinte",
|
||||
"featured_carousel.post": "Publicação",
|
||||
"featured_carousel.previous": "Anterior",
|
||||
"featured_carousel.slide": "{index} de {total}",
|
||||
"filter_modal.added.context_mismatch_explanation": "Esta categoria de filtro não se aplica ao contexto em que acedeste a esta publicação. Se pretenderes que esta publicação seja filtrada também neste contexto, terás que editar o filtro.",
|
||||
"filter_modal.added.context_mismatch_title": "O contexto não coincide!",
|
||||
"filter_modal.added.expired_explanation": "Esta categoria de filtro expirou, tens de alterar a data de validade para que ele seja aplicado.",
|
||||
|
@ -421,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Ver mais publicações em {domain}",
|
||||
"hints.threads.replies_may_be_missing": "É possível que não estejam a ser mostradas todas as respostas de outros servidores.",
|
||||
"hints.threads.see_more": "Ver mais respostas em {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citações",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respostas",
|
||||
"home.hide_announcements": "Ocultar mensagens de manutenção",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"about.domain_blocks.silenced.title": "Obmedzený",
|
||||
"about.domain_blocks.suspended.explanation": "Žiadne údaje z tohto servera nebudú spracovávané, ukladané ani vymieňané, čo znemožní akúkoľvek interakciu alebo komunikáciu s používateľmi z tohto servera.",
|
||||
"about.domain_blocks.suspended.title": "Vylúčený",
|
||||
"about.language_label": "Jazyk",
|
||||
"about.not_available": "Tieto informácie neboli sprístupnené na tomto serveri.",
|
||||
"about.powered_by": "Decentralizovaná sociálna sieť na základe technológie {mastodon}",
|
||||
"about.rules": "Pravidlá servera",
|
||||
|
@ -53,6 +54,7 @@
|
|||
"account.mute_notifications_short": "Stíšiť upozornenia",
|
||||
"account.mute_short": "Stíšiť",
|
||||
"account.muted": "Účet stíšený",
|
||||
"account.mutual": "Nasledujete sa navzájom",
|
||||
"account.no_bio": "Nie je uvedený žiadny popis.",
|
||||
"account.open_original_page": "Otvoriť pôvodnú stránku",
|
||||
"account.posts": "Príspevky",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"about.blocks": "Shërbyes të moderuar",
|
||||
"about.contact": "Kontakt:",
|
||||
"about.default_locale": "Parazgjedhje",
|
||||
"about.disclaimer": "Mastodon-i është software i lirë, me burim të hapët dhe shenjë tregtare e Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "S’ka arsye",
|
||||
"about.domain_blocks.preamble": "Mastodon-i ju lë përgjithësisht të shihni lëndë prej përdoruesish dhe të ndërveproni me ta nga cilido shërbyes tjetër qofshin në fedivers. Ka përjashtime që janë bërë në këtë shërbyes të dhënë.",
|
||||
|
@ -8,6 +9,7 @@
|
|||
"about.domain_blocks.silenced.title": "E kufizuar",
|
||||
"about.domain_blocks.suspended.explanation": "S’do të përpunohen, depozitohen apo shkëmbehen të dhëna të këtij shërbyesi, duke e bërë të pamundur çfarëdo ndërveprimi apo komunikimi me përdorues nga ky shërbyes.",
|
||||
"about.domain_blocks.suspended.title": "E pezulluar",
|
||||
"about.language_label": "Gjuhë",
|
||||
"about.not_available": "Ky informacion, në këtë shërbyes, nuk jepet.",
|
||||
"about.powered_by": "Media shoqërore e decentralizuar, bazuar në {mastodon}",
|
||||
"about.rules": "Rregulla shërbyesi",
|
||||
|
@ -423,6 +425,7 @@
|
|||
"hints.profiles.see_more_posts": "Shihni më tepër postime në {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Mund të mungojnë përgjigje nga shërbyes të tjerë.",
|
||||
"hints.threads.see_more": "Shihni më tepër përgjigje në {domain}",
|
||||
"home.column_settings.show_quotes": "Shfaq thonjëza",
|
||||
"home.column_settings.show_reblogs": "Shfaq përforcime",
|
||||
"home.column_settings.show_replies": "Shfaq përgjigje",
|
||||
"home.hide_announcements": "Fshihi lajmërimet",
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"about.domain_blocks.silenced.title": "ken lukin lili ",
|
||||
"about.domain_blocks.suspended.explanation": "sona ale pi ma ni li kama pali ala, li kama esun ala, li kama awen ala la sina ken ala toki tawa jan pi ma ni.",
|
||||
"about.domain_blocks.suspended.title": "weka",
|
||||
"about.language_label": "toki",
|
||||
"about.not_available": "lon kulupu ni la sina ken alasa ala e sona ni.",
|
||||
"about.powered_by": "lipu kulupu pi jan lawa mute tan {mastodon}",
|
||||
"about.rules": "lawa kulupu",
|
||||
|
@ -15,37 +16,47 @@
|
|||
"account.add_or_remove_from_list": "o ante e lipu jan",
|
||||
"account.badges.bot": "ilo nanpa li lawa e ni",
|
||||
"account.badges.group": "kulupu",
|
||||
"account.block": "o weka e @{name}",
|
||||
"account.block_domain": "o weka e ma {domain}",
|
||||
"account.block_short": "o weka e jan tawa mi",
|
||||
"account.blocked": "jan li weka tawa mi",
|
||||
"account.block": "o len e @{name}",
|
||||
"account.block_domain": "o len e ma {domain}",
|
||||
"account.block_short": "o len",
|
||||
"account.blocked": "jan li len",
|
||||
"account.blocking": "mi len e jan ni",
|
||||
"account.cancel_follow_request": "o kute ala",
|
||||
"account.copy": "o pali same e linja pi lipu jan",
|
||||
"account.direct": "len la o mu e @{name}",
|
||||
"account.disable_notifications": "@{name} li toki la o mu ala e mi",
|
||||
"account.domain_blocking": "mi len e ma ni",
|
||||
"account.edit_profile": "o ante e lipu mi",
|
||||
"account.enable_notifications": "@{name} li toki la o toki e toki ona tawa mi",
|
||||
"account.endorse": "lipu jan la o suli e ni",
|
||||
"account.featured_tags.last_status_at": "sitelen pini pi jan ni li lon tenpo {date}",
|
||||
"account.familiar_followers_many": "{name1} en {name2} en {othersCount, plural, other {jan ante #}} li kute e jan ni",
|
||||
"account.familiar_followers_one": "{name1} li kute e jan ni",
|
||||
"account.familiar_followers_two": "{name1} en {name2} li kute e jan ni",
|
||||
"account.featured": "suli",
|
||||
"account.featured.accounts": "lipu jan",
|
||||
"account.featured.hashtags": "kulupu lipu",
|
||||
"account.featured_tags.last_status_at": "sitelen pini pi jan ni li tan {date}",
|
||||
"account.featured_tags.last_status_never": "toki ala li lon",
|
||||
"account.follow": "o kute",
|
||||
"account.follow_back": "jan ni li kute e sina. o kute",
|
||||
"account.followers": "jan kute",
|
||||
"account.followers.empty": "jan ala li kute e jan ni",
|
||||
"account.followers_counter": "{count, plural, other {jan {counter} li kute e ona}}",
|
||||
"account.followers_you_know_counter": "jan {counter} pi kute sama",
|
||||
"account.following": "sina kute e jan ni",
|
||||
"account.following_counter": "{count, plural, other {ona li kute e jan {counter}}}",
|
||||
"account.follows.empty": "jan ni li kute e jan ala",
|
||||
"account.follows_you": "ona li kute e sina",
|
||||
"account.go_to_profile": "o tawa lipu jan",
|
||||
"account.hide_reblogs": "o lukin ala e pana toki tan @{name}",
|
||||
"account.in_memoriam": "jan ni li moli. pona o tawa ona.",
|
||||
"account.joined_short": "li kama",
|
||||
"account.joined_short": "ona li kama lon tenpo",
|
||||
"account.languages": "sina wile lukin e sitelen pi toki seme",
|
||||
"account.link_verified_on": "{date} la mi sona e ni: jan seme li jo e lipu ni",
|
||||
"account.locked_info": "sina wile kute e jan ni la ona o toki e ken",
|
||||
"account.media": "sitelen",
|
||||
"account.mention": "o toki e jan @{name}",
|
||||
"account.moved_to": "lipu jan sin pi jan {name} li ni:",
|
||||
"account.mention": "o mu e jan @{name}",
|
||||
"account.moved_to": "jan ni la lipu sin li ni:",
|
||||
"account.mute": "o len e @{name}",
|
||||
"account.mute_notifications_short": "o kute ala e mu tan jan ni",
|
||||
"account.mute_short": "o kute ala",
|
||||
|
@ -58,11 +69,12 @@
|
|||
"account.requested": "jan ni o ken e kute sina",
|
||||
"account.requested_follow": "jan {name} li wile kute e sina",
|
||||
"account.share": "o pana e lipu jan @{name}",
|
||||
"account.show_reblogs": "o lukin e pana toki tan @{name}",
|
||||
"account.show_reblogs": "o lukin e toki sike tan @{name}",
|
||||
"account.statuses_counter": "{count, plural, other {toki {counter}}}",
|
||||
"account.unblock": "o weka ala e jan {name}",
|
||||
"account.unblock_domain": "o weka ala e ma {domain}",
|
||||
"account.unblock_short": "o pini weka",
|
||||
"account.unblock": "o len ala e jan {name}",
|
||||
"account.unblock_domain": "o len ala e ma {domain}",
|
||||
"account.unblock_domain_short": "o len ala e jan ni",
|
||||
"account.unblock_short": "o len ala",
|
||||
"account.unendorse": "lipu jan la o suli ala e ni",
|
||||
"account.unfollow": "o kute ala",
|
||||
"account.unmute": "o len ala e @{name}",
|
||||
|
@ -72,7 +84,7 @@
|
|||
"admin.dashboard.daily_retention": "nanpa pi awen jan lon tenpo suno",
|
||||
"admin.dashboard.monthly_retention": "nanpa pi awen jan lon tenpo mun",
|
||||
"admin.dashboard.retention.average": "sama",
|
||||
"admin.dashboard.retention.cohort": "tenpo mun open",
|
||||
"admin.dashboard.retention.cohort": "kama sijelo la tenpo mun",
|
||||
"admin.dashboard.retention.cohort_size": "jan sin",
|
||||
"admin.impact_report.instance_accounts": "ni li pakala li weka e lipu jan ni",
|
||||
"admin.impact_report.instance_followers": "jan pi ma mi li weka tan jan kute ni",
|
||||
|
@ -82,14 +94,14 @@
|
|||
"alert.rate_limited.title": "ilo ni li lili e ken sina",
|
||||
"alert.unexpected.message": "pakala li lon",
|
||||
"alert.unexpected.title": "pakala a!",
|
||||
"alt_text_badge.title": "toki sona sitelen",
|
||||
"alt_text_badge.title": "toki pi sona lukin",
|
||||
"alt_text_modal.add_alt_text": "o pana e toki pi sona lukin",
|
||||
"alt_text_modal.add_text_from_image": "o kama jo e toki sitelen tan sitelen ni",
|
||||
"alt_text_modal.cancel": "weka",
|
||||
"alt_text_modal.add_text_from_image": "o pana e nimi tan sitelen ni",
|
||||
"alt_text_modal.cancel": "o weka",
|
||||
"alt_text_modal.change_thumbnail": "o ante e sitelen lili",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "jan li ken ala kute la o pana e toki pi sona kalama…",
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "jan li ken ala lukin la o pana e toki pi sona lukin…",
|
||||
"alt_text_modal.done": "pini",
|
||||
"alt_text_modal.done": "o pana",
|
||||
"announcement.announcement": "toki suli",
|
||||
"annual_report.summary.archetype.booster": "jan ni li alasa e pona",
|
||||
"annual_report.summary.archetype.lurker": "jan ni li lukin taso",
|
||||
|
@ -103,6 +115,8 @@
|
|||
"annual_report.summary.highlighted_post.by_reblogs": "toki pi sike nanpa wan",
|
||||
"annual_report.summary.highlighted_post.by_replies": "toki li jo e toki kama pi nanpa wan",
|
||||
"annual_report.summary.highlighted_post.possessive": "tan jan {name}",
|
||||
"annual_report.summary.most_used_app.most_used_app": "ilo pi kepeken suli",
|
||||
"annual_report.summary.most_used_hashtag.most_used_hashtag": "kulupu toki pi kepeken suli",
|
||||
"annual_report.summary.most_used_hashtag.none": "ala",
|
||||
"annual_report.summary.new_posts.new_posts": "toki suli sin",
|
||||
"annual_report.summary.percentile.text": "<topLabel>ni la sina nanpa sewi</topLabel><percentage></percentage><bottomLabel>pi jan ale lon {domain}.</bottomLabel>",
|
||||
|
@ -111,17 +125,17 @@
|
|||
"attachments_list.unprocessed": "(nasin open)",
|
||||
"audio.hide": "o len e kalama",
|
||||
"block_modal.remote_users_caveat": "mi pana e wile sina tawa ma {domain}. taso, o sona: ma li ken kepeken nasin len ante la pakala li ken lon. toki pi lukin ale la jan pi ma ala li ken lukin.",
|
||||
"block_modal.show_less": "o pana e lili",
|
||||
"block_modal.show_more": "o pana e mute",
|
||||
"block_modal.show_less": "o lili e toki",
|
||||
"block_modal.show_more": "o suli e toki",
|
||||
"block_modal.they_cant_mention": "ona li ken ala toki tawa sina li ken ala kute e sina.",
|
||||
"block_modal.they_cant_see_posts": "ona li ken ala lukin e toki sina. sina ken ala lukin e toki ona.",
|
||||
"block_modal.they_will_know": "ona li sona e ni: sina weka e lukin ona.",
|
||||
"block_modal.title": "o weka ala weka e jan",
|
||||
"block_modal.they_will_know": "ona li ken sona e ni: sina len e ona.",
|
||||
"block_modal.title": "o len ala len e jan?",
|
||||
"block_modal.you_wont_see_mentions": "jan li toki e nimi ona la sina lukin ala e toki ni.",
|
||||
"boost_modal.combo": "sina ken luka e nena {combo} tawa ni: sina wile ala luka e nena lon tenpo kama",
|
||||
"boost_modal.reblog": "o wawa ala wawa e toki?",
|
||||
"boost_modal.undo_reblog": "o weka ala weka e wawa toki?",
|
||||
"bundle_column_error.copy_stacktrace": "o awen e sona pakala lon ilo sina",
|
||||
"bundle_column_error.copy_stacktrace": "o jo e sona pakala lon ilo sina",
|
||||
"bundle_column_error.error.body": "ilo li ken ala pana e lipu ni. ni li ken tan pakala ilo.",
|
||||
"bundle_column_error.error.title": "pakala a!",
|
||||
"bundle_column_error.network.body": "mi lukin pana e lipu la, pakala li lon. ken la, pakala li tan ilo nanpa sina. ken la, pakala li tan ilo nanpa suli pi ma kulupu ni.",
|
||||
|
@ -129,7 +143,7 @@
|
|||
"bundle_column_error.retry": "o alasa sin",
|
||||
"bundle_column_error.return": "o tawa open",
|
||||
"bundle_column_error.routing.body": "ilo li sona ala e lipu wile. sina pana ala pana e nasin pona tawa lipu?",
|
||||
"bundle_column_error.routing.title": "pakala nanpa 404",
|
||||
"bundle_column_error.routing.title": "pakala #404",
|
||||
"bundle_modal_error.close": "o pini",
|
||||
"bundle_modal_error.message": "ilo li wile kama e ijo ni, taso pakala li lon.",
|
||||
"bundle_modal_error.retry": "o alasa sin",
|
||||
|
@ -139,13 +153,13 @@
|
|||
"closed_registrations_modal.preamble": "ilo Masoton li lon ilo wan ala. sina kepeken ma ante la sina ken lukin li ken kute e jan pi ma ni. sina wile la, sina ken pali e ma sin!",
|
||||
"closed_registrations_modal.title": "sina kama lon kulupu Masoton",
|
||||
"column.about": "sona",
|
||||
"column.blocks": "kulupu pi jan weka",
|
||||
"column.blocks": "jan len",
|
||||
"column.bookmarks": "awen toki",
|
||||
"column.community": "linja tenpo pi ma ni",
|
||||
"column.create_list": "o pali e kulupu",
|
||||
"column.direct": "mu len",
|
||||
"column.directory": "o lukin e jan",
|
||||
"column.domain_blocks": "ma pi wile ala lukin",
|
||||
"column.domain_blocks": "ma len",
|
||||
"column.edit_list": "o ante e kulupu",
|
||||
"column.favourites": "ijo pona",
|
||||
"column.firehose": "toki pi tenpo ni",
|
||||
|
@ -174,27 +188,27 @@
|
|||
"compose.published.body": "toki li pana.",
|
||||
"compose.published.open": "o lukin",
|
||||
"compose.saved.body": "ilo li awen e ijo pana sina.",
|
||||
"compose_form.direct_message_warning_learn_more": "o kama sona e ijo ante",
|
||||
"compose_form.direct_message_warning_learn_more": "o kama sona",
|
||||
"compose_form.encryption_warning": "toki li len ala lon ilo Masoton ꞏ o pana ala e sona suli len lon ilo Masoton",
|
||||
"compose_form.lock_disclaimer": "lipu sina li open, li {locked} ala. jan ale li ken kama kute e sina, li ken lukin e toki sama ni.",
|
||||
"compose_form.lock_disclaimer.lock": "pini",
|
||||
"compose_form.placeholder": "sina wile toki e seme?",
|
||||
"compose_form.poll.duration": "tenpo pana",
|
||||
"compose_form.poll.multiple": "pana mute",
|
||||
"compose_form.poll.multiple": "mute pana",
|
||||
"compose_form.poll.option_placeholder": "ken nanpa {number}",
|
||||
"compose_form.poll.single": "toki pi wan taso",
|
||||
"compose_form.poll.single": "ken pi wan taso",
|
||||
"compose_form.poll.switch_to_multiple": "o ante e nasin pana. pana mute o ken",
|
||||
"compose_form.poll.switch_to_single": "o ante e nasin pana. pana wan taso o lon",
|
||||
"compose_form.poll.type": "nasin",
|
||||
"compose_form.publish": "o toki",
|
||||
"compose_form.publish_form": "o open toki sin",
|
||||
"compose_form.publish_form": "o toki sin",
|
||||
"compose_form.reply": "o toki lon ijo ni",
|
||||
"compose_form.save_changes": "o sin e ni",
|
||||
"compose_form.save_changes": "o sin",
|
||||
"compose_form.spoiler.marked": "o weka e toki pi ijo ike ken",
|
||||
"compose_form.spoiler.unmarked": "o pali e toki pi ijo ike ken",
|
||||
"compose_form.spoiler_placeholder": "toki pi ijo ike ken (sina ken ala e ni)",
|
||||
"confirmation_modal.cancel": "o weka",
|
||||
"confirmations.block.confirm": "o weka",
|
||||
"confirmation_modal.cancel": "ala",
|
||||
"confirmations.block.confirm": "o len",
|
||||
"confirmations.delete.confirm": "o weka",
|
||||
"confirmations.delete.message": "sina wile ala wile weka e toki ni?",
|
||||
"confirmations.delete.title": "o weka ala weka e toki?",
|
||||
|
@ -211,46 +225,57 @@
|
|||
"confirmations.follow_to_list.title": "sina wile ala wile kute?",
|
||||
"confirmations.logout.confirm": "o weka",
|
||||
"confirmations.logout.message": "sina wile ala wile weka",
|
||||
"confirmations.logout.title": "o weka?",
|
||||
"confirmations.missing_alt_text.confirm": "pana e toki pi sona lukin",
|
||||
"confirmations.logout.title": "o weka ala weka?",
|
||||
"confirmations.missing_alt_text.confirm": "o pana e toki pi sona lukin",
|
||||
"confirmations.missing_alt_text.message": "toki ni la sitelen li lon. taso toki pi sona lukin li lon ala. toki pi sona lukin li pona tan ni: jan ale li ken sona e toki.",
|
||||
"confirmations.missing_alt_text.title": "o pana e toki pi sona lukin",
|
||||
"confirmations.missing_alt_text.secondary": "o pana a",
|
||||
"confirmations.missing_alt_text.title": "o pana ala pana e toki pi sona lukin?",
|
||||
"confirmations.mute.confirm": "o len",
|
||||
"confirmations.redraft.confirm": "o weka o pali sin e toki",
|
||||
"confirmations.redraft.message": "pali sin e toki ni la sina wile ala wile weka e ona? sina ni la suli pi toki ni en wawa pi toki ni li weka. kin la toki lon toki ni li jo e mama ala.",
|
||||
"confirmations.redraft.title": "ni li weka li pali sin e toki ni.",
|
||||
"confirmations.reply.confirm": "toki lon toki ni",
|
||||
"confirmations.reply.message": "sina toki lon toki ni la toki pali sina li weka. sina wile ala wile e ni?",
|
||||
"confirmations.remove_from_followers.confirm": "o kama kute ala e jan",
|
||||
"confirmations.remove_from_followers.message": "{name} li kama kute ala e sina. sina wile ala wile e ni?",
|
||||
"confirmations.remove_from_followers.title": "o kama ala kama kute ala e jan?",
|
||||
"confirmations.reply.confirm": "o weka",
|
||||
"confirmations.reply.message": "sina pana e toki tawa lipu ante la ni li weka e toki sina lon. sina wile ala wile weka e toki ni?",
|
||||
"confirmations.reply.title": "sina wile ala wile weka e toki lon?",
|
||||
"confirmations.unfollow.confirm": "o kute ala",
|
||||
"confirmations.unfollow.message": "sina o wile ala wile pini kute e jan {name}?",
|
||||
"confirmations.unfollow.title": "sina wile ala wile pini kute?",
|
||||
"content_warning.hide": "o len",
|
||||
"content_warning.show": "o lukin",
|
||||
"content_warning.show": "o lukin a",
|
||||
"content_warning.show_more": "o lukin",
|
||||
"conversation.delete": "o weka e toki ni",
|
||||
"conversation.mark_as_read": "ni o sin ala",
|
||||
"conversation.open": "o lukin e toki",
|
||||
"conversation.with": "lon {names}",
|
||||
"copy_icon_button.copied": "toki li awen lon ilo sina",
|
||||
"copy_icon_button.copied": "sina jo e toki",
|
||||
"copypaste.copied": "sina jo e toki",
|
||||
"copypaste.copy_to_clipboard": "o awen lon ilo sina",
|
||||
"copypaste.copy_to_clipboard": "o jo e toki",
|
||||
"directory.federated": "tan lipu ante sona",
|
||||
"directory.local": "tan {domain} taso",
|
||||
"directory.local": "tan ma {domain} taso",
|
||||
"directory.new_arrivals": "jan pi kama sin",
|
||||
"directory.recently_active": "jan lon tenpo poka",
|
||||
"disabled_account_banner.account_settings": "wile pi lipu jan",
|
||||
"disabled_account_banner.text": "sina ken ala kepeken e lipu jan sina pi nimi {disabledAccount}.",
|
||||
"dismissable_banner.community_timeline": "ni li toki pi tenpo poka tawa ale tan jan lon ma lawa pi nimi {domain}.",
|
||||
"disabled_account_banner.text": "sina ken ala lon sijelo {disabledAccount}.",
|
||||
"dismissable_banner.community_timeline": "ni li toki suli pi len ala lon ma {domain} tan tenpo poka.",
|
||||
"dismissable_banner.dismiss": "o weka",
|
||||
"dismissable_banner.explore_links": "tenpo suno ni la jan pi kulupu ale li toki e ijo sin ni. ijo sin pi jan ante mute li sewi lon lipu ni.",
|
||||
"dismissable_banner.explore_statuses": "jan mute li lukin e toki ni tan ma ilo weka. toki sin en toki pi wawa mute li lon sewi.",
|
||||
"domain_block_modal.block": "o weka e ma",
|
||||
"domain_block_modal.they_wont_know": "ona li sona ala e ni: sina weka e ona.",
|
||||
"domain_block_modal.title": "sina wile weka ala weka e ma?",
|
||||
"dismissable_banner.explore_links": "tenpo poka la jan pi kulupu ale li toki e ijo sin ni. ijo sin pi jan ante mute li sewi lon lipu ni.",
|
||||
"dismissable_banner.explore_statuses": "tenpo poka la jan pi kulupu ale li toki e ijo ni. ijo sin pi jan ante mute li sewi lon lipu ni.",
|
||||
"dismissable_banner.explore_tags": "tenpo poka la jan pi kulupu ale li toki e ijo ni. ijo sin pi jan ante mute li sewi lon lipu ni.",
|
||||
"dismissable_banner.public_timeline": "toki ni li sin. jan li pali e toki ni la jan ante mute pi ma {domain} li kute e jan ni.",
|
||||
"domain_block_modal.block": "o len e ma",
|
||||
"domain_block_modal.block_account_instead": "o len e @{name} a",
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "jan pi ma ni li ken ijo e toki sina.",
|
||||
"domain_block_modal.they_cant_follow": "jan pi ma ni li ken ala kute e sina.",
|
||||
"domain_block_modal.they_wont_know": "ona li sona ala e ni: sina len e ona.",
|
||||
"domain_block_modal.title": "sina wile ala wile len e ma?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "{followersCount, plural, other {jan {followersCountDisplay}}} li kute e sina la, ona kama kute ala e sina. sina kute e {followingCount, plural,other {jan {followingCountDisplay}}} la, sina kama kute ala e ona.",
|
||||
"domain_block_modal.you_will_lose_relationships": "jan li lon kulupu ni la ona kute e sina la, ona li kama kute ala e sina. jan li lon kulupu ni la sina kute e ona la, sina kama kute ala e ona.",
|
||||
"domain_block_modal.you_wont_see_posts": "sina ken ala lukin e toki tan jan pi ma ni",
|
||||
"domain_pill.activitypub_lets_connect": "ilo ni la sina ken toki tawa jan ante. ni li lon ma Masoton taso ala li lon ma mute a.",
|
||||
"domain_pill.activitypub_like_language": "ilo Masoton li toki kepeken nasin ActivityPub tawa kulupu ilo ante.",
|
||||
"domain_pill.server": "ma",
|
||||
"domain_pill.their_handle": "nimi pi ona taso li ni:",
|
||||
"domain_pill.their_server": "ni li ma ona lon ilo. toki ale ona li lon ma ni.",
|
||||
|
@ -281,14 +306,14 @@
|
|||
"empty_column.account_suspended": "lipu ni li weka",
|
||||
"empty_column.account_timeline": "toki ala li lon!",
|
||||
"empty_column.account_unavailable": "ken ala lukin e lipu jan",
|
||||
"empty_column.blocks": "jan ala li weka tawa sina.",
|
||||
"empty_column.blocks": "sina len ala e jan.",
|
||||
"empty_column.direct": "jan ala li toki len e sina. jan li toki len e sina la sina ken lukin e ni lon ni.",
|
||||
"empty_column.domain_blocks": "ma ala li weka tawa sina.",
|
||||
"empty_column.domain_blocks": "sina len ala e ma.",
|
||||
"empty_column.favourited_statuses": "sina suli ala e toki. sina suli e toki la sina ken lukin e toki ni lon ni.",
|
||||
"empty_column.favourites": "jan ala li suli e toki ni. jan li suli e toki ni la sina ken lukin e ona lon ni.",
|
||||
"empty_column.follow_requests": "jan ala li toki pi wile kute tawa sina. jan li toki pi wile kute tawa sina la sina ken lukin e toki ni lon ni.",
|
||||
"empty_column.followed_tags": "sina alasa ala e toki ꞏ sina alasa e toki la toki li lon ni",
|
||||
"empty_column.hashtag": "ala li lon toki ni",
|
||||
"empty_column.followed_tags": "sina kute ala e kulupu lipu. sina kute la toki li kama lon ni.",
|
||||
"empty_column.hashtag": "toki ala li lon kulupu ni.",
|
||||
"empty_column.home": "ala a li lon lipu open sina! sina wile lon e ijo lon ni la o kute e jan pi toki suli.",
|
||||
"empty_column.list": "ala li lon kulupu lipu ni. jan pi kulupu lipu ni li toki sin la toki ni li lon ni.",
|
||||
"empty_column.mutes": "jan ala li len tawa sina.",
|
||||
|
@ -299,15 +324,21 @@
|
|||
"explore.trending_links": "sin",
|
||||
"explore.trending_statuses": "toki",
|
||||
"explore.trending_tags": "kulupu pi lipu suli",
|
||||
"featured_carousel.next": "kama",
|
||||
"featured_carousel.post": "toki",
|
||||
"featured_carousel.previous": "pini",
|
||||
"featured_carousel.slide": "lipu {total} la lipu nanpa {index}",
|
||||
"filter_modal.added.settings_link": "lipu lawa",
|
||||
"filter_modal.select_filter.expired": "tenpo pini",
|
||||
"filter_modal.select_filter.search": "o alasa anu pali",
|
||||
"filtered_notifications_banner.pending_requests": "ni li tan {count, plural, =0 {jan sina ala} other {jan sina #}}",
|
||||
"firehose.all": "ale",
|
||||
"firehose.local": "kulupu ni",
|
||||
"firehose.remote": "kulupu ante",
|
||||
"follow_request.authorize": "o ken",
|
||||
"follow_request.reject": "o ala",
|
||||
"follow_request.reject": "o weka kute",
|
||||
"follow_suggestions.dismiss": "mi wile lukin sin ala e ni",
|
||||
"follow_suggestions.friends_of_friends_longer": "ni li suli tawa kulupu jan ni: sina kute e ona",
|
||||
"follow_suggestions.hints.friends_of_friends": "jan kute sina li lukin mute e toki pi jan ni.",
|
||||
"follow_suggestions.hints.most_followed": "jan mute lon ma {domain} li kute e jan ni.",
|
||||
"follow_suggestions.hints.most_interactions": "tenpo poka la jan mute pi ma {domain} li lukin mute e toki pi jan ni.",
|
||||
|
@ -320,67 +351,103 @@
|
|||
"footer.privacy_policy": "lawa len",
|
||||
"footer.source_code": "o lukin e toki ilo",
|
||||
"footer.status": "lon",
|
||||
"generic.saved": "ni li awen",
|
||||
"footer.terms_of_service": "lipu lawa",
|
||||
"generic.saved": "mi awen e ni",
|
||||
"getting_started.heading": "mi open",
|
||||
"hashtag.admin_moderation": "o lawa e kulupu #{name}",
|
||||
"hashtag.browse": "o lukin e kulupu toki #{hashtag}",
|
||||
"hashtag.browse_from_account": "o lukin e kulupu toki #{hashtag} tan @{name}",
|
||||
"hashtag.column_header.tag_mode.all": "en {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "anu {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "en {additional} ala",
|
||||
"hashtag.column_settings.select.placeholder": "o alasa e kulupu…",
|
||||
"hashtag.column_settings.tag_mode.all": "ale ni",
|
||||
"hashtag.column_settings.tag_mode.any": "wan ni",
|
||||
"hashtag.column_settings.tag_mode.none": "ala ni",
|
||||
"hashtag.counter_by_accounts": "{count, plural, other {jan {counter}}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, other {toki {counter}}}",
|
||||
"hashtag.follow": "o kute e kulupu lipu",
|
||||
"hashtag.mute": "o kute ala e kulupu #{hashtag}",
|
||||
"hashtag.unfollow": "o kute ala e kulupu lipu",
|
||||
"home.column_settings.show_reblogs": "lukin e wawa",
|
||||
"hints.profiles.followers_may_be_missing": "jan kute li ken weka.",
|
||||
"hints.profiles.see_more_posts": "o lukin e toki ante lon ma {domain}",
|
||||
"hints.threads.see_more": "o lukin e toki ante lon ma {domain}",
|
||||
"home.column_settings.show_reblogs": "o lukin e wawa toki",
|
||||
"home.hide_announcements": "o lukin ala e toki lawa suli",
|
||||
"home.pending_critical_update.link": "o lukin e ijo ilo sin",
|
||||
"home.show_announcements": "o lukin e toki lawa suli",
|
||||
"info_button.label": "sona",
|
||||
"interaction_modal.go": "o tawa ma ni",
|
||||
"interaction_modal.go": "o tawa",
|
||||
"interaction_modal.no_account_yet": "sina jo ala e sijelo anu seme?",
|
||||
"interaction_modal.on_another_server": "lon ma ante",
|
||||
"interaction_modal.on_this_server": "lon ma ni",
|
||||
"interaction_modal.title.favourite": "o suli e toki {name}",
|
||||
"interaction_modal.title.favourite": "o pona tawa {name}",
|
||||
"interaction_modal.title.follow": "o kute e {name}",
|
||||
"interaction_modal.title.reblog": "o wawa e toki {name}",
|
||||
"interaction_modal.title.reply": "o toki lon toki pi jan {name}",
|
||||
"interaction_modal.title.vote": "o pana tawa wile sona pi jan {name}",
|
||||
"interaction_modal.title.reblog": "o pana wawa e toki tan {name}",
|
||||
"interaction_modal.title.reply": "o toki lon lipu tawa {name}",
|
||||
"interaction_modal.title.vote": "o pana e sona wile tawa {name}",
|
||||
"interaction_modal.username_prompt": "ni li sama ni: {example}",
|
||||
"intervals.full.days": "{number, plural, other {suni #}}",
|
||||
"intervals.full.hours": "{number, plural, other {tenpo suli #}}",
|
||||
"keyboard_shortcuts.blocked": "o lukin e lipu sina pi jan weka",
|
||||
"intervals.full.days": "ni li pini lon {number, plural, other {tenpo suno #}}",
|
||||
"intervals.full.hours": "ni li pini lon {number, plural, other {tenpo ilo #}}",
|
||||
"intervals.full.minutes": "ni li pini lon {number, plural, other {tenpo ilo lili #}}",
|
||||
"keyboard_shortcuts.back": "o tawa pini",
|
||||
"keyboard_shortcuts.blocked": "o lukin e lipu pi len sina",
|
||||
"keyboard_shortcuts.boost": "o pana sin e toki",
|
||||
"keyboard_shortcuts.down": "o tawa anpa lon lipu",
|
||||
"keyboard_shortcuts.enter": "o lukin e toki",
|
||||
"keyboard_shortcuts.favourite": "o sitelen pona e toki",
|
||||
"keyboard_shortcuts.favourites": "o lukin e lipu sina pi toki suli",
|
||||
"keyboard_shortcuts.federated": "o lukin e linja toki pi ma ale",
|
||||
"keyboard_shortcuts.muted": "o lukin e lipu sina pi jan len",
|
||||
"keyboard_shortcuts.my_profile": "o lukin e lipu sina",
|
||||
"keyboard_shortcuts.open_media": "o lukin e sitelen",
|
||||
"keyboard_shortcuts.pinned": "o lukin pi lipu sina pi toki sewi",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "o ante e ken lukin",
|
||||
"keyboard_shortcuts.toot": "o toki",
|
||||
"keyboard_shortcuts.reply": "o toki lon ijo ni",
|
||||
"keyboard_shortcuts.toggle_hidden": "o lukin ala lukin e toki len",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "o lukin ala lukin e sitelen",
|
||||
"keyboard_shortcuts.toot": "o toki sin",
|
||||
"keyboard_shortcuts.translate": "o ante e toki lipu",
|
||||
"keyboard_shortcuts.up": "o tawa sewi lon lipu",
|
||||
"lightbox.close": "o pini",
|
||||
"lightbox.next": "sinpin",
|
||||
"lightbox.previous": "monsi",
|
||||
"link_preview.author": "tan {name}",
|
||||
"link_preview.shares": "{count, plural, other {toki {counter}}}",
|
||||
"lists.add_member": "o pana",
|
||||
"lists.add_to_list": "o pana tawa kulupu jan",
|
||||
"lists.add_to_lists": "o pana e {name} tawa kulupu jan",
|
||||
"lists.create": "o lipu e kulupu jan",
|
||||
"lists.create_a_list_to_organize": "o lipu e kulupu tawa nasin pi ilo sina",
|
||||
"lists.create_list": "o lipu e kulupu jan",
|
||||
"lists.delete": "o weka e kulupu lipu",
|
||||
"lists.done": "ale li pini",
|
||||
"lists.edit": "o ante e kulupu lipu",
|
||||
"lists.find_users_to_add": "o alasa e jan",
|
||||
"lists.list_members_count": "{count, plural, other {jan #}}",
|
||||
"lists.list_name": "nimi kulupu",
|
||||
"lists.new_list_name": "nimi pi kulupu sin",
|
||||
"lists.no_lists_yet": "kulupu li lon ala.",
|
||||
"lists.no_members_yet": "jan ala li lon.",
|
||||
"lists.no_results_found": "jan ala li lon.",
|
||||
"lists.remove_member": "o weka",
|
||||
"lists.replies_policy.followed": "jan kute ale",
|
||||
"lists.replies_policy.list": "jan pi kulupu ni taso",
|
||||
"lists.replies_policy.none": "jan ala",
|
||||
"lists.save": "o awen",
|
||||
"lists.search": "o alasa",
|
||||
"load_pending": "{count, plural, other {ijo sin #}}",
|
||||
"loading_indicator.label": "ni li kama…",
|
||||
"media_gallery.hide": "o len",
|
||||
"moved_to_account_banner.text": "sina ante e sijelo tawa sijelo {movedToAccount}. ni la sijelo {disabledAccount} li pini.",
|
||||
"mute_modal.hide_from_notifications": "o len tan mu",
|
||||
"mute_modal.indefinite": "ni li pini ala",
|
||||
"mute_modal.title": "sina wile ala wile kute e jan ni?",
|
||||
"navigation_bar.about": "sona",
|
||||
"navigation_bar.blocks": "jan weka",
|
||||
"navigation_bar.blocks": "jan len",
|
||||
"navigation_bar.compose": "o pali e toki sin",
|
||||
"navigation_bar.domain_blocks": "kulupu pi ma weka",
|
||||
"navigation_bar.discover": "o alasa",
|
||||
"navigation_bar.domain_blocks": "ma len",
|
||||
"navigation_bar.explore": "o alasa",
|
||||
"navigation_bar.favourites": "ijo pona",
|
||||
"navigation_bar.filters": "nimi len",
|
||||
"navigation_bar.lists": "kulupu lipu",
|
||||
|
@ -395,11 +462,13 @@
|
|||
"notification.follow_request": "{name} li wile kute e sina",
|
||||
"notification.label.mention": "jan li toki e sina",
|
||||
"notification.label.private_mention": "jan li toki e sina lon len",
|
||||
"notification.label.private_reply": "Jan li toki tawa toki sina lon len",
|
||||
"notification.label.private_reply": "toki len",
|
||||
"notification.label.reply": "jan li toki tawa toki sina",
|
||||
"notification.mentioned_you": "jan {name} li toki e sina",
|
||||
"notification.moderation-warning.learn_more": "o kama sona e ijo ante",
|
||||
"notification.reblog": "{name} li wawa e toki sina",
|
||||
"notification.relationships_severance_event.domain_block": "ma {from} la jan lawa li len e ma {target}. ma ni la jan {followersCount} li kute e sina. sina kute e {followingCount, plural, other {jan #}} tan ma ni. kama la ona ale li len tawa sina.",
|
||||
"notification.relationships_severance_event.user_domain_block": "sina len e ma {target}. ma ni la jan {followersCount} li kute e sina. sina kute e {followingCount, plural, other {jan #}} tan ma ni. kama la ona ale li len tawa sina.",
|
||||
"notification.status": "{name} li toki",
|
||||
"notification.update": "{name} li ante e toki",
|
||||
"notification_requests.dismiss": "o weka",
|
||||
|
@ -432,8 +501,9 @@
|
|||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"relative_time.today": "tenpo suno ni",
|
||||
"reply_indicator.cancel": "o ala",
|
||||
"report.block": "o weka e jan",
|
||||
"reply_indicator.attachments": "{count, plural, other {sitelen #}}",
|
||||
"reply_indicator.cancel": "o pana ala",
|
||||
"report.block": "o len e jan",
|
||||
"report.block_explanation": "sina kama lukin ala e toki ona. ona li kama ala ken lukin e toki sina li kama ala ken kute e sina. ona li ken sona e kama ni.",
|
||||
"report.categories.other": "ante",
|
||||
"report.categories.spam": "ike tan toki mute",
|
||||
|
@ -465,8 +535,8 @@
|
|||
"search_results.see_all": "ale",
|
||||
"search_results.statuses": "toki",
|
||||
"server_banner.administered_by": "jan lawa:",
|
||||
"status.block": "o weka e @{name}",
|
||||
"status.cancel_reblog_private": "o pini e pana",
|
||||
"status.block": "o len e @{name}",
|
||||
"status.cancel_reblog_private": "o pana ala",
|
||||
"status.delete": "o weka",
|
||||
"status.edit": "o ante",
|
||||
"status.favourite": "o sitelen pona",
|
||||
|
@ -500,6 +570,7 @@
|
|||
"units.short.thousand": "{count}K",
|
||||
"upload_button.label": "o pana e sitelen anu kalama",
|
||||
"upload_error.limit": "ilo li ken ala e suli pi ijo ni.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "sina wile ala pana e sitelen. mi weka e sitelen.",
|
||||
"upload_form.edit": "o ante",
|
||||
"upload_progress.label": "ilo li kama jo e ijo sina...",
|
||||
"upload_progress.processing": "ilo li pali…",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "{domain} adresinde daha fazla gönderi gör",
|
||||
"hints.threads.replies_may_be_missing": "Diğer sunuculardan yanıtlar eksik olabilir.",
|
||||
"hints.threads.see_more": "{domain} adresinde daha fazla yanıt gör",
|
||||
"home.column_settings.show_quotes": "Alıntıları göster",
|
||||
"home.column_settings.show_reblogs": "Yeniden paylaşımları göster",
|
||||
"home.column_settings.show_replies": "Yanıtları göster",
|
||||
"home.hide_announcements": "Duyuruları gizle",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "Xem thêm tút ở {domain}",
|
||||
"hints.threads.replies_may_be_missing": "Lượt trả lời từ máy chủ khác có thể không đầy đủ.",
|
||||
"hints.threads.see_more": "Xem thêm ở {domain}",
|
||||
"home.column_settings.show_quotes": "Hiện các trích dẫn",
|
||||
"home.column_settings.show_reblogs": "Hiện những lượt đăng lại",
|
||||
"home.column_settings.show_replies": "Hiện những tút dạng trả lời",
|
||||
"home.hide_announcements": "Ẩn thông báo máy chủ",
|
||||
|
|
|
@ -341,6 +341,8 @@
|
|||
"explore.trending_links": "新闻",
|
||||
"explore.trending_statuses": "嘟文",
|
||||
"explore.trending_tags": "话题",
|
||||
"featured_carousel.next": "下一步",
|
||||
"featured_carousel.previous": "上一步",
|
||||
"filter_modal.added.context_mismatch_explanation": "这条过滤规则不适用于你当前访问此嘟文的场景。要在此场景下过滤嘟文,你必须编辑此过滤规则。",
|
||||
"filter_modal.added.context_mismatch_title": "场景不匹配!",
|
||||
"filter_modal.added.expired_explanation": "此过滤规则类别已过期,你需要修改到期日期才能应用。",
|
||||
|
@ -418,6 +420,7 @@
|
|||
"hints.profiles.see_more_posts": "在 {domain} 查看更多嘟文",
|
||||
"hints.threads.replies_may_be_missing": "来自其它实例的回复可能没有完全显示。",
|
||||
"hints.threads.see_more": "在 {domain} 查看更多回复",
|
||||
"home.column_settings.show_quotes": "显示引用",
|
||||
"home.column_settings.show_reblogs": "显示转嘟",
|
||||
"home.column_settings.show_replies": "显示回复",
|
||||
"home.hide_announcements": "隐藏公告",
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
"alt_text_modal.cancel": "取消",
|
||||
"alt_text_modal.done": "完成",
|
||||
"announcement.announcement": "公告",
|
||||
"annual_report.summary.thanks": "感謝您成為 Mastodon 的一份子!",
|
||||
"attachments_list.unprocessed": "(未處理)",
|
||||
"audio.hide": "隱藏音訊",
|
||||
"block_modal.remote_users_caveat": "我們會要求 {domain} 伺服器尊重你的決定。然而,由於部份伺服器可能以不同方式處理封鎖,因此無法保證一定會成功。公開帖文仍然有機會被未登入的使用者看見。",
|
||||
|
|
|
@ -430,6 +430,7 @@
|
|||
"hints.profiles.see_more_posts": "於 {domain} 檢視更多嘟文",
|
||||
"hints.threads.replies_may_be_missing": "來自其他站點之回覆或有缺失。",
|
||||
"hints.threads.see_more": "於 {domain} 檢視更多回覆",
|
||||
"home.column_settings.show_quotes": "顯示引用嘟文",
|
||||
"home.column_settings.show_reblogs": "顯示轉嘟",
|
||||
"home.column_settings.show_replies": "顯示回覆",
|
||||
"home.hide_announcements": "隱藏公告",
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { Globals } from '@react-spring/web';
|
||||
|
||||
import { setupBrowserNotifications } from 'mastodon/actions/notifications';
|
||||
import Mastodon from 'mastodon/containers/mastodon';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { me, reduceMotion } from 'mastodon/initial_state';
|
||||
import * as perf from 'mastodon/performance';
|
||||
import ready from 'mastodon/ready';
|
||||
import { store } from 'mastodon/store';
|
||||
|
@ -21,6 +23,12 @@ function main() {
|
|||
mountNode.getAttribute('data-props') ?? '{}',
|
||||
) as Record<string, unknown>;
|
||||
|
||||
if (reduceMotion) {
|
||||
Globals.assign({
|
||||
skipAnimation: true,
|
||||
});
|
||||
}
|
||||
|
||||
const root = createRoot(mountNode);
|
||||
root.render(<Mastodon {...props} />);
|
||||
store.dispatch(setupBrowserNotifications());
|
||||
|
|
|
@ -24,6 +24,7 @@ import { markersReducer } from './markers';
|
|||
import media_attachments from './media_attachments';
|
||||
import meta from './meta';
|
||||
import { modalReducer } from './modal';
|
||||
import { navigationReducer } from './navigation';
|
||||
import { notificationGroupsReducer } from './notification_groups';
|
||||
import { notificationPolicyReducer } from './notification_policy';
|
||||
import { notificationRequestsReducer } from './notification_requests';
|
||||
|
@ -84,6 +85,7 @@ const reducers = {
|
|||
reaction_deck,
|
||||
notificationPolicy: notificationPolicyReducer,
|
||||
notificationRequests: notificationRequestsReducer,
|
||||
navigation: navigationReducer,
|
||||
};
|
||||
|
||||
// We want the root state to be an ImmutableRecord, which is an object with a defined list of keys,
|
||||
|
|
28
app/javascript/mastodon/reducers/navigation.ts
Normal file
28
app/javascript/mastodon/reducers/navigation.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { createReducer } from '@reduxjs/toolkit';
|
||||
|
||||
import {
|
||||
openNavigation,
|
||||
closeNavigation,
|
||||
toggleNavigation,
|
||||
} from 'mastodon/actions/navigation';
|
||||
|
||||
interface State {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
const initialState: State = {
|
||||
open: false,
|
||||
};
|
||||
|
||||
export const navigationReducer = createReducer(initialState, (builder) => {
|
||||
builder
|
||||
.addCase(openNavigation, (state) => {
|
||||
state.open = true;
|
||||
})
|
||||
.addCase(closeNavigation, (state) => {
|
||||
state.open = false;
|
||||
})
|
||||
.addCase(toggleNavigation, (state) => {
|
||||
state.open = !state.open;
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue