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

Merge upstream changes up to 520974e05211e988b0447f7f29e88798b1794bcf
This commit is contained in:
Claire 2025-06-05 19:34:17 +02:00 committed by GitHub
commit ad8f984cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 678 additions and 281 deletions

View File

@ -1,3 +1,6 @@
--- ---
Naming/BlockForwarding: Naming/BlockForwarding:
EnforcedStyle: explicit EnforcedStyle: explicit
Naming/PredicateMethod:
Enabled: false

View File

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.75.8. # using RuboCop version 1.76.0.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -33,7 +33,6 @@ Style/FetchEnvVar:
Exclude: Exclude:
- 'config/initializers/2_limited_federation_mode.rb' - 'config/initializers/2_limited_federation_mode.rb'
- 'config/initializers/paperclip.rb' - 'config/initializers/paperclip.rb'
- 'lib/tasks/repo.rake'
# This cop supports safe autocorrection (--autocorrect). # This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals.

View File

@ -110,7 +110,7 @@ group :opentelemetry do
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.22.0', require: false
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.22.0', require: false
gem 'opentelemetry-instrumentation-excon', '~> 0.23.0', require: false gem 'opentelemetry-instrumentation-excon', '~> 0.23.0', require: false
gem 'opentelemetry-instrumentation-faraday', '~> 0.26.0', require: false gem 'opentelemetry-instrumentation-faraday', '~> 0.27.0', require: false
gem 'opentelemetry-instrumentation-http', '~> 0.24.0', require: false gem 'opentelemetry-instrumentation-http', '~> 0.24.0', require: false
gem 'opentelemetry-instrumentation-http_client', '~> 0.23.0', require: false gem 'opentelemetry-instrumentation-http_client', '~> 0.23.0', require: false
gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false gem 'opentelemetry-instrumentation-net_http', '~> 0.23.0', require: false

View File

@ -90,7 +90,7 @@ GEM
public_suffix (>= 2.0.2, < 7.0) public_suffix (>= 2.0.2, < 7.0)
aes_key_wrap (1.1.0) aes_key_wrap (1.1.0)
android_key_attestation (0.3.0) android_key_attestation (0.3.0)
annotaterb (4.14.0) annotaterb (4.15.0)
ast (2.4.3) ast (2.4.3)
attr_required (1.0.2) attr_required (1.0.2)
aws-eventstream (1.3.2) aws-eventstream (1.3.2)
@ -544,7 +544,7 @@ GEM
opentelemetry-instrumentation-excon (0.23.0) opentelemetry-instrumentation-excon (0.23.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-faraday (0.26.0) opentelemetry-instrumentation-faraday (0.27.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.23.0) opentelemetry-instrumentation-base (~> 0.23.0)
opentelemetry-instrumentation-http (0.24.0) opentelemetry-instrumentation-http (0.24.0)
@ -751,7 +751,7 @@ GEM
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 9) sidekiq (>= 5, < 9)
rspec-support (3.13.3) rspec-support (3.13.3)
rubocop (1.75.8) rubocop (1.76.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0) lint_roller (~> 1.1.0)
@ -759,10 +759,10 @@ GEM
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0) rubocop-ast (>= 1.45.0, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.1) rubocop-ast (1.45.0)
parser (>= 3.3.7.2) parser (>= 3.3.7.2)
prism (~> 1.4) prism (~> 1.4)
rubocop-capybara (2.22.1) rubocop-capybara (2.22.1)
@ -1026,7 +1026,7 @@ DEPENDENCIES
opentelemetry-instrumentation-active_model_serializers (~> 0.22.0) opentelemetry-instrumentation-active_model_serializers (~> 0.22.0)
opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0) opentelemetry-instrumentation-concurrent_ruby (~> 0.22.0)
opentelemetry-instrumentation-excon (~> 0.23.0) opentelemetry-instrumentation-excon (~> 0.23.0)
opentelemetry-instrumentation-faraday (~> 0.26.0) opentelemetry-instrumentation-faraday (~> 0.27.0)
opentelemetry-instrumentation-http (~> 0.24.0) opentelemetry-instrumentation-http (~> 0.24.0)
opentelemetry-instrumentation-http_client (~> 0.23.0) opentelemetry-instrumentation-http_client (~> 0.23.0)
opentelemetry-instrumentation-net_http (~> 0.23.0) opentelemetry-instrumentation-net_http (~> 0.23.0)

View File

@ -138,7 +138,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
set_locale { render :rules } set_locale { render :rules }
end end
def is_flashing_format? # rubocop:disable Naming/PredicateName def is_flashing_format? # rubocop:disable Naming/PredicatePrefix
if params[:action] == 'create' if params[:action] == 'create'
false # Disable flash messages for sign-up false # Disable flash messages for sign-up
else else

View File

@ -2,6 +2,7 @@ import { browserHistory } from 'flavours/glitch/components/router';
import { debounceWithDispatchAndArguments } from 'flavours/glitch/utils/debounce'; import { debounceWithDispatchAndArguments } from 'flavours/glitch/utils/debounce';
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { me } from '../initial_state';
import { import {
followAccountSuccess, unfollowAccountSuccess, followAccountSuccess, unfollowAccountSuccess,
@ -12,6 +13,7 @@ import {
blockAccountSuccess, unblockAccountSuccess, blockAccountSuccess, unblockAccountSuccess,
pinAccountSuccess, unpinAccountSuccess, pinAccountSuccess, unpinAccountSuccess,
fetchRelationshipsSuccess, fetchRelationshipsSuccess,
fetchEndorsedAccounts,
} from './accounts_typed'; } from './accounts_typed';
import { importFetchedAccount, importFetchedAccounts } from './importer'; import { importFetchedAccount, importFetchedAccounts } from './importer';
@ -634,6 +636,7 @@ export function pinAccount(id) {
api().post(`/api/v1/accounts/${id}/pin`).then(response => { api().post(`/api/v1/accounts/${id}/pin`).then(response => {
dispatch(pinAccountSuccess({ relationship: response.data })); dispatch(pinAccountSuccess({ relationship: response.data }));
dispatch(fetchEndorsedAccounts({ accountId: me }));
}).catch(error => { }).catch(error => {
dispatch(pinAccountFail(error)); dispatch(pinAccountFail(error));
}); });
@ -646,6 +649,7 @@ export function unpinAccount(id) {
api().post(`/api/v1/accounts/${id}/unpin`).then(response => { api().post(`/api/v1/accounts/${id}/unpin`).then(response => {
dispatch(unpinAccountSuccess({ relationship: response.data })); dispatch(unpinAccountSuccess({ relationship: response.data }));
dispatch(fetchEndorsedAccounts({ accountId: me }));
}).catch(error => { }).catch(error => {
dispatch(unpinAccountFail(error)); dispatch(unpinAccountFail(error));
}); });

View File

@ -11,6 +11,8 @@ import {
muteAccount, muteAccount,
unmuteAccount, unmuteAccount,
followAccountSuccess, followAccountSuccess,
unpinAccount,
pinAccount,
} from 'flavours/glitch/actions/accounts'; } from 'flavours/glitch/actions/accounts';
import { showAlertForError } from 'flavours/glitch/actions/alerts'; import { showAlertForError } from 'flavours/glitch/actions/alerts';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
@ -63,14 +65,23 @@ const messages = defineMessages({
}, },
}); });
export const Account: React.FC<{ interface AccountProps {
size?: number; size?: number;
id: string; id: string;
hidden?: boolean; hidden?: boolean;
minimal?: boolean; minimal?: boolean;
defaultAction?: 'block' | 'mute'; defaultAction?: 'block' | 'mute';
withBio?: boolean; withBio?: boolean;
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => { }
export const Account: React.FC<AccountProps> = ({
id,
size = 46,
hidden,
minimal,
defaultAction,
withBio,
}) => {
const intl = useIntl(); const intl = useIntl();
const { signedIn } = useIdentity(); const { signedIn } = useIdentity();
const account = useAppSelector((state) => state.accounts.get(id)); const account = useAppSelector((state) => state.accounts.get(id));
@ -120,8 +131,6 @@ export const Account: React.FC<{
}, },
]; ];
} else if (defaultAction !== 'block') { } else if (defaultAction !== 'block') {
arr = [];
if (isRemote && accountUrl) { if (isRemote && accountUrl) {
arr.push({ arr.push({
text: intl.formatMessage(messages.openOriginalPage), text: intl.formatMessage(messages.openOriginalPage),
@ -174,6 +183,25 @@ export const Account: React.FC<{
text: intl.formatMessage(messages.addToLists), text: intl.formatMessage(messages.addToLists),
action: handleAddToLists, 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,
});
}
} }
} }

View File

@ -8,6 +8,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize'; import Textarea from 'react-textarea-autosize';
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
const messages = defineMessages({ const messages = defineMessages({
placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' }, placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' },
}); });
@ -153,17 +155,23 @@ class AccountNote extends ImmutablePureComponent {
<FormattedMessage id='account.account_note_header' defaultMessage='Personal note' /> <InlineAlert show={saved} /> <FormattedMessage id='account.account_note_header' defaultMessage='Personal note' /> <InlineAlert show={saved} />
</label> </label>
<Textarea {this.props.value === undefined ? (
id={`account-note-${accountId}`} <div className='account__header__account-note__loading-indicator-wrapper'>
className='account__header__account-note__content' <LoadingIndicator />
disabled={this.props.value === null || value === null} </div>
placeholder={intl.formatMessage(messages.placeholder)} ) : (
value={value || ''} <Textarea
onChange={this.handleChange} id={`account-note-${accountId}`}
onKeyDown={this.handleKeyDown} className='account__header__account-note__content'
onBlur={this.handleBlur} disabled={value === null}
ref={this.setTextareaRef} placeholder={intl.formatMessage(messages.placeholder)}
/> value={value || ''}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onBlur={this.handleBlur}
ref={this.setTextareaRef}
/>
)}
</div> </div>
); );
} }

View File

@ -17,6 +17,7 @@ import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { SpoilerButton } from 'flavours/glitch/components/spoiler_button'; import { SpoilerButton } from 'flavours/glitch/components/spoiler_button';
import { formatTime, getPointerPosition } from 'flavours/glitch/features/video'; import { formatTime, getPointerPosition } from 'flavours/glitch/features/video';
import { useAudioContext } from 'flavours/glitch/hooks/useAudioContext';
import { useAudioVisualizer } from 'flavours/glitch/hooks/useAudioVisualizer'; import { useAudioVisualizer } from 'flavours/glitch/hooks/useAudioVisualizer';
import { import {
displayMedia, displayMedia,
@ -119,12 +120,17 @@ export const Audio: React.FC<{
const seekRef = useRef<HTMLDivElement>(null); const seekRef = useRef<HTMLDivElement>(null);
const volumeRef = useRef<HTMLDivElement>(null); const volumeRef = useRef<HTMLDivElement>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(); const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
const [resumeAudio, suspendAudio, frequencyBands] = useAudioVisualizer(
audioRef,
3,
);
const accessibilityId = useId(); const accessibilityId = useId();
const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } =
useAudioContext({ audioElementRef: audioRef });
const frequencyBands = useAudioVisualizer({
audioContextRef,
sourceRef,
numBands: 3,
});
const [style, spring] = useSpring(() => ({ const [style, spring] = useSpring(() => ({
progress: '0%', progress: '0%',
buffer: '0%', buffer: '0%',
@ -152,6 +158,9 @@ export const Audio: React.FC<{
restoreVolume(audioRef.current); restoreVolume(audioRef.current);
setVolume(audioRef.current.volume); setVolume(audioRef.current.volume);
setMuted(audioRef.current.muted); setMuted(audioRef.current.muted);
if (gainNodeRef.current) {
gainNodeRef.current.gain.value = audioRef.current.volume;
}
void spring.start({ void spring.start({
volume: `${audioRef.current.volume * 100}%`, volume: `${audioRef.current.volume * 100}%`,
immediate: reduceMotion, immediate: reduceMotion,
@ -159,15 +168,14 @@ export const Audio: React.FC<{
} }
}, },
[ [
spring, deployPictureInPicture,
setVolume,
setMuted,
src, src,
poster, poster,
backgroundColor, backgroundColor,
accentColor,
foregroundColor, foregroundColor,
deployPictureInPicture, accentColor,
gainNodeRef,
spring,
], ],
); );
@ -178,7 +186,11 @@ export const Audio: React.FC<{
audioRef.current.volume = volume; audioRef.current.volume = volume;
audioRef.current.muted = muted; audioRef.current.muted = muted;
}, [volume, muted]);
if (gainNodeRef.current) {
gainNodeRef.current.gain.value = muted ? 0 : volume;
}
}, [volume, muted, gainNodeRef]);
useEffect(() => { useEffect(() => {
if (typeof visible !== 'undefined') { if (typeof visible !== 'undefined') {
@ -192,11 +204,10 @@ export const Audio: React.FC<{
}, [visible, sensitive]); }, [visible, sensitive]);
useEffect(() => { useEffect(() => {
if (!revealed && audioRef.current) { if (!revealed) {
audioRef.current.pause(); pauseAudio();
suspendAudio();
} }
}, [suspendAudio, revealed]); }, [pauseAudio, revealed]);
useEffect(() => { useEffect(() => {
let nextFrame: ReturnType<typeof requestAnimationFrame>; let nextFrame: ReturnType<typeof requestAnimationFrame>;
@ -228,13 +239,11 @@ export const Audio: React.FC<{
} }
if (audioRef.current.paused) { if (audioRef.current.paused) {
resumeAudio(); playAudio();
void audioRef.current.play();
} else { } else {
audioRef.current.pause(); pauseAudio();
suspendAudio();
} }
}, [resumeAudio, suspendAudio]); }, [playAudio, pauseAudio]);
const handlePlay = useCallback(() => { const handlePlay = useCallback(() => {
setPaused(false); setPaused(false);
@ -349,8 +358,7 @@ export const Audio: React.FC<{
document.removeEventListener('mouseup', handleSeekMouseUp, true); document.removeEventListener('mouseup', handleSeekMouseUp, true);
setDragging(false); setDragging(false);
resumeAudio(); playAudio();
void audioRef.current?.play();
}; };
const handleSeekMouseMove = (e: MouseEvent) => { const handleSeekMouseMove = (e: MouseEvent) => {
@ -377,7 +385,7 @@ export const Audio: React.FC<{
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}, },
[setDragging, spring, resumeAudio], [playAudio, spring],
); );
const handleMouseEnter = useCallback(() => { const handleMouseEnter = useCallback(() => {
@ -446,10 +454,9 @@ export const Audio: React.FC<{
const handleCanPlayThrough = useCallback(() => { const handleCanPlayThrough = useCallback(() => {
if (startPlaying) { if (startPlaying) {
resumeAudio(); playAudio();
void audioRef.current?.play();
} }
}, [startPlaying, resumeAudio]); }, [startPlaying, playAudio]);
const seekBy = (time: number) => { const seekBy = (time: number) => {
if (!audioRef.current) { if (!audioRef.current) {
@ -492,7 +499,7 @@ export const Audio: React.FC<{
return; return;
} }
const newVolume = audioRef.current.volume + step; const newVolume = Math.max(0, audioRef.current.volume + step);
if (!isNaN(newVolume)) { if (!isNaN(newVolume)) {
audioRef.current.volume = newVolume; audioRef.current.volume = newVolume;

View File

@ -51,6 +51,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 <SettingToggle
prefix='home_timeline' prefix='home_timeline'
settings={settings} settings={settings}

View File

@ -31,28 +31,31 @@ const makeGetStatusIds = (pending = false) => createSelector([
if (id === null || id === 'inline-follow-suggestions') return true; if (id === null || id === 'inline-follow-suggestions') return true;
const statusForId = statuses.get(id); const statusForId = statuses.get(id);
let showStatus = true;
if (statusForId.get('account') === me) return true; if (statusForId.get('account') === me) return true;
if (columnSettings.getIn(['shows', 'reblog']) === false) { if (columnSettings.getIn(['shows', 'reblog']) === false && statusForId.get('reblog') !== null) {
showStatus = showStatus && statusForId.get('reblog') === null; return false;
} }
if (columnSettings.getIn(['shows', 'reply']) === false) { if (columnSettings.getIn(['shows', 'reply']) === false && statusForId.get('in_reply_to_id') !== null && statusForId.get('in_reply_to_account_id') !== me) {
showStatus = showStatus && (statusForId.get('in_reply_to_id') === null || statusForId.get('in_reply_to_account_id') === me); return false;
} }
if (columnSettings.getIn(['shows', 'direct']) === false) { if (columnSettings.getIn(['shows', 'quote']) === false && statusForId.get('quote') !== null) {
showStatus = showStatus && statusForId.get('visibility') !== 'direct'; return false;
} }
if (showStatus && regex) { if (columnSettings.getIn(['shows', 'direct']) === false && statusForId.get('visibility') === 'direct') {
const searchIndex = statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'search_index']) : statusForId.get('search_index'); return false;
showStatus = !regex.test(searchIndex);
} }
return showStatus; const searchIndex = statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'search_index']) : statusForId.get('search_index');
if (regex && !regex.test(searchIndex)) {
return false;
}
return true;
}); });
}); });

View 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,
};
};

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef } from 'react';
const normalizeFrequencies = (arr: Float32Array): number[] => { const normalizeFrequencies = (arr: Float32Array): number[] => {
return new Array(...arr).map((value: number) => { return new Array(...arr).map((value: number) => {
@ -10,12 +10,17 @@ const normalizeFrequencies = (arr: Float32Array): number[] => {
}); });
}; };
export const useAudioVisualizer = ( interface AudioVisualiserOptions {
ref: React.MutableRefObject<HTMLAudioElement | null>, audioContextRef: React.MutableRefObject<AudioContext | undefined>;
numBands: number, sourceRef: React.MutableRefObject<MediaElementAudioSourceNode | undefined>;
) => { numBands: number;
const audioContextRef = useRef<AudioContext>(); }
const sourceRef = useRef<MediaElementAudioSourceNode>();
export const useAudioVisualizer = ({
audioContextRef,
sourceRef,
numBands,
}: AudioVisualiserOptions) => {
const analyzerRef = useRef<AnalyserNode>(); const analyzerRef = useRef<AnalyserNode>();
const [frequencyBands, setFrequencyBands] = useState<number[]>( const [frequencyBands, setFrequencyBands] = useState<number[]>(
@ -23,47 +28,31 @@ export const useAudioVisualizer = (
); );
useEffect(() => { useEffect(() => {
if (!audioContextRef.current) { if (audioContextRef.current) {
audioContextRef.current = new AudioContext();
analyzerRef.current = audioContextRef.current.createAnalyser(); analyzerRef.current = audioContextRef.current.createAnalyser();
analyzerRef.current.smoothingTimeConstant = 0.6; analyzerRef.current.smoothingTimeConstant = 0.6;
analyzerRef.current.fftSize = 2048; analyzerRef.current.fftSize = 2048;
} }
}, [audioContextRef]);
return () => {
if (audioContextRef.current) {
void audioContextRef.current.close();
}
};
}, []);
useEffect(() => { useEffect(() => {
if ( if (analyzerRef.current && sourceRef.current) {
audioContextRef.current &&
analyzerRef.current &&
!sourceRef.current &&
ref.current
) {
sourceRef.current = audioContextRef.current.createMediaElementSource(
ref.current,
);
sourceRef.current.connect(analyzerRef.current); sourceRef.current.connect(analyzerRef.current);
sourceRef.current.connect(audioContextRef.current.destination);
} }
const currentSource = sourceRef.current;
return () => { return () => {
if (sourceRef.current) { if (currentSource && analyzerRef.current) {
sourceRef.current.disconnect(); currentSource.disconnect(analyzerRef.current);
} }
}; };
}, [ref]); }, [audioContextRef, sourceRef]);
useEffect(() => { useEffect(() => {
const source = sourceRef.current;
const analyzer = analyzerRef.current; const analyzer = analyzerRef.current;
const context = audioContextRef.current; const context = audioContextRef.current;
if (!source || !analyzer || !context) { if (!analyzer || !context) {
return; return;
} }
@ -94,19 +83,7 @@ export const useAudioVisualizer = (
return () => { return () => {
clearInterval(updateInterval); clearInterval(updateInterval);
}; };
}, [numBands]); }, [numBands, audioContextRef]);
const resume = useCallback(() => { return frequencyBands;
if (audioContextRef.current) {
void audioContextRef.current.resume();
}
}, []);
const suspend = useCallback(() => {
if (audioContextRef.current) {
void audioContextRef.current.suspend();
}
}, []);
return [resume, suspend, frequencyBands] as const;
}; };

View File

@ -20,6 +20,7 @@ const initialState = ImmutableMap({
home: ImmutableMap({ home: ImmutableMap({
shows: ImmutableMap({ shows: ImmutableMap({
quote: true,
reblog: true, reblog: true,
reply: true, reply: true,
direct: true, direct: true,

View File

@ -7355,18 +7355,19 @@ img.modal-warning {
} }
.audio-player { .audio-player {
overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
container: audio-player / inline-size;
position: relative; position: relative;
background: var(--player-background-color, var(--background-color)); overflow: hidden;
color: var(--player-foreground-color); display: flex;
border-radius: 8px; flex-direction: column;
padding-bottom: 44px;
width: 100%; width: 100%;
aspect-ratio: 16 / 9;
color: var(--player-foreground-color);
background: var(--player-background-color, var(--background-color));
border-radius: 8px;
outline: 1px solid var(--media-outline-color); outline: 1px solid var(--media-outline-color);
outline-offset: -1px; outline-offset: -1px;
aspect-ratio: 16 / 9;
container: audio-player / inline-size;
&__controls { &__controls {
display: grid; display: grid;
@ -7415,9 +7416,17 @@ img.modal-warning {
} }
&__visualizer { &__visualizer {
width: 100%;
max-width: 200px; max-width: 200px;
} }
.video-player__seek {
position: absolute;
inset: 0 0 auto;
height: 24px;
z-index: 1; /* Ensure this renders on top of audio player controls */
}
&.inactive { &.inactive {
.video-player__seek, .video-player__seek,
.audio-player__controls, .audio-player__controls,
@ -8497,6 +8506,20 @@ noscript {
font-weight: 400; font-weight: 400;
margin-bottom: 10px; margin-bottom: 10px;
&__loading-indicator-wrapper {
position: relative;
height: 37px;
.loading-indicator {
left: 10px;
}
.circular-progress {
width: 14px;
height: 14px;
}
}
label { label {
display: block; display: block;
font-size: 12px; font-size: 12px;
@ -11373,6 +11396,8 @@ noscript {
&__slide { &__slide {
flex: 0 0 auto; flex: 0 0 auto;
flex-basis: 100%; flex-basis: 100%;
width: 100%;
overflow: hidden;
} }
.status { .status {

View File

@ -2,6 +2,7 @@ import { browserHistory } from 'mastodon/components/router';
import { debounceWithDispatchAndArguments } from 'mastodon/utils/debounce'; import { debounceWithDispatchAndArguments } from 'mastodon/utils/debounce';
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { me } from '../initial_state';
import { import {
followAccountSuccess, unfollowAccountSuccess, followAccountSuccess, unfollowAccountSuccess,
@ -12,6 +13,7 @@ import {
blockAccountSuccess, unblockAccountSuccess, blockAccountSuccess, unblockAccountSuccess,
pinAccountSuccess, unpinAccountSuccess, pinAccountSuccess, unpinAccountSuccess,
fetchRelationshipsSuccess, fetchRelationshipsSuccess,
fetchEndorsedAccounts,
} from './accounts_typed'; } from './accounts_typed';
import { importFetchedAccount, importFetchedAccounts } from './importer'; import { importFetchedAccount, importFetchedAccounts } from './importer';
@ -634,6 +636,7 @@ export function pinAccount(id) {
api().post(`/api/v1/accounts/${id}/pin`).then(response => { api().post(`/api/v1/accounts/${id}/pin`).then(response => {
dispatch(pinAccountSuccess({ relationship: response.data })); dispatch(pinAccountSuccess({ relationship: response.data }));
dispatch(fetchEndorsedAccounts({ accountId: me }));
}).catch(error => { }).catch(error => {
dispatch(pinAccountFail(error)); dispatch(pinAccountFail(error));
}); });
@ -646,6 +649,7 @@ export function unpinAccount(id) {
api().post(`/api/v1/accounts/${id}/unpin`).then(response => { api().post(`/api/v1/accounts/${id}/unpin`).then(response => {
dispatch(unpinAccountSuccess({ relationship: response.data })); dispatch(unpinAccountSuccess({ relationship: response.data }));
dispatch(fetchEndorsedAccounts({ accountId: me }));
}).catch(error => { }).catch(error => {
dispatch(unpinAccountFail(error)); dispatch(unpinAccountFail(error));
}); });

View File

@ -12,6 +12,8 @@ import {
muteAccount, muteAccount,
unmuteAccount, unmuteAccount,
followAccountSuccess, followAccountSuccess,
unpinAccount,
pinAccount,
} from 'mastodon/actions/accounts'; } from 'mastodon/actions/accounts';
import { showAlertForError } from 'mastodon/actions/alerts'; import { showAlertForError } from 'mastodon/actions/alerts';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
@ -62,14 +64,23 @@ const messages = defineMessages({
}, },
}); });
export const Account: React.FC<{ interface AccountProps {
size?: number; size?: number;
id: string; id: string;
hidden?: boolean; hidden?: boolean;
minimal?: boolean; minimal?: boolean;
defaultAction?: 'block' | 'mute'; defaultAction?: 'block' | 'mute';
withBio?: boolean; withBio?: boolean;
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => { }
export const Account: React.FC<AccountProps> = ({
id,
size = 46,
hidden,
minimal,
defaultAction,
withBio,
}) => {
const intl = useIntl(); const intl = useIntl();
const { signedIn } = useIdentity(); const { signedIn } = useIdentity();
const account = useAppSelector((state) => state.accounts.get(id)); const account = useAppSelector((state) => state.accounts.get(id));
@ -119,8 +130,6 @@ export const Account: React.FC<{
}, },
]; ];
} else if (defaultAction !== 'block') { } else if (defaultAction !== 'block') {
arr = [];
if (isRemote && accountUrl) { if (isRemote && accountUrl) {
arr.push({ arr.push({
text: intl.formatMessage(messages.openOriginalPage), text: intl.formatMessage(messages.openOriginalPage),
@ -173,6 +182,25 @@ export const Account: React.FC<{
text: intl.formatMessage(messages.addToLists), text: intl.formatMessage(messages.addToLists),
action: handleAddToLists, 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,
});
}
} }
} }

View File

@ -8,6 +8,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize'; import Textarea from 'react-textarea-autosize';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
const messages = defineMessages({ const messages = defineMessages({
placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' }, placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' },
}); });
@ -153,17 +155,23 @@ class AccountNote extends ImmutablePureComponent {
<FormattedMessage id='account.account_note_header' defaultMessage='Personal note' /> <InlineAlert show={saved} /> <FormattedMessage id='account.account_note_header' defaultMessage='Personal note' /> <InlineAlert show={saved} />
</label> </label>
<Textarea {this.props.value === undefined ? (
id={`account-note-${accountId}`} <div className='account__header__account-note__loading-indicator-wrapper'>
className='account__header__account-note__content' <LoadingIndicator />
disabled={this.props.value === null || value === null} </div>
placeholder={intl.formatMessage(messages.placeholder)} ) : (
value={value || ''} <Textarea
onChange={this.handleChange} id={`account-note-${accountId}`}
onKeyDown={this.handleKeyDown} className='account__header__account-note__content'
onBlur={this.handleBlur} disabled={value === null}
ref={this.setTextareaRef} placeholder={intl.formatMessage(messages.placeholder)}
/> value={value || ''}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
onBlur={this.handleBlur}
ref={this.setTextareaRef}
/>
)}
</div> </div>
); );
} }

View File

@ -17,6 +17,7 @@ import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { SpoilerButton } from 'mastodon/components/spoiler_button'; import { SpoilerButton } from 'mastodon/components/spoiler_button';
import { formatTime, getPointerPosition } from 'mastodon/features/video'; import { formatTime, getPointerPosition } from 'mastodon/features/video';
import { useAudioContext } from 'mastodon/hooks/useAudioContext';
import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer'; import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer';
import { import {
displayMedia, displayMedia,
@ -119,12 +120,17 @@ export const Audio: React.FC<{
const seekRef = useRef<HTMLDivElement>(null); const seekRef = useRef<HTMLDivElement>(null);
const volumeRef = useRef<HTMLDivElement>(null); const volumeRef = useRef<HTMLDivElement>(null);
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(); const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
const [resumeAudio, suspendAudio, frequencyBands] = useAudioVisualizer(
audioRef,
3,
);
const accessibilityId = useId(); const accessibilityId = useId();
const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } =
useAudioContext({ audioElementRef: audioRef });
const frequencyBands = useAudioVisualizer({
audioContextRef,
sourceRef,
numBands: 3,
});
const [style, spring] = useSpring(() => ({ const [style, spring] = useSpring(() => ({
progress: '0%', progress: '0%',
buffer: '0%', buffer: '0%',
@ -152,6 +158,9 @@ export const Audio: React.FC<{
restoreVolume(audioRef.current); restoreVolume(audioRef.current);
setVolume(audioRef.current.volume); setVolume(audioRef.current.volume);
setMuted(audioRef.current.muted); setMuted(audioRef.current.muted);
if (gainNodeRef.current) {
gainNodeRef.current.gain.value = audioRef.current.volume;
}
void spring.start({ void spring.start({
volume: `${audioRef.current.volume * 100}%`, volume: `${audioRef.current.volume * 100}%`,
immediate: reduceMotion, immediate: reduceMotion,
@ -159,15 +168,14 @@ export const Audio: React.FC<{
} }
}, },
[ [
spring, deployPictureInPicture,
setVolume,
setMuted,
src, src,
poster, poster,
backgroundColor, backgroundColor,
accentColor,
foregroundColor, foregroundColor,
deployPictureInPicture, accentColor,
gainNodeRef,
spring,
], ],
); );
@ -178,7 +186,11 @@ export const Audio: React.FC<{
audioRef.current.volume = volume; audioRef.current.volume = volume;
audioRef.current.muted = muted; audioRef.current.muted = muted;
}, [volume, muted]);
if (gainNodeRef.current) {
gainNodeRef.current.gain.value = muted ? 0 : volume;
}
}, [volume, muted, gainNodeRef]);
useEffect(() => { useEffect(() => {
if (typeof visible !== 'undefined') { if (typeof visible !== 'undefined') {
@ -192,11 +204,10 @@ export const Audio: React.FC<{
}, [visible, sensitive]); }, [visible, sensitive]);
useEffect(() => { useEffect(() => {
if (!revealed && audioRef.current) { if (!revealed) {
audioRef.current.pause(); pauseAudio();
suspendAudio();
} }
}, [suspendAudio, revealed]); }, [pauseAudio, revealed]);
useEffect(() => { useEffect(() => {
let nextFrame: ReturnType<typeof requestAnimationFrame>; let nextFrame: ReturnType<typeof requestAnimationFrame>;
@ -228,13 +239,11 @@ export const Audio: React.FC<{
} }
if (audioRef.current.paused) { if (audioRef.current.paused) {
resumeAudio(); playAudio();
void audioRef.current.play();
} else { } else {
audioRef.current.pause(); pauseAudio();
suspendAudio();
} }
}, [resumeAudio, suspendAudio]); }, [playAudio, pauseAudio]);
const handlePlay = useCallback(() => { const handlePlay = useCallback(() => {
setPaused(false); setPaused(false);
@ -349,8 +358,7 @@ export const Audio: React.FC<{
document.removeEventListener('mouseup', handleSeekMouseUp, true); document.removeEventListener('mouseup', handleSeekMouseUp, true);
setDragging(false); setDragging(false);
resumeAudio(); playAudio();
void audioRef.current?.play();
}; };
const handleSeekMouseMove = (e: MouseEvent) => { const handleSeekMouseMove = (e: MouseEvent) => {
@ -377,7 +385,7 @@ export const Audio: React.FC<{
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}, },
[setDragging, spring, resumeAudio], [playAudio, spring],
); );
const handleMouseEnter = useCallback(() => { const handleMouseEnter = useCallback(() => {
@ -446,10 +454,9 @@ export const Audio: React.FC<{
const handleCanPlayThrough = useCallback(() => { const handleCanPlayThrough = useCallback(() => {
if (startPlaying) { if (startPlaying) {
resumeAudio(); playAudio();
void audioRef.current?.play();
} }
}, [startPlaying, resumeAudio]); }, [startPlaying, playAudio]);
const seekBy = (time: number) => { const seekBy = (time: number) => {
if (!audioRef.current) { if (!audioRef.current) {
@ -492,7 +499,7 @@ export const Audio: React.FC<{
return; return;
} }
const newVolume = audioRef.current.volume + step; const newVolume = Math.max(0, audioRef.current.volume + step);
if (!isNaN(newVolume)) { if (!isNaN(newVolume)) {
audioRef.current.volume = newVolume; audioRef.current.volume = newVolume;

View File

@ -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 <SettingToggle
prefix='home_timeline' prefix='home_timeline'
settings={settings} settings={settings}

View File

@ -17,19 +17,22 @@ const makeGetStatusIds = (pending = false) => createSelector([
if (id === null || id === 'inline-follow-suggestions') return true; if (id === null || id === 'inline-follow-suggestions') return true;
const statusForId = statuses.get(id); const statusForId = statuses.get(id);
let showStatus = true;
if (statusForId.get('account') === me) return true; if (statusForId.get('account') === me) return true;
if (columnSettings.getIn(['shows', 'reblog']) === false) { if (columnSettings.getIn(['shows', 'reblog']) === false && statusForId.get('reblog') !== null) {
showStatus = showStatus && statusForId.get('reblog') === null; return false;
} }
if (columnSettings.getIn(['shows', 'reply']) === false) { if (columnSettings.getIn(['shows', 'reply']) === false && statusForId.get('in_reply_to_id') !== null && statusForId.get('in_reply_to_account_id') !== me) {
showStatus = showStatus && (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;
}); });
}); });

View 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,
};
};

View File

@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useCallback } from 'react'; import { useState, useEffect, useRef } from 'react';
const normalizeFrequencies = (arr: Float32Array): number[] => { const normalizeFrequencies = (arr: Float32Array): number[] => {
return new Array(...arr).map((value: number) => { return new Array(...arr).map((value: number) => {
@ -10,12 +10,17 @@ const normalizeFrequencies = (arr: Float32Array): number[] => {
}); });
}; };
export const useAudioVisualizer = ( interface AudioVisualiserOptions {
ref: React.MutableRefObject<HTMLAudioElement | null>, audioContextRef: React.MutableRefObject<AudioContext | undefined>;
numBands: number, sourceRef: React.MutableRefObject<MediaElementAudioSourceNode | undefined>;
) => { numBands: number;
const audioContextRef = useRef<AudioContext>(); }
const sourceRef = useRef<MediaElementAudioSourceNode>();
export const useAudioVisualizer = ({
audioContextRef,
sourceRef,
numBands,
}: AudioVisualiserOptions) => {
const analyzerRef = useRef<AnalyserNode>(); const analyzerRef = useRef<AnalyserNode>();
const [frequencyBands, setFrequencyBands] = useState<number[]>( const [frequencyBands, setFrequencyBands] = useState<number[]>(
@ -23,47 +28,31 @@ export const useAudioVisualizer = (
); );
useEffect(() => { useEffect(() => {
if (!audioContextRef.current) { if (audioContextRef.current) {
audioContextRef.current = new AudioContext();
analyzerRef.current = audioContextRef.current.createAnalyser(); analyzerRef.current = audioContextRef.current.createAnalyser();
analyzerRef.current.smoothingTimeConstant = 0.6; analyzerRef.current.smoothingTimeConstant = 0.6;
analyzerRef.current.fftSize = 2048; analyzerRef.current.fftSize = 2048;
} }
}, [audioContextRef]);
return () => {
if (audioContextRef.current) {
void audioContextRef.current.close();
}
};
}, []);
useEffect(() => { useEffect(() => {
if ( if (analyzerRef.current && sourceRef.current) {
audioContextRef.current &&
analyzerRef.current &&
!sourceRef.current &&
ref.current
) {
sourceRef.current = audioContextRef.current.createMediaElementSource(
ref.current,
);
sourceRef.current.connect(analyzerRef.current); sourceRef.current.connect(analyzerRef.current);
sourceRef.current.connect(audioContextRef.current.destination);
} }
const currentSource = sourceRef.current;
return () => { return () => {
if (sourceRef.current) { if (currentSource && analyzerRef.current) {
sourceRef.current.disconnect(); currentSource.disconnect(analyzerRef.current);
} }
}; };
}, [ref]); }, [audioContextRef, sourceRef]);
useEffect(() => { useEffect(() => {
const source = sourceRef.current;
const analyzer = analyzerRef.current; const analyzer = analyzerRef.current;
const context = audioContextRef.current; const context = audioContextRef.current;
if (!source || !analyzer || !context) { if (!analyzer || !context) {
return; return;
} }
@ -94,19 +83,7 @@ export const useAudioVisualizer = (
return () => { return () => {
clearInterval(updateInterval); clearInterval(updateInterval);
}; };
}, [numBands]); }, [numBands, audioContextRef]);
const resume = useCallback(() => { return frequencyBands;
if (audioContextRef.current) {
void audioContextRef.current.resume();
}
}, []);
const suspend = useCallback(() => {
if (audioContextRef.current) {
void audioContextRef.current.suspend();
}
}, []);
return [resume, suspend, frequencyBands] as const;
}; };

View File

@ -430,6 +430,7 @@
"hints.profiles.see_more_posts": "See more posts on {domain}", "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.replies_may_be_missing": "Replies from other servers may be missing.",
"hints.threads.see_more": "See more replies on {domain}", "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_reblogs": "Show boosts",
"home.column_settings.show_replies": "Show replies", "home.column_settings.show_replies": "Show replies",
"home.hide_announcements": "Hide announcements", "home.hide_announcements": "Hide announcements",

View File

@ -1,6 +1,7 @@
{ {
"about.blocks": "Modereeritavad serverid", "about.blocks": "Modereeritavad serverid",
"about.contact": "Kontakt:", "about.contact": "Kontakt:",
"about.default_locale": "Vaikimisi",
"about.disclaimer": "Mastodon on tasuta ja vaba tarkvara ning Mastodon gGmbH kaubamärk.", "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.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.", "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.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.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.domain_blocks.suspended.title": "Peatatud",
"about.language_label": "Keel",
"about.not_available": "See info ei ole sellel serveril saadavaks tehtud.", "about.not_available": "See info ei ole sellel serveril saadavaks tehtud.",
"about.powered_by": "Hajutatud sotsiaalmeedia, mille taga on {mastodon}", "about.powered_by": "Hajutatud sotsiaalmeedia, mille taga on {mastodon}",
"about.rules": "Serveri reeglid", "about.rules": "Serveri reeglid",
@ -322,6 +324,8 @@
"explore.trending_links": "Uudised", "explore.trending_links": "Uudised",
"explore.trending_statuses": "Postitused", "explore.trending_statuses": "Postitused",
"explore.trending_tags": "Sildid", "explore.trending_tags": "Sildid",
"featured_carousel.next": "Järgmine",
"featured_carousel.previous": "Eelmine",
"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_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.context_mismatch_title": "Konteksti mittesobivus!",
"filter_modal.added.expired_explanation": "Selle filtri kategooria on aegunud. pead muutma aegumiskuupäeva, kui tahad, et filter kehtiks.", "filter_modal.added.expired_explanation": "Selle filtri kategooria on aegunud. pead muutma aegumiskuupäeva, kui tahad, et filter kehtiks.",
@ -387,6 +391,7 @@
"hashtag.counter_by_uses": "{count, plural, one {{counter} postitusega} other {{counter} postitusega}}", "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.counter_by_uses_today": "{count, plural, one {{counter} postitust} other {{counter} postitust}} täna",
"hashtag.follow": "Jälgi silti", "hashtag.follow": "Jälgi silti",
"hashtag.mute": "Vaigista @#{hashtag}",
"hashtag.unfollow": "Lõpeta sildi jälgimine", "hashtag.unfollow": "Lõpeta sildi jälgimine",
"hashtags.and_other": "…ja {count, plural, one {}other {# veel}}", "hashtags.and_other": "…ja {count, plural, one {}other {# veel}}",
"hints.profiles.followers_may_be_missing": "Selle profiili jälgijaid võib olla puudu.", "hints.profiles.followers_may_be_missing": "Selle profiili jälgijaid võib olla puudu.",
@ -892,6 +897,8 @@
"video.expand": "Suurenda video", "video.expand": "Suurenda video",
"video.fullscreen": "Täisekraan", "video.fullscreen": "Täisekraan",
"video.hide": "Peida video", "video.hide": "Peida video",
"video.mute": "Vaigista",
"video.pause": "Paus", "video.pause": "Paus",
"video.play": "Mängi" "video.play": "Mängi",
"video.unmute": "Lõpeta vaigistamine"
} }

View File

@ -1,6 +1,7 @@
{ {
"about.blocks": "Frithealaichean fo mhaorsainneachd", "about.blocks": "Frithealaichean fo mhaorsainneachd",
"about.contact": "Fios thugainn:", "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.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.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.", "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.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.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.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.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.powered_by": "Lìonra sòisealta sgaoilte le cumhachd {mastodon}",
"about.rules": "Riaghailtean an fhrithealaiche", "about.rules": "Riaghailtean an fhrithealaiche",
@ -28,6 +30,9 @@
"account.edit_profile": "Deasaich a phròifil", "account.edit_profile": "Deasaich a phròifil",
"account.enable_notifications": "Cuir brath thugam nuair a chuireas @{name} post ris", "account.enable_notifications": "Cuir brath thugam nuair a chuireas @{name} post ris",
"account.endorse": "Brosnaich air a phròifil", "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": "Ga bhrosnachadh",
"account.featured.accounts": "Pròifilean", "account.featured.accounts": "Pròifilean",
"account.featured.hashtags": "Tagaichean hais", "account.featured.hashtags": "Tagaichean hais",
@ -38,6 +43,7 @@
"account.followers": "Luchd-leantainn", "account.followers": "Luchd-leantainn",
"account.followers.empty": "Chan eil neach sam bith a leantainn air a chleachdaiche seo fhathast.", "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_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": "A leantainn",
"account.following_counter": "{count, plural, one {A leantainn {counter}} other {A leantainn {counter}}}", "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.", "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.search_results": "Toraidhean an luirg",
"emoji_button.symbols": "Samhlaidhean", "emoji_button.symbols": "Samhlaidhean",
"emoji_button.travel": "Siubhal ⁊ àitichean", "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_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_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", "empty_column.account_suspended": "Chaidh an cunntas a chur à rèim",
@ -337,6 +345,11 @@
"explore.trending_links": "Naidheachdan", "explore.trending_links": "Naidheachdan",
"explore.trending_statuses": "Postaichean", "explore.trending_statuses": "Postaichean",
"explore.trending_tags": "Tagaichean hais", "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 dhinntrig 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_explanation": "Chan eil an roinn-seòrsa criathraidh iom seo chaidh dhan cho-theacs san do dhinntrig 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.context_mismatch_title": "Co-theacsa neo-iomchaidh!",
"filter_modal.added.expired_explanation": "Dhfhalbh 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.", "filter_modal.added.expired_explanation": "Dhfhalbh 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.mute_conversation": "Mùch an còmhradh",
"status.open": "Leudaich am post seo", "status.open": "Leudaich am post seo",
"status.pin": "Prìnich ris a phròifil", "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.read_more": "Leugh an còrr",
"status.reblog": "Brosnaich", "status.reblog": "Brosnaich",
"status.reblog_private": "Brosnaich leis an t-so-fhaicsinneachd tùsail", "status.reblog_private": "Brosnaich leis an t-so-fhaicsinneachd tùsail",

View File

@ -1,6 +1,7 @@
{ {
"about.blocks": "Moderált kiszolgálók", "about.blocks": "Moderált kiszolgálók",
"about.contact": "Kapcsolat:", "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.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.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.", "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.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.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.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.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.powered_by": "Decentralizált közösségi média a {mastodon} segítségével",
"about.rules": "Kiszolgáló szabályai", "about.rules": "Kiszolgáló szabályai",

View File

@ -8,6 +8,7 @@
"about.domain_blocks.silenced.title": "ken lukin lili ", "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.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.domain_blocks.suspended.title": "weka",
"about.language_label": "toki",
"about.not_available": "lon kulupu ni la sina ken alasa ala e sona ni.", "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.powered_by": "lipu kulupu pi jan lawa mute tan {mastodon}",
"about.rules": "lawa kulupu", "about.rules": "lawa kulupu",
@ -15,37 +16,47 @@
"account.add_or_remove_from_list": "o ante e lipu jan", "account.add_or_remove_from_list": "o ante e lipu jan",
"account.badges.bot": "ilo nanpa li lawa e ni", "account.badges.bot": "ilo nanpa li lawa e ni",
"account.badges.group": "kulupu", "account.badges.group": "kulupu",
"account.block": "o weka e @{name}", "account.block": "o len e @{name}",
"account.block_domain": "o weka e ma {domain}", "account.block_domain": "o len e ma {domain}",
"account.block_short": "o weka e jan tawa mi", "account.block_short": "o len",
"account.blocked": "jan li weka tawa mi", "account.blocked": "jan li len",
"account.blocking": "mi len e jan ni",
"account.cancel_follow_request": "o kute ala", "account.cancel_follow_request": "o kute ala",
"account.copy": "o pali same e linja pi lipu jan", "account.copy": "o pali same e linja pi lipu jan",
"account.direct": "len la o mu e @{name}", "account.direct": "len la o mu e @{name}",
"account.disable_notifications": "@{name} li toki la o mu ala e mi", "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.edit_profile": "o ante e lipu mi",
"account.enable_notifications": "@{name} li toki la o toki e toki ona tawa 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.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.featured_tags.last_status_never": "toki ala li lon",
"account.follow": "o kute", "account.follow": "o kute",
"account.follow_back": "jan ni li kute e sina. o kute", "account.follow_back": "jan ni li kute e sina. o kute",
"account.followers": "jan kute", "account.followers": "jan kute",
"account.followers.empty": "jan ala li kute e jan ni", "account.followers.empty": "jan ala li kute e jan ni",
"account.followers_counter": "{count, plural, other {jan {counter} li kute e ona}}", "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": "sina kute e jan ni",
"account.following_counter": "{count, plural, other {ona li kute e jan {counter}}}", "account.following_counter": "{count, plural, other {ona li kute e jan {counter}}}",
"account.follows.empty": "jan ni li kute e jan ala", "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.go_to_profile": "o tawa lipu jan",
"account.hide_reblogs": "o lukin ala e pana toki tan @{name}", "account.hide_reblogs": "o lukin ala e pana toki tan @{name}",
"account.in_memoriam": "jan ni li moli. pona o tawa ona.", "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.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.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.locked_info": "sina wile kute e jan ni la ona o toki e ken",
"account.media": "sitelen", "account.media": "sitelen",
"account.mention": "o toki e jan @{name}", "account.mention": "o mu e jan @{name}",
"account.moved_to": "lipu jan sin pi jan {name} li ni:", "account.moved_to": "jan ni la lipu sin li ni:",
"account.mute": "o len e @{name}", "account.mute": "o len e @{name}",
"account.mute_notifications_short": "o kute ala e mu tan jan ni", "account.mute_notifications_short": "o kute ala e mu tan jan ni",
"account.mute_short": "o kute ala", "account.mute_short": "o kute ala",
@ -58,11 +69,12 @@
"account.requested": "jan ni o ken e kute sina", "account.requested": "jan ni o ken e kute sina",
"account.requested_follow": "jan {name} li wile kute e sina", "account.requested_follow": "jan {name} li wile kute e sina",
"account.share": "o pana e lipu jan @{name}", "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.statuses_counter": "{count, plural, other {toki {counter}}}",
"account.unblock": "o weka ala e jan {name}", "account.unblock": "o len ala e jan {name}",
"account.unblock_domain": "o weka ala e ma {domain}", "account.unblock_domain": "o len ala e ma {domain}",
"account.unblock_short": "o pini weka", "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.unendorse": "lipu jan la o suli ala e ni",
"account.unfollow": "o kute ala", "account.unfollow": "o kute ala",
"account.unmute": "o len ala e @{name}", "account.unmute": "o len ala e @{name}",
@ -72,7 +84,7 @@
"admin.dashboard.daily_retention": "nanpa pi awen jan lon tenpo suno", "admin.dashboard.daily_retention": "nanpa pi awen jan lon tenpo suno",
"admin.dashboard.monthly_retention": "nanpa pi awen jan lon tenpo mun", "admin.dashboard.monthly_retention": "nanpa pi awen jan lon tenpo mun",
"admin.dashboard.retention.average": "sama", "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.dashboard.retention.cohort_size": "jan sin",
"admin.impact_report.instance_accounts": "ni li pakala li weka e lipu jan ni", "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", "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.rate_limited.title": "ilo ni li lili e ken sina",
"alert.unexpected.message": "pakala li lon", "alert.unexpected.message": "pakala li lon",
"alert.unexpected.title": "pakala a!", "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_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.add_text_from_image": "o pana e nimi tan sitelen ni",
"alt_text_modal.cancel": "weka", "alt_text_modal.cancel": "o weka",
"alt_text_modal.change_thumbnail": "o ante e sitelen lili", "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_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.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", "announcement.announcement": "toki suli",
"annual_report.summary.archetype.booster": "jan ni li alasa e pona", "annual_report.summary.archetype.booster": "jan ni li alasa e pona",
"annual_report.summary.archetype.lurker": "jan ni li lukin taso", "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_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.by_replies": "toki li jo e toki kama pi nanpa wan",
"annual_report.summary.highlighted_post.possessive": "tan jan {name}", "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.most_used_hashtag.none": "ala",
"annual_report.summary.new_posts.new_posts": "toki suli sin", "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>", "annual_report.summary.percentile.text": "<topLabel>ni la sina nanpa sewi</topLabel><percentage></percentage><bottomLabel>pi jan ale lon {domain}.</bottomLabel>",
@ -115,8 +129,8 @@
"block_modal.show_more": "o pana e mute", "block_modal.show_more": "o pana e mute",
"block_modal.they_cant_mention": "ona li ken ala toki tawa sina li ken ala kute e sina.", "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_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.they_will_know": "ona li ken sona e ni: sina len e ona.",
"block_modal.title": "o weka ala weka e jan", "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.", "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.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.reblog": "o wawa ala wawa e toki?",
@ -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.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", "closed_registrations_modal.title": "sina kama lon kulupu Masoton",
"column.about": "sona", "column.about": "sona",
"column.blocks": "kulupu pi jan weka", "column.blocks": "jan len",
"column.bookmarks": "awen toki", "column.bookmarks": "awen toki",
"column.community": "linja tenpo pi ma ni", "column.community": "linja tenpo pi ma ni",
"column.create_list": "o pali e kulupu", "column.create_list": "o pali e kulupu",
"column.direct": "mu len", "column.direct": "mu len",
"column.directory": "o lukin e jan", "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.edit_list": "o ante e kulupu",
"column.favourites": "ijo pona", "column.favourites": "ijo pona",
"column.firehose": "toki pi tenpo ni", "column.firehose": "toki pi tenpo ni",
@ -194,7 +208,7 @@
"compose_form.spoiler.unmarked": "o pali 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)", "compose_form.spoiler_placeholder": "toki pi ijo ike ken (sina ken ala e ni)",
"confirmation_modal.cancel": "o weka", "confirmation_modal.cancel": "o weka",
"confirmations.block.confirm": "o weka", "confirmations.block.confirm": "o len",
"confirmations.delete.confirm": "o weka", "confirmations.delete.confirm": "o weka",
"confirmations.delete.message": "sina wile ala wile weka e toki ni?", "confirmations.delete.message": "sina wile ala wile weka e toki ni?",
"confirmations.delete.title": "o weka ala weka e toki?", "confirmations.delete.title": "o weka ala weka e toki?",
@ -214,6 +228,7 @@
"confirmations.logout.title": "o weka?", "confirmations.logout.title": "o weka?",
"confirmations.missing_alt_text.confirm": "pana e toki pi sona lukin", "confirmations.missing_alt_text.confirm": "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.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.secondary": "o pana a",
"confirmations.missing_alt_text.title": "o pana e toki pi sona lukin", "confirmations.missing_alt_text.title": "o pana e toki pi sona lukin",
"confirmations.mute.confirm": "o len", "confirmations.mute.confirm": "o len",
"confirmations.redraft.confirm": "o weka o pali sin e toki", "confirmations.redraft.confirm": "o weka o pali sin e toki",
@ -245,9 +260,11 @@
"dismissable_banner.dismiss": "o weka", "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_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.", "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.block": "o len e ma",
"domain_block_modal.they_wont_know": "ona li sona ala e ni: sina weka e ona.", "domain_block_modal.block_account_instead": "o len e @{name}",
"domain_block_modal.title": "sina wile weka ala weka e ma?", "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_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_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_block_modal.you_wont_see_posts": "sina ken ala lukin e toki tan jan pi ma ni",
@ -281,9 +298,9 @@
"empty_column.account_suspended": "lipu ni li weka", "empty_column.account_suspended": "lipu ni li weka",
"empty_column.account_timeline": "toki ala li lon!", "empty_column.account_timeline": "toki ala li lon!",
"empty_column.account_unavailable": "ken ala lukin e lipu jan", "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.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.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.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.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.",
@ -345,7 +362,7 @@
"interaction_modal.username_prompt": "ni li sama ni: {example}", "interaction_modal.username_prompt": "ni li sama ni: {example}",
"intervals.full.days": "{number, plural, other {suni #}}", "intervals.full.days": "{number, plural, other {suni #}}",
"intervals.full.hours": "{number, plural, other {tenpo suli #}}", "intervals.full.hours": "{number, plural, other {tenpo suli #}}",
"keyboard_shortcuts.blocked": "o lukin e lipu sina pi jan weka", "keyboard_shortcuts.blocked": "o lukin e lipu pi len sina",
"keyboard_shortcuts.boost": "o pana sin e toki", "keyboard_shortcuts.boost": "o pana sin e toki",
"keyboard_shortcuts.down": "o tawa anpa lon lipu", "keyboard_shortcuts.down": "o tawa anpa lon lipu",
"keyboard_shortcuts.enter": "o lukin e toki", "keyboard_shortcuts.enter": "o lukin e toki",
@ -362,6 +379,7 @@
"lightbox.next": "sinpin", "lightbox.next": "sinpin",
"lightbox.previous": "monsi", "lightbox.previous": "monsi",
"link_preview.author": "tan {name}", "link_preview.author": "tan {name}",
"lists.create": "o lipu e kulupu jan",
"lists.delete": "o weka e kulupu lipu", "lists.delete": "o weka e kulupu lipu",
"lists.done": "ale li pini", "lists.done": "ale li pini",
"lists.edit": "o ante e kulupu lipu", "lists.edit": "o ante e kulupu lipu",
@ -378,9 +396,9 @@
"loading_indicator.label": "ni li kama…", "loading_indicator.label": "ni li kama…",
"mute_modal.title": "sina wile ala wile kute e jan ni?", "mute_modal.title": "sina wile ala wile kute e jan ni?",
"navigation_bar.about": "sona", "navigation_bar.about": "sona",
"navigation_bar.blocks": "jan weka", "navigation_bar.blocks": "jan len",
"navigation_bar.compose": "o pali e toki sin", "navigation_bar.compose": "o pali e toki sin",
"navigation_bar.domain_blocks": "kulupu pi ma weka", "navigation_bar.domain_blocks": "ma len",
"navigation_bar.favourites": "ijo pona", "navigation_bar.favourites": "ijo pona",
"navigation_bar.filters": "nimi len", "navigation_bar.filters": "nimi len",
"navigation_bar.lists": "kulupu lipu", "navigation_bar.lists": "kulupu lipu",
@ -400,6 +418,8 @@
"notification.mentioned_you": "jan {name} li toki e sina", "notification.mentioned_you": "jan {name} li toki e sina",
"notification.moderation-warning.learn_more": "o kama sona e ijo ante", "notification.moderation-warning.learn_more": "o kama sona e ijo ante",
"notification.reblog": "{name} li wawa e toki sina", "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.status": "{name} li toki",
"notification.update": "{name} li ante e toki", "notification.update": "{name} li ante e toki",
"notification_requests.dismiss": "o weka", "notification_requests.dismiss": "o weka",
@ -433,7 +453,7 @@
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"relative_time.today": "tenpo suno ni", "relative_time.today": "tenpo suno ni",
"reply_indicator.cancel": "o ala", "reply_indicator.cancel": "o ala",
"report.block": "o weka e jan", "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.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.other": "ante",
"report.categories.spam": "ike tan toki mute", "report.categories.spam": "ike tan toki mute",
@ -465,7 +485,7 @@
"search_results.see_all": "ale", "search_results.see_all": "ale",
"search_results.statuses": "toki", "search_results.statuses": "toki",
"server_banner.administered_by": "jan lawa:", "server_banner.administered_by": "jan lawa:",
"status.block": "o weka e @{name}", "status.block": "o len e @{name}",
"status.cancel_reblog_private": "o pini e pana", "status.cancel_reblog_private": "o pini e pana",
"status.delete": "o weka", "status.delete": "o weka",
"status.edit": "o ante", "status.edit": "o ante",

View File

@ -20,6 +20,7 @@ const initialState = ImmutableMap({
home: ImmutableMap({ home: ImmutableMap({
shows: ImmutableMap({ shows: ImmutableMap({
quote: true,
reblog: true, reblog: true,
reply: true, reply: true,
}), }),

View File

@ -7068,18 +7068,19 @@ a.status-card {
} }
.audio-player { .audio-player {
overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
container: audio-player / inline-size;
position: relative; position: relative;
background: var(--player-background-color, var(--background-color)); overflow: hidden;
color: var(--player-foreground-color); display: flex;
border-radius: 8px; flex-direction: column;
padding-bottom: 44px;
width: 100%; width: 100%;
aspect-ratio: 16 / 9;
color: var(--player-foreground-color);
background: var(--player-background-color, var(--background-color));
border-radius: 8px;
outline: 1px solid var(--media-outline-color); outline: 1px solid var(--media-outline-color);
outline-offset: -1px; outline-offset: -1px;
aspect-ratio: 16 / 9;
container: audio-player / inline-size;
&__controls { &__controls {
display: grid; display: grid;
@ -7128,9 +7129,17 @@ a.status-card {
} }
&__visualizer { &__visualizer {
width: 100%;
max-width: 200px; max-width: 200px;
} }
.video-player__seek {
position: absolute;
inset: 0 0 auto;
height: 24px;
z-index: 1; /* Ensure this renders on top of audio player controls */
}
&.inactive { &.inactive {
.video-player__seek, .video-player__seek,
.audio-player__controls, .audio-player__controls,
@ -8194,6 +8203,20 @@ noscript {
font-weight: 400; font-weight: 400;
margin-bottom: 10px; margin-bottom: 10px;
&__loading-indicator-wrapper {
position: relative;
height: 37px;
.loading-indicator {
left: 10px;
}
.circular-progress {
width: 14px;
height: 14px;
}
}
label { label {
display: block; display: block;
font-size: 12px; font-size: 12px;
@ -11053,6 +11076,8 @@ noscript {
&__slide { &__slide {
flex: 0 0 auto; flex: 0 0 auto;
flex-basis: 100%; flex-basis: 100%;
width: 100%;
overflow: hidden;
} }
.status { .status {

View File

@ -233,7 +233,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
approval_uri = @status_parser.quote_approval_uri approval_uri = @status_parser.quote_approval_uri
approval_uri = nil if unsupported_uri_scheme?(approval_uri) approval_uri = nil if unsupported_uri_scheme?(approval_uri)
@quote = Quote.new(account: @account, approval_uri: approval_uri) @quote = Quote.new(account: @account, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?)
end end
def process_hashtag(tag) def process_hashtag(tag)

View File

@ -125,6 +125,10 @@ class ActivityPub::Parser::StatusParser
end.first end.first
end end
def legacy_quote?
!@object.key?('quote')
end
# The inlined quote; out of the attributes we support, only `https://w3id.org/fep/044f#quote` explicitly supports inlined objects # The inlined quote; out of the attributes we support, only `https://w3id.org/fep/044f#quote` explicitly supports inlined objects
def quoted_object def quoted_object
as_array(@object['quote']).first as_array(@object['quote']).first

View File

@ -22,7 +22,7 @@ module Attachmentable
).freeze ).freeze
included do included do
def self.has_attached_file(name, options = {}) # rubocop:disable Naming/PredicateName def self.has_attached_file(name, options = {}) # rubocop:disable Naming/PredicatePrefix
super super
send(:"before_#{name}_validate", prepend: true) do send(:"before_#{name}_validate", prepend: true) do

View File

@ -7,6 +7,7 @@
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# activity_uri :string # activity_uri :string
# approval_uri :string # approval_uri :string
# legacy :boolean default(FALSE), not null
# state :integer default("pending"), not null # state :integer default("pending"), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
@ -46,6 +47,10 @@ class Quote < ApplicationRecord
end end
end end
def acceptable?
accepted? || !legacy?
end
def schedule_refresh_if_stale! def schedule_refresh_if_stale!
return unless quoted_status_id.present? && approval_uri.present? && updated_at <= BACKGROUND_REFRESH_INTERVAL.ago return unless quoted_status_id.present? && approval_uri.present? && updated_at <= BACKGROUND_REFRESH_INTERVAL.ago

View File

@ -17,7 +17,7 @@ module UserSettings::Glue
self.class.definition_for(key)&.type self.class.definition_for(key)&.type
end end
def has_attribute?(key) # rubocop:disable Naming/PredicateName def has_attribute?(key) # rubocop:disable Naming/PredicatePrefix
self.class.definition_for?(key) self.class.definition_for?(key)
end end
end end

View File

@ -37,6 +37,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
delegate :local?, to: :object delegate :local?, to: :object
def quote
object.quote if object.quote&.acceptable?
end
def id def id
object.id.to_s object.id.to_s
end end

View File

@ -283,14 +283,14 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
# If the quoted post has changed, discard the old object and create a new one # If the quoted post has changed, discard the old object and create a new one
if @status.quote.quoted_status.present? && ActivityPub::TagManager.instance.uri_for(@status.quote.quoted_status) != quote_uri if @status.quote.quoted_status.present? && ActivityPub::TagManager.instance.uri_for(@status.quote.quoted_status) != quote_uri
@status.quote.destroy @status.quote.destroy
quote = Quote.create(status: @status, approval_uri: approval_uri) quote = Quote.create(status: @status, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?)
@quote_changed = true @quote_changed = true
else else
quote = @status.quote quote = @status.quote
quote.update(approval_uri: approval_uri, state: :pending) if quote.approval_uri != @status_parser.quote_approval_uri quote.update(approval_uri: approval_uri, state: :pending, legacy: @status_parser.legacy_quote?) if quote.approval_uri != @status_parser.quote_approval_uri
end end
else else
quote = Quote.create(status: @status, approval_uri: approval_uri) quote = Quote.create(status: @status, approval_uri: approval_uri, legacy: @status_parser.legacy_quote?)
@quote_changed = true @quote_changed = true
end end

View File

@ -21,7 +21,7 @@ Rails.application.configure do
config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') do config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') do
if Rails.env.production? if Rails.env.production?
"ws#{https ? 's' : ''}://#{web_host}" "ws#{'s' if https}://#{web_host}"
else else
"ws://#{host.split(':').first}:4000" "ws://#{host.split(':').first}:4000"
end end

View File

@ -32,7 +32,7 @@ Rails.application.config.content_security_policy do |p|
if Rails.env.development? if Rails.env.development?
vite_public_host = ENV.fetch('VITE_DEV_SERVER_PUBLIC', "localhost:#{ViteRuby.config.port}") vite_public_host = ENV.fetch('VITE_DEV_SERVER_PUBLIC', "localhost:#{ViteRuby.config.port}")
front_end_build_urls = %w(ws http).map { |protocol| "#{protocol}#{ViteRuby.config.https ? 's' : ''}://#{vite_public_host}" } front_end_build_urls = %w(ws http).map { |protocol| "#{protocol}#{'s' if ViteRuby.config.https}://#{vite_public_host}" }
p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, *front_end_build_urls p.connect_src :self, :data, :blob, *media_hosts, Rails.configuration.x.streaming_api_base_url, *front_end_build_urls
p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host p.script_src :self, :unsafe_inline, :unsafe_eval, assets_host

View File

@ -23,6 +23,8 @@ et:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: sisaldab väärtusi, kus väljade nimed on puudu
username: username:
invalid: ainult tähtmärgid, numbrid ja alakriipsud invalid: ainult tähtmärgid, numbrid ja alakriipsud
reserved: on reserveeritud reserved: on reserveeritud
@ -47,8 +49,14 @@ et:
attributes: attributes:
reblog: reblog:
taken: postitusel on juba taken: postitusel on juba
terms_of_service:
attributes:
effective_date:
too_soon: on liiga vara, peab olema hiljem, kui %{date}
user: user:
attributes: attributes:
date_of_birth:
below_limit: on allpool vanuse alampiiri
email: email:
blocked: kasutab mitte lubatud e-posti teenusepakkujat blocked: kasutab mitte lubatud e-posti teenusepakkujat
unreachable: ei paista eksisteerivat unreachable: ei paista eksisteerivat

View File

@ -22,7 +22,7 @@ et:
action: Kinnita e-postiaadress action: Kinnita e-postiaadress
action_with_app: Kinnita ja naase %{app} action_with_app: Kinnita ja naase %{app}
explanation: Oled loonud %{host} konto selle e-postiaadressiga. Oled konto aktiveerimisest ühe kliki kaugusel. Kui see polnud sina, palun eira seda kirja. explanation: Oled loonud %{host} konto selle e-postiaadressiga. Oled konto aktiveerimisest ühe kliki kaugusel. Kui see polnud sina, palun eira seda kirja.
explanation_when_pending: "Selle e-postiaadressiga on esitatud taotlus saada %{host} kasutajaks. E-postiaadress vajab kinnitamist. Pärast seda vaatame me taotluse üle. Saad siseneda, et oma andmeid muuta või konto kustutada, aga enamustele tegevustele ei pääse enne ligi, kui konto on meie moderaatorite poolt kinnitatud. Kui konto on kinnitatud, saabub selle kohta eraldi e-kiri.\nKui taotlus lükatakse aga tagasi, kustutatakse ka andmed ja mingit järeltegevust pole vaja. \nKui see polnud sina, siis palume seda kirja eirata." explanation_when_pending: "Selle e-postiaadressiga on esitatud taotlus saada %{host} kasutajaks. E-postiaadress vajab kinnitamist. Pärast seda vaatame me taotluse üle. Saad siseneda, et oma andmeid muuta või konto kustutada, aga enamustele tegevustele ei pääse enne ligi, kui konto on meie moderaatorite poolt kinnitatud. Kui konto on kinnitatud, saabub selle kohta eraldi e-kiri.\nKui taotlus lükatakse aga tagasi, kustutatakse ka andmed ja mingit järeltegevust pole vaja. \nKui see polnud sina, siis palun eira seda kirja."
extra_html: Palun tutvu <a href="%{terms_path}">meie serveri reeglitega</a> ning <a href="%{policy_path}">meie kasutustingimustega</a>. extra_html: Palun tutvu <a href="%{terms_path}">meie serveri reeglitega</a> ning <a href="%{policy_path}">meie kasutustingimustega</a>.
subject: 'Mastodon: %{instance} kinnitamisjuhised' subject: 'Mastodon: %{instance} kinnitamisjuhised'
title: Kinnita e-postiaadress title: Kinnita e-postiaadress
@ -97,9 +97,9 @@ et:
update_needs_confirmation: Konto uuendamine õnnestus, kuid e-postiaadress tuleb veel kinnitada. Palun kontrolli oma e-posti ning järgi kirjas olevat kinnituslinki, et e-postiaadress kinnitada. Palun kontrolli rämpspostikausta, kui selline kiri ei saabunud. update_needs_confirmation: Konto uuendamine õnnestus, kuid e-postiaadress tuleb veel kinnitada. Palun kontrolli oma e-posti ning järgi kirjas olevat kinnituslinki, et e-postiaadress kinnitada. Palun kontrolli rämpspostikausta, kui selline kiri ei saabunud.
updated: Konto uuendamine õnnestus. updated: Konto uuendamine õnnestus.
sessions: sessions:
already_signed_out: Väljumine õnnestus. already_signed_out: Välja logimine õnnestus.
signed_in: Sisenemine õnnestus. signed_in: Sisenemine õnnestus.
signed_out: Väljumine õnnestus. signed_out: Välja logimine õnnestus.
unlocks: unlocks:
send_instructions: Saad paari minuti pärast juhistega e-kirja, kuidas oma konto lukust lahti teha. Palun kontrolli oma rämpsposti kausta, kui selline kiri ei saabunud. send_instructions: Saad paari minuti pärast juhistega e-kirja, kuidas oma konto lukust lahti teha. Palun kontrolli oma rämpsposti kausta, kui selline kiri ei saabunud.
send_paranoid_instructions: Kui konto on olemas, saabub paari minuti pärast e-kiri juhistega, kuidas konto lukust lahti teha. Palun kontrolli rämpsposti kausta, kui selline e-kiri ei saabunud. send_paranoid_instructions: Kui konto on olemas, saabub paari minuti pärast e-kiri juhistega, kuidas konto lukust lahti teha. Palun kontrolli rämpsposti kausta, kui selline e-kiri ei saabunud.

View File

@ -210,6 +210,7 @@ et:
enable_user: Lubas kasutaja enable_user: Lubas kasutaja
memorialize_account: Igaveselt lahkunuks märkimine memorialize_account: Igaveselt lahkunuks märkimine
promote_user: Edendas kasutaja promote_user: Edendas kasutaja
publish_terms_of_service: Avalda kasutustingimused
reject_appeal: Lükka vaidlustus tagasi reject_appeal: Lükka vaidlustus tagasi
reject_user: Kasutaja tagasilükkamine reject_user: Kasutaja tagasilükkamine
remove_avatar_user: Kustutas profiilipildi remove_avatar_user: Kustutas profiilipildi
@ -270,6 +271,7 @@ et:
enable_user_html: "%{name} lubas %{target} sisenemise" enable_user_html: "%{name} lubas %{target} sisenemise"
memorialize_account_html: "%{name} märkis %{target} igaveselt lahkunuks" memorialize_account_html: "%{name} märkis %{target} igaveselt lahkunuks"
promote_user_html: "%{name} ülendas kasutajat %{target}" promote_user_html: "%{name} ülendas kasutajat %{target}"
publish_terms_of_service_html: "%{name} teatas kasutustingimuste muudatusest"
reject_appeal_html: "%{name} lükkas %{target} modereerimisotsuse kaebuse tagasi" reject_appeal_html: "%{name} lükkas %{target} modereerimisotsuse kaebuse tagasi"
reject_user_html: "%{name} lükkas %{target} liitumissoovi tagasi" reject_user_html: "%{name} lükkas %{target} liitumissoovi tagasi"
remove_avatar_user_html: "%{name} eemaldas %{target} avatari" remove_avatar_user_html: "%{name} eemaldas %{target} avatari"
@ -299,6 +301,7 @@ et:
title: Auditilogi title: Auditilogi
unavailable_instance: "(domeeni nimi pole saadaval)" unavailable_instance: "(domeeni nimi pole saadaval)"
announcements: announcements:
back: Tagasi teadaannete juurde
destroyed_msg: Teadaande kustutamine õnnestus! destroyed_msg: Teadaande kustutamine õnnestus!
edit: edit:
title: Teate muutmine title: Teate muutmine
@ -465,6 +468,10 @@ et:
new: new:
title: Domeenikeeldude import title: Domeenikeeldude import
no_file: Faili pole valitud no_file: Faili pole valitud
fasp:
providers:
sign_in: Logi sisse
status: Olek
follow_recommendations: follow_recommendations:
description_html: "<strong>Jälgimissoovitused aitavad uutel kasutajatel kiirelt leida huvipakkuvat sisu</strong>. Kui kasutaja pole teistega piisavalt läbi käinud, et saaks luua personaalseid soovitusi, soovitatakse neid kontosid. Need arvutatakse ümber igapäevaselt konkreetse keele populaarseimate postituste ja enim jälgitavate kontode seast." description_html: "<strong>Jälgimissoovitused aitavad uutel kasutajatel kiirelt leida huvipakkuvat sisu</strong>. Kui kasutaja pole teistega piisavalt läbi käinud, et saaks luua personaalseid soovitusi, soovitatakse neid kontosid. Need arvutatakse ümber igapäevaselt konkreetse keele populaarseimate postituste ja enim jälgitavate kontode seast."
language: Keel language: Keel
@ -741,11 +748,16 @@ et:
title: Rollid title: Rollid
rules: rules:
add_new: Lisa reegel add_new: Lisa reegel
add_translation: Lisa tõlge
delete: Kustuta delete: Kustuta
description_html: Kuigi enamik väidab, et on teenusetingimused läbi lugenud ja nõustub nendega, loevad inimesed tavaliselt need läbi alles pärast probleemi tekkimist. <strong>Muuda oma serveri reeglite ühe pilguga haaramine hõlpsaks, esitades need lihtsa täpploendina.</strong> Püüa hoida reegli punktid lühikesed ja lihtsad, kuid ära jaga neid ka paljudeks eraldi üksusteks. description_html: Kuigi enamik väidab, et on teenusetingimused läbi lugenud ja nõustub nendega, loevad inimesed tavaliselt need läbi alles pärast probleemi tekkimist. <strong>Muuda oma serveri reeglite ühe pilguga haaramine hõlpsaks, esitades need lihtsa täpploendina.</strong> Püüa hoida reegli punktid lühikesed ja lihtsad, kuid ära jaga neid ka paljudeks eraldi üksusteks.
edit: Reegli muutmine edit: Reegli muutmine
empty: Serveri reegleid pole veel defineeritud. empty: Serveri reegleid pole veel defineeritud.
move_down: Liiguta alla
move_up: Liiguta üles
title: Serveri reeglid title: Serveri reeglid
translation: Tõlge
translations: Tõlked
settings: settings:
about: about:
manage_rules: Halda serveri reegleid manage_rules: Halda serveri reegleid
@ -771,6 +783,7 @@ et:
discovery: discovery:
follow_recommendations: Jälgi soovitusi follow_recommendations: Jälgi soovitusi
preamble: Huvitava sisu esiletoomine on oluline uute kasutajate kaasamisel, kes ei pruugi Mastodonist kedagi tunda. Kontrolli, kuidas erinevad avastamisfunktsioonid serveris töötavad. preamble: Huvitava sisu esiletoomine on oluline uute kasutajate kaasamisel, kes ei pruugi Mastodonist kedagi tunda. Kontrolli, kuidas erinevad avastamisfunktsioonid serveris töötavad.
privacy: Privaatsus
profile_directory: Kasutajate kataloog profile_directory: Kasutajate kataloog
public_timelines: Avalikud ajajooned public_timelines: Avalikud ajajooned
publish_statistics: Avalda statistika publish_statistics: Avalda statistika
@ -819,6 +832,7 @@ et:
batch: batch:
remove_from_report: Eemalda raportist remove_from_report: Eemalda raportist
report: Raport report: Raport
contents: Sisu
deleted: Kustutatud deleted: Kustutatud
favourites: Lemmikud favourites: Lemmikud
history: Versiooniajalugu history: Versiooniajalugu
@ -927,6 +941,9 @@ et:
explanation_html: Esitatud teenusetingimuste näidis on mõeldud ainult teavitamise eesmärgil ja seda ei tohiks tõlgendada kui juriidilist nõuannet mis tahes küsimuses. Palun konsulteeri olukorra ja konkreetsete juriidiliste küsimuste osas oma õigusnõustajaga. explanation_html: Esitatud teenusetingimuste näidis on mõeldud ainult teavitamise eesmärgil ja seda ei tohiks tõlgendada kui juriidilist nõuannet mis tahes küsimuses. Palun konsulteeri olukorra ja konkreetsete juriidiliste küsimuste osas oma õigusnõustajaga.
title: Teenuse tingimuste seadistamine title: Teenuse tingimuste seadistamine
history: Ajalugu history: Ajalugu
publish: Postita
published_on_html: Postitatud %{date}
title: Kasutustingimused
title: Administreerimine title: Administreerimine
trends: trends:
allow: Luba allow: Luba
@ -1163,8 +1180,8 @@ et:
new_confirmation_instructions_sent: Saad mõne minuti pärast uue kinnituslingiga e-kirja! new_confirmation_instructions_sent: Saad mõne minuti pärast uue kinnituslingiga e-kirja!
title: Kontrolli sisendkasti title: Kontrolli sisendkasti
sign_in: sign_in:
preamble_html: Logi sisse oma <strong>%{domain}</strong> volitustega. Kui konto asub teises serveris, ei saa siin sisse logida. preamble_html: Logi sisse oma kasutajakontoga serveris <strong>%{domain}</strong>. Kui konto asub teises serveris, siis sa ei saa siin sisse logida.
title: Logi sisse kohta %{domain} title: Logi sisse serverisse %{domain}
sign_up: sign_up:
manual_review: Liitumised kohas %{domain} vaadatakse meie moderaatorite poolt käsitsi läbi. Aitamaks meil sinu taotlust läbi vaadata, kirjuta palun natuke endast ja miks soovid kontot kohas %{domain}. manual_review: Liitumised kohas %{domain} vaadatakse meie moderaatorite poolt käsitsi läbi. Aitamaks meil sinu taotlust läbi vaadata, kirjuta palun natuke endast ja miks soovid kontot kohas %{domain}.
preamble: Selle Mastodoni serveri kontoga saad jälgida mistahes teist isikut fediversumis, sõltumata sellest, kus ta konto on majutatud. preamble: Selle Mastodoni serveri kontoga saad jälgida mistahes teist isikut fediversumis, sõltumata sellest, kus ta konto on majutatud.
@ -1677,6 +1694,7 @@ et:
scheduled_statuses: scheduled_statuses:
over_daily_limit: Lubatud ajastatud postituste arv %{limit} päevas on tänaseks ületatud over_daily_limit: Lubatud ajastatud postituste arv %{limit} päevas on tänaseks ületatud
over_total_limit: Oled jõudnud ajastatud postituste lubatud maksimumarvuni %{limit} over_total_limit: Oled jõudnud ajastatud postituste lubatud maksimumarvuni %{limit}
too_soon: kuupäev peab olema tulevikus
self_destruct: self_destruct:
lead_html: Kahjuks suletakse <strong>%{domain}</strong> lõplikult. Kui sul oli seal konto, ei saa sa seda enam kasutada, kuid siiski võid taotleda oma andmete varukoopiat. lead_html: Kahjuks suletakse <strong>%{domain}</strong> lõplikult. Kui sul oli seal konto, ei saa sa seda enam kasutada, kuid siiski võid taotleda oma andmete varukoopiat.
title: See server suletakse title: See server suletakse
@ -1788,6 +1806,8 @@ et:
limit: Kinnitatud on juba maksimaalne arv postitusi limit: Kinnitatud on juba maksimaalne arv postitusi
ownership: Kellegi teise postitust ei saa kinnitada ownership: Kellegi teise postitust ei saa kinnitada
reblog: Jagamist ei saa kinnitada reblog: Jagamist ei saa kinnitada
quote_policies:
followers: Jälgijad ja mainitud kasutajad
title: '%{name}: "%{quote}"' title: '%{name}: "%{quote}"'
visibilities: visibilities:
direct: Otsene direct: Otsene
@ -1841,6 +1861,8 @@ et:
too_late: On hilja seda juhtumit vaidlustada too_late: On hilja seda juhtumit vaidlustada
tags: tags:
does_not_match_previous_name: ei ühti eelmise nimega does_not_match_previous_name: ei ühti eelmise nimega
terms_of_service:
title: Kasutustingimused
themes: themes:
contrast: Mastodon (Kõrge kontrast) contrast: Mastodon (Kõrge kontrast)
default: Mastodon (Tume) default: Mastodon (Tume)
@ -1864,7 +1886,7 @@ et:
enabled: Kaheastmeline autentimine on lubatud enabled: Kaheastmeline autentimine on lubatud
enabled_success: Kaheastmeline autentimine lubatud enabled_success: Kaheastmeline autentimine lubatud
generate_recovery_codes: Loo taastekoodid generate_recovery_codes: Loo taastekoodid
lost_recovery_codes: Taastekoodide abil on võimalik telefoni kaotsimineku puhul kontole siseneda. Taastekoodide puudumisel saab need siin luua. Eelnevad taastekoodid kaotavad kehtivuse. lost_recovery_codes: Taastekoodide abil on võimalik telefoni kaotsimineku puhul kontole sisse logida. Taastekoodide puudumisel saad need siin luua. Eelnevad taastekoodid kaotavad kehtivuse.
methods: Kaheastmelised meetodid methods: Kaheastmelised meetodid
otp: Autentimisrakendus otp: Autentimisrakendus
recovery_codes: Taastekoodide varundamine recovery_codes: Taastekoodide varundamine
@ -1872,6 +1894,9 @@ et:
recovery_instructions_html: Kui telefon peaks kaotsi minema, on võimalik kontole sisenemisel kasutada ühte järgnevatest taastekoodidest. <strong>Hoia taastekoode turvaliselt</strong>. Näiteks võib neid prindituna hoida koos teiste tähtsate dokumentidega. recovery_instructions_html: Kui telefon peaks kaotsi minema, on võimalik kontole sisenemisel kasutada ühte järgnevatest taastekoodidest. <strong>Hoia taastekoode turvaliselt</strong>. Näiteks võib neid prindituna hoida koos teiste tähtsate dokumentidega.
webauthn: Turvavõtmed webauthn: Turvavõtmed
user_mailer: user_mailer:
announcement_published:
subject: Saidi teadaanne teenuste kohta
title: "%{domain} saidi teadaanne teenuste kohta"
appeal_approved: appeal_approved:
action: Konto seaded action: Konto seaded
explanation: "%{appeal_date} esitatud vaidlustus %{strike_date} otsuse kohta on rahuldatud. Konto on ennistatud." explanation: "%{appeal_date} esitatud vaidlustus %{strike_date} otsuse kohta on rahuldatud. Konto on ennistatud."
@ -1901,6 +1926,11 @@ et:
further_actions_html: Kui see tuleb üllatusena, soovitame viivitamata %{action} ja lülitada konto turvamiseks sisse kaheastmeline autentimine. further_actions_html: Kui see tuleb üllatusena, soovitame viivitamata %{action} ja lülitada konto turvamiseks sisse kaheastmeline autentimine.
subject: Kontole sisenemine uuelt IP-aadressilt subject: Kontole sisenemine uuelt IP-aadressilt
title: Uus sisenemine title: Uus sisenemine
terms_of_service_changed:
sign_off: "%{domain} saidi tiim"
subject: Meie kasutustingimuste uuendused
subtitle: "%{domain} saidi kasutustingimused muutuvad"
title: Oluline uuendus
warning: warning:
appeal: Vaidlustuse esitamine appeal: Vaidlustuse esitamine
appeal_description: Kui see võib olla eksitus, on võimalik %{instance} haldajatele esitada vaidlustus. appeal_description: Kui see võib olla eksitus, on võimalik %{instance} haldajatele esitada vaidlustus.

View File

@ -814,17 +814,26 @@ gd:
title: Dreuchdan title: Dreuchdan
rules: rules:
add_new: Cuir riaghailt ris add_new: Cuir riaghailt ris
add_translation: Cuir eadar-theangachadh ris
delete: Sguab às delete: Sguab às
description_html: Ged a dhinnseas a mhòrchuid gun do leugh iad teirmichean na seirbheise is gu bheil iad ag aontachadh riutha, s ann mar as trice nach lean daoine orra gan leughadh gun deireadh nuair a thachras iad ri duilgheadas. <strong>Dèan e nas fhasa dhaibh gun tuig iad riaghailtean an fhrithealaiche ann am priobadh na sùla is tu a toirt liosta peilearaichte dhaibh.</strong> Feuch an cùm thu gach riaghailt goirid is sìmplidh ach feuch nach sgaoil thu ann an iomadh nì iad nas motha. description_html: Ged a dhinnseas a mhòrchuid gun do leugh iad teirmichean na seirbheise is gu bheil iad ag aontachadh riutha, s ann mar as trice nach lean daoine orra gan leughadh gun deireadh nuair a thachras iad ri duilgheadas. <strong>Dèan e nas fhasa dhaibh gun tuig iad riaghailtean an fhrithealaiche ann am priobadh na sùla is tu a toirt liosta peilearaichte dhaibh.</strong> Feuch an cùm thu gach riaghailt goirid is sìmplidh ach feuch nach sgaoil thu ann an iomadh nì iad nas motha.
edit: Deasaich an riaghailt edit: Deasaich an riaghailt
empty: Cha deach riaghailtean an fhrithealaiche a mhìneachadh fhathast. empty: Cha deach riaghailtean an fhrithealaiche a mhìneachadh fhathast.
move_down: Gluais sìos
move_up: Gluais suas
title: Riaghailtean an fhrithealaiche title: Riaghailtean an fhrithealaiche
translation: Eadar-theangachadh
translations: Eadar-theangachaidhean
translations_explanation: "S urrainn dhut na riaghailtean eadar-theangachadh ma thogras tu. Thèid an luach bunaiteach a shealltainn mur eil eadar-theangachadh ann. Dèan cinnteach an-còmhnaidh gur ionnann an t-eadar-theangachadh s an luach bunaiteach."
settings: settings:
about: about:
manage_rules: Stiùirich riaghailtean an fhrithealaiche manage_rules: Stiùirich riaghailtean an fhrithealaiche
preamble: Solair fiosrachadh domhainn mu sholar, maorsainneachd is maoineachadh an fhrithealaiche seo. preamble: Solair fiosrachadh domhainn mu sholar, maorsainneachd is maoineachadh an fhrithealaiche seo.
rules_hint: Tha roinn sònraichte ann dha na riaghailtean air am bu chòir an luchd-cleachdaidh agad a leantainn. rules_hint: Tha roinn sònraichte ann dha na riaghailtean air am bu chòir an luchd-cleachdaidh agad a leantainn.
title: Mu dhèidhinn title: Mu dhèidhinn
allow_referrer_origin:
desc: Nuair a bhriogas an luchd-cleachdaidh agad ceanglaichean gu làraichean air ann taobh a-muigh, dhfhaoidte gun cuir am brabhsair aca seòladh an fhrithealaiche Mastodon agad thuca mar an referrer. Cuir seo à comas ma dhaithnicheadh sin an luchd-cleachdaidh fa leth agad, can mas e frithealaiche Mastodon pearsanta a th ann.
title: Ceadaich gum faic làraichean air an taobh a-muigh an fhrithealaiche Mastodon agad mar thùs trafaige
appearance: appearance:
preamble: Gnàthaich eadar-aghaidh-lìn Mhastodon. preamble: Gnàthaich eadar-aghaidh-lìn Mhastodon.
title: Coltas title: Coltas
@ -844,6 +853,7 @@ gd:
discovery: discovery:
follow_recommendations: Molaidhean leantainn follow_recommendations: Molaidhean leantainn
preamble: Tha tighinn an uachdar susbainte inntinniche fìor-chudromach airson toiseach-tòiseachaidh an luchd-cleachdaidh ùr nach eil eòlach air duine sam bith air Mastodon, ma dhfhaoidte. Stiùirich mar a dhobraicheas gleusan an rùrachaidh air an fhrithealaiche agad. preamble: Tha tighinn an uachdar susbainte inntinniche fìor-chudromach airson toiseach-tòiseachaidh an luchd-cleachdaidh ùr nach eil eòlach air duine sam bith air Mastodon, ma dhfhaoidte. Stiùirich mar a dhobraicheas gleusan an rùrachaidh air an fhrithealaiche agad.
privacy: Prìobhaideachd
profile_directory: Eòlaire nam pròifil profile_directory: Eòlaire nam pròifil
public_timelines: Loidhnichean-ama poblach public_timelines: Loidhnichean-ama poblach
publish_statistics: Foillsich an stadastaireachd publish_statistics: Foillsich an stadastaireachd
@ -1943,6 +1953,10 @@ gd:
limit: Tha an àireamh as motha de phostaichean prìnichte agad a tha ceadaichte limit: Tha an àireamh as motha de phostaichean prìnichte agad a tha ceadaichte
ownership: Chan urrainn dhut post càich a phrìneachadh ownership: Chan urrainn dhut post càich a phrìneachadh
reblog: Chan urrainn dhut brosnachadh a phrìneachadh reblog: Chan urrainn dhut brosnachadh a phrìneachadh
quote_policies:
followers: Luchd-leantainn s cleachdaichean le iomradh orra
nobody: Cleachdaichean le iomradh orra a-mhàin
public: A h-uile duine
title: "%{name}: “%{quote}”" title: "%{name}: “%{quote}”"
visibilities: visibilities:
direct: Dìreach direct: Dìreach
@ -1996,6 +2010,11 @@ gd:
does_not_match_previous_name: " chan eil seo a-rèir an ainm roimhe" does_not_match_previous_name: " chan eil seo a-rèir an ainm roimhe"
terms_of_service: terms_of_service:
title: Teirmichean na seirbheise title: Teirmichean na seirbheise
terms_of_service_interstitial:
future_preamble_html: Tha sinn ag atharrachadh teirmichean na seirbheise againn a bhios èifeachdach on <strong>%{date}</strong> a-mach. Mholamaid gun dèan thu lèirmheas air na teirmichean ùra.
past_preamble_html: Dhatharraich sinn teirmichean na seirbheise againn on turas mu dheireadh a thadhail thu oirnn. Mholamaid gun dèan thu lèirmheas air na teirmichean ùra.
review_link: Dèan lèirmheas air teirmichean na seirbheise
title: Tha teirmichean na seirbheise aig %{domain} gu bhith atharrachadh
themes: themes:
contrast: Mastodon (iomsgaradh àrd) contrast: Mastodon (iomsgaradh àrd)
default: Mastodon (dorcha) default: Mastodon (dorcha)

View File

@ -1884,6 +1884,7 @@ ko:
terms_of_service: terms_of_service:
title: 이용 약관 title: 이용 약관
terms_of_service_interstitial: terms_of_service_interstitial:
future_preamble_html: 이용약관에 몇가지 변경사항이 있으며, <strong>%{date}</strong>부터 효력이 발생합니다. 변경된 약관을 확인하시기 바랍니다.
review_link: 이용 약관 검토하기 review_link: 이용 약관 검토하기
title: "%{domain} 도메인의 이용 약관에 변경 있음" title: "%{domain} 도메인의 이용 약관에 변경 있음"
themes: themes:

View File

@ -54,9 +54,9 @@ de:
password: Verwende mindestens 8 Zeichen password: Verwende mindestens 8 Zeichen
phrase: Wird unabhängig von der Groß- und Kleinschreibung im Text oder der Inhaltswarnung eines Beitrags abgeglichen phrase: Wird unabhängig von der Groß- und Kleinschreibung im Text oder der Inhaltswarnung eines Beitrags abgeglichen
scopes: Welche Schnittstellen der Applikation erlaubt sind. Wenn du einen Top-Level-Scope auswählst, dann musst du nicht jeden einzelnen darunter auswählen. scopes: Welche Schnittstellen der Applikation erlaubt sind. Wenn du einen Top-Level-Scope auswählst, dann musst du nicht jeden einzelnen darunter auswählen.
setting_aggregate_reblogs: Beiträge, die erst kürzlich geteilt wurden, werden nicht noch einmal angezeigt (wirkt sich nur auf zukünftige geteilte Beiträge aus) setting_aggregate_reblogs: Beiträge, die erst kürzlich geteilt wurden, werden nicht noch einmal angezeigt (betrifft nur zukünftig geteilte Beiträge)
setting_always_send_emails: Normalerweise werden Benachrichtigungen nicht per E-Mail versendet, wenn du gerade auf Mastodon aktiv bist setting_always_send_emails: Normalerweise werden Benachrichtigungen nicht per E-Mail versendet, wenn du gerade auf Mastodon aktiv bist
setting_default_quote_policy: Erwähnte Profile dürfen immer zitieren. Diese Einstellung wirkt sich nur für Beiträge aus, die mit der zukünftigen Mastodon-Version erstellt wurden. Als Vorbereitung darauf kannst du bereits jetzt die Einstellung vornehmen. setting_default_quote_policy: Erwähnte Profile dürfen immer zitieren. Diese Einstellung gilt nur für Beiträge, die mit der zukünftigen Mastodon-Version erstellt wurden. Als Vorbereitung darauf kannst du bereits jetzt die Einstellung vornehmen
setting_default_sensitive: Medien, die mit einer Inhaltswarnung versehen worden sind, werden je nach Einstellung erst nach einem zusätzlichen Klick angezeigt setting_default_sensitive: Medien, die mit einer Inhaltswarnung versehen worden sind, werden je nach Einstellung erst nach einem zusätzlichen Klick angezeigt
setting_display_media_default: Medien mit Inhaltswarnung ausblenden setting_display_media_default: Medien mit Inhaltswarnung ausblenden
setting_display_media_hide_all: Medien immer ausblenden setting_display_media_hide_all: Medien immer ausblenden

View File

@ -317,6 +317,13 @@ et:
name: Silt name: Silt
trendable: Luba sellel sildil trendida trendable: Luba sellel sildil trendida
usable: Luba seda märksõna postitustes kasutada lokaalselt usable: Luba seda märksõna postitustes kasutada lokaalselt
terms_of_service:
changelog: Mis on muutunud?
effective_date: Jõustumise kuupäev
text: Kasutustingimused
terms_of_service_generator:
admin_email: E-posti aadress juriidiliste teadete jaoks
jurisdiction: Jurisdiktsioon
user: user:
role: Roll role: Roll
time_zone: Ajavöönd time_zone: Ajavöönd

View File

@ -56,6 +56,7 @@ gd:
scopes: Na APIan a dhfhaodas an aplacaid inntrigeadh. Ma thaghas tu sgòp air ìre as àirde, cha leig thu leas sgòpaichean fa leth a thaghadh. scopes: Na APIan a dhfhaodas an aplacaid inntrigeadh. Ma thaghas tu sgòp air ìre as àirde, cha leig thu leas sgòpaichean fa leth a thaghadh.
setting_aggregate_reblogs: Na seall brosnachaidhean ùra do phostaichean a chaidh a bhrosnachadh o chionn goirid (cha doir seo buaidh ach air brosnachaidhean ùra o seo a-mach) setting_aggregate_reblogs: Na seall brosnachaidhean ùra do phostaichean a chaidh a bhrosnachadh o chionn goirid (cha doir seo buaidh ach air brosnachaidhean ùra o seo a-mach)
setting_always_send_emails: Mar as àbhaist, cha dèid brathan puist-d a chur nuair a a bhios tu ri Mastodon gu cunbhalach setting_always_send_emails: Mar as àbhaist, cha dèid brathan puist-d a chur nuair a a bhios tu ri Mastodon gu cunbhalach
setting_default_quote_policy: Faodaidh luchd-cleachdaidh le iomradh orra luaidh an-còmhnaidh. Cha bhi an roghainn seo ann sàs ach air postaichean a thèid a chruthachadh leis an ath-thionndadh de Mhastodon ach s urrainn dhut do roghainn a thaghadh airson ullachadh dha.
setting_default_sensitive: Thèid meadhanan frionasach fhalach a ghnàth is gabhaidh an nochdadh le briogadh orra setting_default_sensitive: Thèid meadhanan frionasach fhalach a ghnàth is gabhaidh an nochdadh le briogadh orra
setting_display_media_default: Falaich meadhanan ris a bheil comharra gu bheil iad frionasach setting_display_media_default: Falaich meadhanan ris a bheil comharra gu bheil iad frionasach
setting_display_media_hide_all: Falaich na meadhanan an-còmhnaidh setting_display_media_hide_all: Falaich na meadhanan an-còmhnaidh
@ -148,6 +149,11 @@ gd:
min_age: Cha bu chòir seo a bhith fon aois as lugha a dhiarras laghain an t-uachdranais laghail agad. min_age: Cha bu chòir seo a bhith fon aois as lugha a dhiarras laghain an t-uachdranais laghail agad.
user: user:
chosen_languages: Nuair a bhios cromag ris, cha nochd ach postaichean sna cànain a thagh thu air loidhnichean-ama poblach chosen_languages: Nuair a bhios cromag ris, cha nochd ach postaichean sna cànain a thagh thu air loidhnichean-ama poblach
date_of_birth:
few: Feumaidh sinn dèanamh cinnteach gu bheil thu %{count} bliadhnaichean a dhaois air a char as lugha mus cleachd thu Mastodon. Cha chlàraich sinn seo.
one: Feumaidh sinn dèanamh cinnteach gu bheil thu %{count} bhliadhna a dhaois air a char as lugha mus cleachd thu Mastodon. Cha chlàraich sinn seo.
other: Feumaidh sinn dèanamh cinnteach gu bheil thu %{count} bliadhna a dhaois air a char as lugha mus cleachd thu Mastodon. Cha chlàraich sinn seo.
two: Feumaidh sinn dèanamh cinnteach gu bheil thu %{count} bhliadhna a dhaois air a char as lugha mus cleachd thu Mastodon. Cha chlàraich sinn seo.
role: Stiùiridh an dreuchd dè na ceadan a bhios aig cleachdaiche. role: Stiùiridh an dreuchd dè na ceadan a bhios aig cleachdaiche.
user_role: user_role:
color: An datha a bhios air an dreuchd air feadh na h-eadar-aghaidh, na RGB san fhòrmat sia-dheicheach color: An datha a bhios air an dreuchd air feadh na h-eadar-aghaidh, na RGB san fhòrmat sia-dheicheach
@ -228,6 +234,7 @@ gd:
setting_boost_modal: Seall còmhradh dearbhaidh mus dèan thu brosnachadh setting_boost_modal: Seall còmhradh dearbhaidh mus dèan thu brosnachadh
setting_default_language: Cànan postaidh setting_default_language: Cànan postaidh
setting_default_privacy: Prìobhaideachd postaidh setting_default_privacy: Prìobhaideachd postaidh
setting_default_quote_policy: Cò dhfhaodas luaidh
setting_default_sensitive: Cuir comharra ri meadhanan an-còmhnaidh gu bheil iad frionasach setting_default_sensitive: Cuir comharra ri meadhanan an-còmhnaidh gu bheil iad frionasach
setting_delete_modal: Seall còmhradh dearbhaidh mus sguab thu às post setting_delete_modal: Seall còmhradh dearbhaidh mus sguab thu às post
setting_disable_hover_cards: Na ro-sheall pròifil nuair a dhfhanas mi os a cionn setting_disable_hover_cards: Na ro-sheall pròifil nuair a dhfhanas mi os a cionn

View File

@ -56,6 +56,7 @@ ko:
scopes: 애플리케이션에 허용할 API들입니다. 최상위 스코프를 선택하면 개별적인 것은 선택하지 않아도 됩니다. scopes: 애플리케이션에 허용할 API들입니다. 최상위 스코프를 선택하면 개별적인 것은 선택하지 않아도 됩니다.
setting_aggregate_reblogs: 최근에 부스트 됐던 게시물은 새로 부스트 되어도 보여주지 않기 (새로 받은 부스트에만 적용됩니다) setting_aggregate_reblogs: 최근에 부스트 됐던 게시물은 새로 부스트 되어도 보여주지 않기 (새로 받은 부스트에만 적용됩니다)
setting_always_send_emails: 기본적으로 마스토돈을 활동적으로 사용하고 있을 때에는 이메일 알림이 보내지지 않습니다 setting_always_send_emails: 기본적으로 마스토돈을 활동적으로 사용하고 있을 때에는 이메일 알림이 보내지지 않습니다
setting_default_quote_policy: 멘션된 사용자는 항상 인용할 수 있도록 허용됩니다. 이 설정은 다음 마스토돈 버전부터 효과가 적용되지만 미리 준비할 수 있도록 설정을 제공합니다
setting_default_sensitive: 민감한 미디어는 기본적으로 가려져 있으며 클릭해서 볼 수 있습니다 setting_default_sensitive: 민감한 미디어는 기본적으로 가려져 있으며 클릭해서 볼 수 있습니다
setting_display_media_default: 민감함으로 표시된 미디어 가리기 setting_display_media_default: 민감함으로 표시된 미디어 가리기
setting_display_media_hide_all: 모든 미디어를 가리기 setting_display_media_hide_all: 모든 미디어를 가리기
@ -146,6 +147,8 @@ ko:
min_age: 관할지역의 법률에서 요구하는 최저 연령보다 작으면 안 됩니다. min_age: 관할지역의 법률에서 요구하는 최저 연령보다 작으면 안 됩니다.
user: user:
chosen_languages: 체크하면, 선택 된 언어로 작성된 게시물들만 공개 타임라인에 보여집니다 chosen_languages: 체크하면, 선택 된 언어로 작성된 게시물들만 공개 타임라인에 보여집니다
date_of_birth:
other: 마스토돈을 사용하려면 %{count}세 이상임을 확인해야 합니다. 이 정보는 저장되지 않습니다.
role: 역할은 사용자가 어떤 권한을 가지게 될 지 결정합니다. role: 역할은 사용자가 어떤 권한을 가지게 될 지 결정합니다.
user_role: user_role:
color: 색상은 사용자 인터페이스에서 역할을 나타내기 위해 사용되며, RGB 16진수 형식입니다 color: 색상은 사용자 인터페이스에서 역할을 나타내기 위해 사용되며, RGB 16진수 형식입니다

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddLegacyToQuotes < ActiveRecord::Migration[8.0]
def change
add_column :quotes, :legacy, :boolean, null: false, default: false
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_05_20_204643) do ActiveRecord::Schema[8.0].define(version: 2025_06_05_110215) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -906,6 +906,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_20_204643) do
t.string "activity_uri" t.string "activity_uri"
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "legacy", default: false, null: false
t.index ["account_id", "quoted_account_id"], name: "index_quotes_on_account_id_and_quoted_account_id" t.index ["account_id", "quoted_account_id"], name: "index_quotes_on_account_id_and_quoted_account_id"
t.index ["activity_uri"], name: "index_quotes_on_activity_uri", unique: true, where: "(activity_uri IS NOT NULL)" t.index ["activity_uri"], name: "index_quotes_on_activity_uri", unique: true, where: "(activity_uri IS NOT NULL)"
t.index ["approval_uri"], name: "index_quotes_on_approval_uri", where: "(approval_uri IS NOT NULL)" t.index ["approval_uri"], name: "index_quotes_on_approval_uri", where: "(approval_uri IS NOT NULL)"

View File

@ -23,7 +23,7 @@ module Mastodon
def announce_countdown def announce_countdown
WARNING_SECONDS.downto(1) do |i| WARNING_SECONDS.downto(1) do |i|
say "Continuing in #{i} second#{i == 1 ? '' : 's'}...", true say "Continuing in #{i} second#{'s' unless i == 1}...", true
sleep 1 sleep 1
end end
end end

View File

@ -75,7 +75,7 @@ module Mastodon
end end
def user_agent def user_agent
@user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)" @user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{'s' if Rails.configuration.x.use_https}://#{Rails.configuration.x.web_domain}/)"
end end
def version_configuration def version_configuration

View File

@ -57,7 +57,7 @@ namespace :repo do
response = nil response = nil
loop do loop do
response = HTTP.headers('Authorization' => "token #{ENV['GITHUB_API_TOKEN']}").get("https://api.github.com/repos/#{REPOSITORY_NAME}/pulls/#{pull_request_number}") response = HTTP.headers('Authorization' => "token #{ENV.fetch('GITHUB_API_TOKEN')}").get("https://api.github.com/repos/#{REPOSITORY_NAME}/pulls/#{pull_request_number}")
if response.code == 403 if response.code == 403
sleep_for = (response.headers['X-RateLimit-Reset'].to_i - Time.now.to_i).abs sleep_for = (response.headers['X-RateLimit-Reset'].to_i - Time.now.to_i).abs

View File

@ -1,7 +1,7 @@
{ {
"name": "@mastodon/mastodon", "name": "@mastodon/mastodon",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.9.1", "packageManager": "yarn@4.9.2",
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },

View File

@ -11,13 +11,13 @@ RSpec.describe 'Account notes', :inline_jobs, :js, :streaming do
let(:finished_onboarding) { true } let(:finished_onboarding) { true }
let!(:other_account) { Fabricate(:account) } let!(:other_account) { Fabricate(:account) }
let(:note_text) { 'This is a personal note' }
before { as_a_logged_in_user } before { as_a_logged_in_user }
it 'can be written and viewed' do it 'can be written and viewed' do
visit_profile(other_account) visit_profile(other_account)
note_text = 'This is a personal note'
fill_in frontend_translations('account_note.placeholder'), with: note_text fill_in frontend_translations('account_note.placeholder'), with: note_text
# This is a bit awkward since there is no button to save the change # This is a bit awkward since there is no button to save the change

View File

@ -9,12 +9,12 @@ RSpec.describe 'NewStatuses', :inline_jobs, :js, :streaming do
let(:password) { 'password' } let(:password) { 'password' }
let(:confirmed_at) { Time.zone.now } let(:confirmed_at) { Time.zone.now }
let(:finished_onboarding) { true } let(:finished_onboarding) { true }
let(:status_text) { 'This is a new status!' }
before { as_a_logged_in_user } before { as_a_logged_in_user }
it 'can be posted' do it 'can be posted' do
visit_homepage visit_homepage
status_text = 'This is a new status!'
within('.compose-form') do within('.compose-form') do
fill_in frontend_translations('compose_form.placeholder'), with: status_text fill_in frontend_translations('compose_form.placeholder'), with: status_text

View File

@ -1,7 +1,7 @@
{ {
"name": "@mastodon/streaming", "name": "@mastodon/streaming",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.9.1", "packageManager": "yarn@4.9.2",
"engines": { "engines": {
"node": ">=20" "node": ">=20"
}, },

View File

@ -9194,14 +9194,14 @@ __metadata:
linkType: hard linkType: hard
"pino-http@npm:^10.0.0": "pino-http@npm:^10.0.0":
version: 10.4.0 version: 10.5.0
resolution: "pino-http@npm:10.4.0" resolution: "pino-http@npm:10.5.0"
dependencies: dependencies:
get-caller-file: "npm:^2.0.5" get-caller-file: "npm:^2.0.5"
pino: "npm:^9.0.0" pino: "npm:^9.0.0"
pino-std-serializers: "npm:^7.0.0" pino-std-serializers: "npm:^7.0.0"
process-warning: "npm:^4.0.0" process-warning: "npm:^5.0.0"
checksum: 10c0/64144e2c94e939070f56ad82dfb012b6a98d21582e0660cf821e7cee64d4e06f7724aa40bc5bf9cd1254d58ab7cbd972dec287b7989eba647d384f6edd8d95fd checksum: 10c0/17597d653a4088f7faed4d58500a5ef51d4d05247307696760006313c33c1d23177af98fb902e15b8e2cd92d81306c884673f841ba5b9bf0c064802f3c0bd775
languageName: node languageName: node
linkType: hard linkType: hard
@ -9765,13 +9765,6 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"process-warning@npm:^4.0.0":
version: 4.0.0
resolution: "process-warning@npm:4.0.0"
checksum: 10c0/5312a72b69d37a1b82ad03f3dfa0090dab3804a8fd995d06c28e3c002852bd82f5584217d9f4a3f197892bb2afc22d57e2c662c7e906b5abb48c0380c7b0880d
languageName: node
linkType: hard
"process-warning@npm:^5.0.0": "process-warning@npm:^5.0.0":
version: 5.0.0 version: 5.0.0
resolution: "process-warning@npm:5.0.0" resolution: "process-warning@npm:5.0.0"