mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-13 07:49:29 +00:00
Compare commits
68 Commits
v4.5.0-rc.
...
v4.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2506cc110 | ||
|
|
d18491b7a7 | ||
|
|
13330030cd | ||
|
|
f9012a774c | ||
|
|
375af385e7 | ||
|
|
7e5224a3c0 | ||
|
|
140d782cba | ||
|
|
c7a7ce8ce7 | ||
|
|
afbe0a4860 | ||
|
|
febde69d0b | ||
|
|
85b9a5944d | ||
|
|
585827c14f | ||
|
|
bb6093c315 | ||
|
|
058f704c21 | ||
|
|
6baa8f2466 | ||
|
|
e742eff044 | ||
|
|
55b9d21537 | ||
|
|
59f0134578 | ||
|
|
28b9e9087a | ||
|
|
fa2cc409ce | ||
|
|
8a100d84c5 | ||
|
|
9ae0464e8f | ||
|
|
9eea4479e1 | ||
|
|
30103fd2c8 | ||
|
|
a9a7ad62f1 | ||
|
|
ea663cf7c7 | ||
|
|
fbe05d42fb | ||
|
|
29ae9c9c4b | ||
|
|
4684d5e69b | ||
|
|
3ca92c4ae2 | ||
|
|
81b1a34d96 | ||
|
|
26c78392f8 | ||
|
|
caecc88247 | ||
|
|
bed4ca26e2 | ||
|
|
5d108e95d7 | ||
|
|
8a2d38a47b | ||
|
|
048430f4e8 | ||
|
|
d45b4db1d7 | ||
|
|
ef3a95affc | ||
|
|
3e6a9371b0 | ||
|
|
7ab0cfd637 | ||
|
|
b33bcc8be6 | ||
|
|
cdad6ee0c9 | ||
|
|
2d958cb909 | ||
|
|
949f15e200 | ||
|
|
105a2d64a7 | ||
|
|
1c17990413 | ||
|
|
0a8f96d3be | ||
|
|
1ec8e42dbb | ||
|
|
7ea3c6a039 | ||
|
|
e91c764590 | ||
|
|
cfdd9396c0 | ||
|
|
ba498ae779 | ||
|
|
5bae08d1ff | ||
|
|
5253527ec4 | ||
|
|
0b50789c5b | ||
|
|
a978e37f4c | ||
|
|
dd708298a8 | ||
|
|
449eb03f11 | ||
|
|
1baede0a7c | ||
|
|
a7ecfc1ca5 | ||
|
|
e62baacfc1 | ||
|
|
b5a6feb3bf | ||
|
|
05964f571b | ||
|
|
16a54f7158 | ||
|
|
6d53ca63d6 | ||
|
|
93acfdd7d3 | ||
|
|
a209b8e544 |
4
.github/workflows/build-releases.yml
vendored
4
.github/workflows/build-releases.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
# Only tag with latest when ran against the latest stable branch
|
||||
# This needs to be updated after each minor version release
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.5.') }}
|
||||
tags: |
|
||||
type=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
# Only tag with latest when ran against the latest stable branch
|
||||
# This needs to be updated after each minor version release
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.5.') }}
|
||||
tags: |
|
||||
type=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -2,17 +2,33 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.5.0] - UNRELEASED
|
||||
## [4.5.1] - 2025-11-13
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix Cmd/Ctrl + Enter not submitting Alt text modal on some browsers (#36866 by @diondiondion)
|
||||
- Fix posts coming from public/hashtag streaming being marked as unquotable (#36860 and #36869 by @ClearlyClaire)
|
||||
- Fix old previously-undiscovered posts being treated as new when receiving an `Update` (#36848 by @ClearlyClaire)
|
||||
- Fix blank screen in browsers that don't support `Intl.DisplayNames` (#36847 by @diondiondion)
|
||||
- Fix filters not being applied to quotes in detailed view (#36843 by @ClearlyClaire)
|
||||
- Fix scroll shift caused by fetch-all-replies alerts (#36807 by @diondiondion)
|
||||
- Fix dropdown menu not focusing first item when opened via keyboard (#36804 by @diondiondion)
|
||||
- Fix assets build issue on arch64 (#36781 by @ClearlyClaire)
|
||||
- Fix `/api/v1/statuses/:id/context` sometimes returing `Mastodon-Async-Refresh` without `result_count` (#36779 by @ClearlyClaire)
|
||||
- Fix prepared quote not being discarded with contents when replying (#36778 by @ClearlyClaire)
|
||||
|
||||
## [4.5.0] - 2025-11-06
|
||||
|
||||
### Added
|
||||
|
||||
- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516, #36528, #36549, #36550 and #36559 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\
|
||||
- **Add support for allowing and authoring quotes** (#35355, #35578, #35614, #35618, #35624, #35626, #35652, #35629, #35665, #35653, #35670, #35677, #35690, #35697, #35689, #35699, #35700, #35701, #35709, #35714, #35713, #35715, #35725, #35749, #35769, #35780, #35762, #35804, #35808, #35805, #35819, #35824, #35828, #35822, #35835, #35865, #35860, #35832, #35891, #35894, #35895, #35820, #35917, #35924, #35925, #35914, #35930, #35941, #35939, #35948, #35955, #35967, #35990, #35991, #35975, #35971, #36002, #35986, #36031, #36034, #36038, #36054, #36052, #36055, #36065, #36068, #36083, #36087, #36080, #36091, #36090, #36118, #36119, #36128, #36094, #36129, #36138, #36132, #36151, #36158, #36171, #36194, #36220, #36169, #36130, #36249, #36153, #36299, #36291, #36301, #36315, #36317, #36364, #36383, #36381, #36459, #36464, #36461, #36516, #36528, #36549, #36550, #36559, #36693, #36704, #36690, #36689, #36696, #36721, #36695 and #36736 by @ChaosExAnima, @ClearlyClaire, @Lycolia, @diondiondion, and @tribela)\
|
||||
This includes a revamp of the composer interface.\
|
||||
See https://blog.joinmastodon.org/2025/09/introducing-quote-posts/ for a user-centric overview of the feature, and https://docs.joinmastodon.org/client/quotes/ for API documentation.
|
||||
- **Add support for fetching and refreshing replies to the web UI** (#35210, #35496, #35575, #35500, #35577, #35602, #35603, #35654, #36141, #36237, #36172, #36256, #36271, #36334, #36382, #36239, #36484, #36481, #36583, #36627 and #36547 by @ClearlyClaire, @diondiondion, @Gargron and @renchap)
|
||||
- **Add ability to block words in usernames** (#35407, #35655, and #35806 by @ClearlyClaire and @Gargron)
|
||||
- Add ability to individually disable local or remote feeds for visitors or logged-in users `disabled` value to server setting for live and topic feeds, as well as user permission to bypass that (#36338, #36467, #36497, #36563, #36577, #36585, and #36607 by @ClearlyClaire)\
|
||||
This splits the `timeline_preview` setting into four more granular settings controlling live feeds and topic (hashtag, trending link) feeds, with 3 values each: `public`, `authenticated`, `disabled`.\
|
||||
- Add ability to individually disable local or remote feeds for visitors or logged-in users `disabled` value to server setting for live and topic feeds, as well as user permission to bypass that (#36338, #36467, #36497, #36563, #36577, #36585, #36607 and #36703 by @ClearlyClaire)\
|
||||
This splits the `timeline_preview` setting into four more granular settings controlling live feeds and topic (hashtag, trending link) feeds.\
|
||||
The setting for local topic feeds has 2 values: `public` and `authenticated`. Every other setting has 3 values: `public`, `authenticated`, `disabled`.\
|
||||
When `disabled`, users with the “View live and topic feeds” will still be able to view them.
|
||||
- Add support for displaying of quote posts in Moderator UI (#35964 by @ThisIsMissEm)
|
||||
- Add support for displaying link previews for Admin UI (#35958 by @ThisIsMissEm)
|
||||
@@ -20,21 +36,22 @@ All notable changes to this project will be documented in this file.
|
||||
- Add support for `Update` activities on converted object types (#36322 by @ClearlyClaire)
|
||||
- Add support for dynamic viewport height (#36272 by @e1berd)
|
||||
- Add support for numeric-based URIs for new local accounts (#32724, #36304, #36316, and #36365 by @ClearlyClaire)
|
||||
- Add default visualizer for audio upload without poster (#36734 by @ChaosExAnima)
|
||||
- Add Traditional Mongolian to posting languages (#36196 by @shimon1024)
|
||||
- Add example post with manual quote approval policy to `dev:populate_sample_data` (#36099 by @ClearlyClaire)
|
||||
- Add server-side support for handling posts with a quote policy allowing followers to quote (#36093 and #36127 by @ClearlyClaire)
|
||||
- Add schema.org markup to SEO-enabled posts (#36075 by @Gargron)
|
||||
- Add migration to fill unset default quote policy based on default post privacy (#36041 by @ClearlyClaire)
|
||||
- Add “Posting defaults” setting page, moving existing settings from “Other” (#35896, #36033, #35966, #35969, and #36084 by @ClearlyClaire and @diondiondion)
|
||||
- Added emoji from Twemoji v16 (#36501 and #36530 by @ChaosExAnima)
|
||||
- Add feature to select custom emoji rendering (#35229, #35282, #35253, #35424, #35473, #35483, #35505, #35568, #35605, #35659, #35664, #35739, #35985, #36051, #36071, #36137, #36165, #36248, #36262, #36275, #36293, #36341, #36342, #36366, #36377, #36378, #36385, #36393, #36397, #36403, #36413, #36410, #36454, #36402, #36503, #36502, #36532, #36603, #36409, #36638 and #36750 by @ChaosExAnima, @ClearlyClaire and @braddunbar)\
|
||||
This also completely reworks the processing and rendering of emojis and server-rendered HTML in statuses and other places.
|
||||
- Add support for exposing conversation context for new public conversations according to FEP-7888 (#35959 and #36064 by @ClearlyClaire and @jesseplusplus)
|
||||
- Add digest re-check before removing followers in synchronization mechanism (#34273 by @ClearlyClaire)
|
||||
- Add “Posting defaults” setting page, moving existing settings from “Other” (#35896, #36033, #35966, #35969, and #36084 by @ClearlyClaire and @diondiondion)
|
||||
- Add support for displaying Valkey version on admin dashboard (#35785 by @ykzts)
|
||||
- Add delivery failure tracking and handling to FASP jobs (#35625, #35628, and #35723 by @oneiros)
|
||||
- Add example of quote post with a preview card to development sample data (#35616 by @ClearlyClaire)
|
||||
- Add second set of blocked text that applies to accounts regardless of account age for spam-blocking (#35563 by @ClearlyClaire)
|
||||
- Added emoji from Twemoji v16 (#36501 and #36530 by @ChaosExAnima)
|
||||
- Add feature to select custom emoji rendering (#35229, #35282, #35253, #35424, #35473, #35483, #35505, #35568, #35605, #35659, #35664, #35739, #35985, #36051, #36071, #36137, #36165, #36248, #36262, #36275, #36293, #36341, #36342, #36366, #36377, #36378, #36385, #36393, #36397, #36403, #36413, #36410, #36454, #36402, #36503, #36502, #36532, #36603, #36409 and #36638 by @ChaosExAnima, @ClearlyClaire and @braddunbar)\
|
||||
This also completely reworks the processing and rendering of emojis and server-rendered HTML in statuses and other places.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -44,6 +61,8 @@ All notable changes to this project will be documented in this file.
|
||||
- Change display of blocked and muted quoted users (#36619 by @ClearlyClaire)\
|
||||
This adds `blocked_account`, `blocked_domain` and `muted_account` values to the `state` attribute of `Quote` and `ShallowQuote` REST API entities.
|
||||
- Change submitting an empty post to show an error rather than failing silently (#36650 by @diondiondion)
|
||||
- Change "Privacy and reach" settings from "Public profile" to their own top-level category (#27294 by @ChaelCodes)
|
||||
- Change number of times quote verification is retried to better deal with temporary failures (#36698 by @ClearlyClaire)
|
||||
- Change display of content warnings in Admin UI (#35935 by @ThisIsMissEm)
|
||||
- Change styling of column banners (#36531 by @ClearlyClaire)
|
||||
- Change recommended Node version to 24 (LTS) (#36539 by @renchap)
|
||||
@@ -75,6 +94,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Fix URL comparison for mentions in case of empty path (#36613 and #36626 by @ClearlyClaire)
|
||||
- Fix hashtags not being picked up when full-width hash sign is used (#36103 and #36625 by @ClearlyClaire and @Gargron)
|
||||
- Fix layout of severed relationships when purged events are listed (#36593 by @mejofi)
|
||||
- Fix Skeleton placeholders being animated when setting to reduce animations is enabled (#36716 by @ClearlyClaire)
|
||||
- Fix vacuum tasks being interrupted by a single batch failure (#36606 by @Gargron)
|
||||
- Fix handling of unreachable network error for search services (#36587 by @mjankowski)
|
||||
- Fix bookmarks export when a bookmarked status is soft-deleted (#36576 by @ClearlyClaire)
|
||||
|
||||
14
Gemfile.lock
14
Gemfile.lock
@@ -128,7 +128,7 @@ GEM
|
||||
blurhash (0.1.8)
|
||||
bootsnap (1.18.6)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.0.2)
|
||||
brakeman (7.1.1)
|
||||
racc
|
||||
browser (6.2.0)
|
||||
builder (3.3.0)
|
||||
@@ -224,7 +224,7 @@ GEM
|
||||
mail (~> 2.7)
|
||||
email_validator (2.2.4)
|
||||
activemodel
|
||||
erb (5.1.1)
|
||||
erb (5.1.3)
|
||||
erubi (1.13.1)
|
||||
et-orbi (1.4.0)
|
||||
tzinfo
|
||||
@@ -337,7 +337,7 @@ GEM
|
||||
activesupport (>= 3.0)
|
||||
nokogiri (>= 1.6)
|
||||
io-console (0.8.1)
|
||||
irb (1.15.2)
|
||||
irb (1.15.3)
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
@@ -621,7 +621,7 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.8.1)
|
||||
rack (3.2.3)
|
||||
rack (3.2.4)
|
||||
rack-attack (6.8.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (3.0.0)
|
||||
@@ -691,7 +691,7 @@ GEM
|
||||
readline (~> 0.0)
|
||||
rdf-normalize (0.7.0)
|
||||
rdf (~> 3.3)
|
||||
rdoc (6.15.0)
|
||||
rdoc (6.15.1)
|
||||
erb
|
||||
psych (>= 4.0.0)
|
||||
tsort
|
||||
@@ -791,7 +791,7 @@ GEM
|
||||
ruby-vips (2.2.5)
|
||||
ffi (~> 1.12)
|
||||
logger
|
||||
rubyzip (3.2.1)
|
||||
rubyzip (3.2.2)
|
||||
rufus-scheduler (3.9.2)
|
||||
fugit (~> 1.1, >= 1.11.1)
|
||||
safety_net_attestation (0.5.0)
|
||||
@@ -805,7 +805,7 @@ GEM
|
||||
securerandom (0.4.1)
|
||||
shoulda-matchers (6.5.0)
|
||||
activesupport (>= 5.2.0)
|
||||
sidekiq (8.0.8)
|
||||
sidekiq (8.0.9)
|
||||
connection_pool (>= 2.5.0)
|
||||
json (>= 2.9.0)
|
||||
logger (>= 1.6.2)
|
||||
|
||||
@@ -15,7 +15,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ---------------- |
|
||||
| 4.5.x | Yes |
|
||||
| 4.4.x | Yes |
|
||||
| 4.3.x | Yes |
|
||||
| 4.3.x | Until 2026-05-06 |
|
||||
| 4.2.x | Until 2026-01-08 |
|
||||
| < 4.2 | No |
|
||||
|
||||
@@ -9,7 +9,7 @@ class ActivityPub::QuoteAuthorizationsController < ActivityPub::BaseController
|
||||
before_action :set_quote_authorization
|
||||
|
||||
def show
|
||||
expires_in 30.seconds, public: true if @quote.status.distributable? && public_fetch_mode?
|
||||
expires_in 30.seconds, public: true if @quote.quoted_status.distributable? && public_fetch_mode?
|
||||
render json: @quote, serializer: ActivityPub::QuoteAuthorizationSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ class ActivityPub::QuoteAuthorizationsController < ActivityPub::BaseController
|
||||
@quote = Quote.accepted.where(quoted_account: @account).find(params[:id])
|
||||
return not_found unless @quote.status.present? && @quote.quoted_status.present?
|
||||
|
||||
authorize @quote.status, :show?
|
||||
authorize @quote.quoted_status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
@@ -66,7 +66,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
if async_refresh.running?
|
||||
add_async_refresh_header(async_refresh)
|
||||
elsif !current_account.nil? && @status.should_fetch_replies?
|
||||
add_async_refresh_header(AsyncRefresh.create(refresh_key))
|
||||
add_async_refresh_header(AsyncRefresh.create(refresh_key, count_results: true))
|
||||
|
||||
WorkerBatch.new.within do |batch|
|
||||
batch.connect(refresh_key, threshold: 1.0)
|
||||
|
||||
@@ -58,7 +58,6 @@ export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE'
|
||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
||||
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||
export const COMPOSE_CONTENT_TYPE_CHANGE = 'COMPOSE_CONTENT_TYPE_CHANGE';
|
||||
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
||||
@@ -825,13 +824,6 @@ export function changeComposeSpoilerText(text) {
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeVisibility(value) {
|
||||
return {
|
||||
type: COMPOSE_VISIBILITY_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function insertEmojiCompose(position, emoji, needsSpace) {
|
||||
return {
|
||||
type: COMPOSE_EMOJI_INSERT,
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
} from 'flavours/glitch/store/typed_functions';
|
||||
|
||||
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||
import type { Status } from '../models/status';
|
||||
import type { Status, StatusVisibility } from '../models/status';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
import { showAlert } from './alerts';
|
||||
import { focusCompose } from './compose';
|
||||
import { changeCompose, focusCompose } from './compose';
|
||||
import { importFetchedStatuses } from './importer';
|
||||
import { openModal } from './modal';
|
||||
|
||||
@@ -41,6 +42,10 @@ const messages = defineMessages({
|
||||
id: 'quote_error.unauthorized',
|
||||
defaultMessage: 'You are not authorized to quote this post.',
|
||||
},
|
||||
quoteErrorPrivateMention: {
|
||||
id: 'quote_error.private_mentions',
|
||||
defaultMessage: 'Quoting is not allowed with direct mentions.',
|
||||
},
|
||||
});
|
||||
|
||||
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
||||
@@ -67,6 +72,39 @@ const simulateModifiedApiResponse = (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const changeComposeVisibility = createAppThunk(
|
||||
'compose/visibility_change',
|
||||
(visibility: StatusVisibility, { dispatch, getState }) => {
|
||||
if (visibility !== 'direct') {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const quotedStatusId = state.compose.get('quoted_status_id') as
|
||||
| string
|
||||
| null;
|
||||
if (!quotedStatusId) {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
// Remove the quoted status
|
||||
dispatch(quoteComposeCancel());
|
||||
const quotedStatus = state.statuses.get(quotedStatusId) as Status | null;
|
||||
if (!quotedStatus) {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
// Append the quoted status URL to the compose text
|
||||
const url = quotedStatus.get('url') as string;
|
||||
const text = state.compose.get('text') as string;
|
||||
if (!text.includes(url)) {
|
||||
const newText = text.trim() ? `${text}\n\n${url}` : url;
|
||||
dispatch(changeCompose(newText));
|
||||
}
|
||||
return visibility;
|
||||
},
|
||||
);
|
||||
|
||||
export const changeUploadCompose = createDataLoadingThunk(
|
||||
'compose/changeUpload',
|
||||
async (
|
||||
@@ -130,6 +168,8 @@ export const quoteComposeByStatus = createAppThunk(
|
||||
|
||||
if (composeState.get('id')) {
|
||||
dispatch(showAlert({ message: messages.quoteErrorEdit }));
|
||||
} else if (composeState.get('privacy') === 'direct') {
|
||||
dispatch(showAlert({ message: messages.quoteErrorPrivateMention }));
|
||||
} else if (composeState.get('poll')) {
|
||||
dispatch(showAlert({ message: messages.quoteErrorPoll }));
|
||||
} else if (
|
||||
@@ -173,6 +213,17 @@ export const quoteComposeById = createAppThunk(
|
||||
},
|
||||
);
|
||||
|
||||
const composeStateForbidsLink = (composeState: RootState['compose']) => {
|
||||
return (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id') ||
|
||||
composeState.get('privacy') === 'direct'
|
||||
);
|
||||
};
|
||||
|
||||
export const pasteLinkCompose = createDataLoadingThunk(
|
||||
'compose/pasteLink',
|
||||
async ({ url }: { url: string }) => {
|
||||
@@ -183,15 +234,12 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
limit: 2,
|
||||
});
|
||||
},
|
||||
(data, { dispatch, getState }) => {
|
||||
(data, { dispatch, getState, requestId }) => {
|
||||
const composeState = getState().compose;
|
||||
|
||||
if (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id')
|
||||
composeStateForbidsLink(composeState) ||
|
||||
composeState.get('fetching_link') !== requestId // Request has been cancelled
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -207,6 +255,17 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
dispatch(quoteComposeById(data.statuses[0].id));
|
||||
}
|
||||
},
|
||||
{
|
||||
useLoadingBar: false,
|
||||
condition: (_, { getState }) =>
|
||||
!getState().compose.get('fetching_link') &&
|
||||
!composeStateForbidsLink(getState().compose),
|
||||
},
|
||||
);
|
||||
|
||||
// Ideally this would cancel the action and the HTTP request, but this is good enough
|
||||
export const cancelPasteLinkCompose = createAction(
|
||||
'compose/cancelPasteLinkCompose',
|
||||
);
|
||||
|
||||
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
||||
|
||||
@@ -46,11 +46,11 @@ export function importFetchedAccounts(accounts) {
|
||||
return importAccounts({ accounts: normalAccounts });
|
||||
}
|
||||
|
||||
export function importFetchedStatus(status) {
|
||||
return importFetchedStatuses([status]);
|
||||
export function importFetchedStatus(status, options = {}) {
|
||||
return importFetchedStatuses([status], options);
|
||||
}
|
||||
|
||||
export function importFetchedStatuses(statuses) {
|
||||
export function importFetchedStatuses(statuses, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const accounts = [];
|
||||
const normalStatuses = [];
|
||||
@@ -58,7 +58,7 @@ export function importFetchedStatuses(statuses) {
|
||||
const filters = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), getState().get('local_settings')));
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), { ...options, settings: getState().get('local_settings') }));
|
||||
pushUnique(accounts, status.account);
|
||||
|
||||
if (status.filtered) {
|
||||
|
||||
@@ -27,9 +27,12 @@ function stripQuoteFallback(text) {
|
||||
return wrapper.innerHTML;
|
||||
}
|
||||
|
||||
export function normalizeStatus(status, normalOldStatus, settings) {
|
||||
export function normalizeStatus(status, normalOldStatus, { settings, bogusQuotePolicy = false }) {
|
||||
const normalStatus = { ...status };
|
||||
|
||||
if (bogusQuotePolicy)
|
||||
normalStatus.quote_approval = null;
|
||||
|
||||
normalStatus.account = status.account.id;
|
||||
|
||||
if (status.reblog && status.reblog.id) {
|
||||
@@ -101,6 +104,8 @@ export function normalizeStatus(status, normalOldStatus, settings) {
|
||||
}
|
||||
|
||||
if (normalOldStatus) {
|
||||
normalStatus.quote_approval ||= normalOldStatus.quote_approval;
|
||||
|
||||
const list = normalOldStatus.get('media_attachments');
|
||||
if (normalStatus.media_attachments && list) {
|
||||
normalStatus.media_attachments.forEach(item => {
|
||||
|
||||
@@ -204,8 +204,8 @@ export function deleteStatusFail(id, error) {
|
||||
};
|
||||
}
|
||||
|
||||
export const updateStatus = status => dispatch =>
|
||||
dispatch(importFetchedStatus(status));
|
||||
export const updateStatus = (status, { bogusQuotePolicy }) => dispatch =>
|
||||
dispatch(importFetchedStatus(status, { bogusQuotePolicy }));
|
||||
|
||||
export function muteStatus(id) {
|
||||
return (dispatch) => {
|
||||
|
||||
@@ -52,6 +52,9 @@ const randomUpTo = max =>
|
||||
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
|
||||
const { messages } = getLocale();
|
||||
|
||||
// Public streams are currently not returning personalized quote policies
|
||||
const bogusQuotePolicy = channelName.startsWith('public') || channelName.startsWith('hashtag');
|
||||
|
||||
return connectStream(channelName, params, (dispatch, getState) => {
|
||||
// @ts-ignore
|
||||
const locale = getState().getIn(['meta', 'locale']);
|
||||
@@ -97,11 +100,11 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
||||
switch (data.event) {
|
||||
case 'update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), { accept: options.accept, bogusQuotePolicy }));
|
||||
break;
|
||||
case 'status.update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateStatus(JSON.parse(data.payload)));
|
||||
dispatch(updateStatus(JSON.parse(data.payload), { bogusQuotePolicy }));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
|
||||
@@ -33,7 +33,7 @@ export const loadPending = timeline => ({
|
||||
timeline,
|
||||
});
|
||||
|
||||
export function updateTimeline(timeline, status, accept) {
|
||||
export function updateTimeline(timeline, status, { accept = undefined, bogusQuotePolicy = false } = {}) {
|
||||
return (dispatch, getState) => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
@@ -55,7 +55,7 @@ export function updateTimeline(timeline, status, accept) {
|
||||
filtered = filters.length > 0;
|
||||
}
|
||||
|
||||
dispatch(importFetchedStatus(status));
|
||||
dispatch(importFetchedStatus(status, { bogusQuotePolicy }));
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE,
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
closeDropdownMenu,
|
||||
} from 'flavours/glitch/actions/dropdown_menu';
|
||||
import { openModal, closeModal } from 'flavours/glitch/actions/modal';
|
||||
import { fetchStatus } from 'flavours/glitch/actions/statuses';
|
||||
import { CircularProgress } from 'flavours/glitch/components/circular_progress';
|
||||
import { isUserTouching } from 'flavours/glitch/is_mobile';
|
||||
import {
|
||||
@@ -42,16 +43,10 @@ import { IconButton } from './icon_button';
|
||||
|
||||
let id = 0;
|
||||
|
||||
export interface RenderItemFnHandlers {
|
||||
onClick: React.MouseEventHandler;
|
||||
onKeyUp: React.KeyboardEventHandler;
|
||||
}
|
||||
|
||||
export type RenderItemFn<Item = MenuItem> = (
|
||||
item: Item,
|
||||
index: number,
|
||||
handlers: RenderItemFnHandlers,
|
||||
focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void,
|
||||
onClick: React.MouseEventHandler,
|
||||
) => React.ReactNode;
|
||||
|
||||
type ItemClickFn<Item = MenuItem> = (item: Item, index: number) => void;
|
||||
@@ -101,7 +96,6 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
onItemClick,
|
||||
}: DropdownMenuProps<Item>) => {
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const focusedItemRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleDocumentClick = (e: MouseEvent) => {
|
||||
@@ -163,8 +157,11 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
document.addEventListener('click', handleDocumentClick, { capture: true });
|
||||
document.addEventListener('keydown', handleKeyDown, { capture: true });
|
||||
|
||||
if (focusedItemRef.current && openedViaKeyboard) {
|
||||
focusedItemRef.current.focus({ preventScroll: true });
|
||||
if (openedViaKeyboard) {
|
||||
const firstMenuItem = nodeRef.current?.querySelector<
|
||||
HTMLAnchorElement | HTMLButtonElement
|
||||
>('li:first-child > :is(a, button)');
|
||||
firstMenuItem?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -175,13 +172,6 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
};
|
||||
}, [onClose, openedViaKeyboard]);
|
||||
|
||||
const handleFocusedItemRef = useCallback(
|
||||
(c: HTMLAnchorElement | HTMLButtonElement | null) => {
|
||||
focusedItemRef.current = c as HTMLElement;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
@@ -207,15 +197,6 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
[onClose, onItemClick, items],
|
||||
);
|
||||
|
||||
const handleItemKeyUp = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleItemClick(e);
|
||||
}
|
||||
},
|
||||
[handleItemClick],
|
||||
);
|
||||
|
||||
const nativeRenderItem = (option: Item, i: number) => {
|
||||
if (!isMenuItem(option)) {
|
||||
return null;
|
||||
@@ -232,9 +213,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
if (isActionItem(option)) {
|
||||
element = (
|
||||
<button
|
||||
ref={i === 0 ? handleFocusedItemRef : undefined}
|
||||
onClick={handleItemClick}
|
||||
onKeyUp={handleItemKeyUp}
|
||||
data-index={i}
|
||||
aria-disabled={disabled}
|
||||
>
|
||||
@@ -248,9 +227,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
target={option.target ?? '_target'}
|
||||
data-method={option.method}
|
||||
rel='noopener'
|
||||
ref={i === 0 ? handleFocusedItemRef : undefined}
|
||||
onClick={handleItemClick}
|
||||
onKeyUp={handleItemKeyUp}
|
||||
data-index={i}
|
||||
>
|
||||
<DropdownMenuItemContent item={option} />
|
||||
@@ -258,13 +235,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
);
|
||||
} else {
|
||||
element = (
|
||||
<Link
|
||||
to={option.to}
|
||||
ref={i === 0 ? handleFocusedItemRef : undefined}
|
||||
onClick={handleItemClick}
|
||||
onKeyUp={handleItemKeyUp}
|
||||
data-index={i}
|
||||
>
|
||||
<Link to={option.to} onClick={handleItemClick} data-index={i}>
|
||||
<DropdownMenuItemContent item={option} />
|
||||
</Link>
|
||||
);
|
||||
@@ -307,15 +278,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
})}
|
||||
>
|
||||
{items.map((option, i) =>
|
||||
renderItemMethod(
|
||||
option,
|
||||
i,
|
||||
{
|
||||
onClick: handleItemClick,
|
||||
onKeyUp: handleItemKeyUp,
|
||||
},
|
||||
i === 0 ? handleFocusedItemRef : undefined,
|
||||
),
|
||||
renderItemMethod(option, i, handleItemClick),
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
@@ -340,6 +303,7 @@ interface DropdownProps<Item extends object | null = MenuItem> {
|
||||
*/
|
||||
scrollKey?: string;
|
||||
status?: ImmutableMap<string, unknown>;
|
||||
needsStatusRefresh?: boolean;
|
||||
forceDropdown?: boolean;
|
||||
renderItem?: RenderItemFn<Item>;
|
||||
renderHeader?: RenderHeaderFn<Item>;
|
||||
@@ -363,6 +327,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
placement = 'bottom',
|
||||
offset = [5, 5],
|
||||
status,
|
||||
needsStatusRefresh,
|
||||
forceDropdown = false,
|
||||
renderItem,
|
||||
renderHeader,
|
||||
@@ -382,6 +347,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
const prefetchAccountId = status
|
||||
? status.getIn(['account', 'id'])
|
||||
: undefined;
|
||||
const statusId = status?.get('id') as string | undefined;
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (buttonRef.current) {
|
||||
@@ -399,7 +365,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
}, [dispatch, currentId]);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
(e: React.MouseEvent) => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const item = items?.[i];
|
||||
|
||||
@@ -420,10 +386,20 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
[handleClose, onItemClick, items],
|
||||
);
|
||||
|
||||
const toggleDropdown = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
const { type } = e;
|
||||
const isKeypressRef = useRef(false);
|
||||
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
isKeypressRef.current = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const unsetIsKeypress = useCallback(() => {
|
||||
isKeypressRef.current = false;
|
||||
}, []);
|
||||
|
||||
const toggleDropdown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (open) {
|
||||
handleClose();
|
||||
} else {
|
||||
@@ -436,6 +412,15 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
dispatch(fetchRelationships([prefetchAccountId]));
|
||||
}
|
||||
|
||||
if (needsStatusRefresh && statusId) {
|
||||
dispatch(
|
||||
fetchStatus(statusId, {
|
||||
forceFetch: true,
|
||||
alsoFetchContext: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (isUserTouching() && !forceDropdown) {
|
||||
dispatch(
|
||||
openModal({
|
||||
@@ -450,10 +435,11 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
dispatch(
|
||||
openDropdownMenu({
|
||||
id: currentId,
|
||||
keyboard: type !== 'click',
|
||||
keyboard: isKeypressRef.current,
|
||||
scrollKey,
|
||||
}),
|
||||
);
|
||||
isKeypressRef.current = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -468,6 +454,8 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
items,
|
||||
forceDropdown,
|
||||
handleClose,
|
||||
statusId,
|
||||
needsStatusRefresh,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -484,6 +472,9 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
const buttonProps = {
|
||||
disabled,
|
||||
onClick: toggleDropdown,
|
||||
onKeyDown: handleKeyDown,
|
||||
onKeyUp: unsetIsKeypress,
|
||||
onBlur: unsetIsKeypress,
|
||||
'aria-expanded': open,
|
||||
'aria-controls': menuId,
|
||||
ref: buttonRef,
|
||||
|
||||
@@ -58,17 +58,7 @@ export const EditedTimestamp: React.FC<{
|
||||
}, []);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(
|
||||
item: HistoryItem,
|
||||
index: number,
|
||||
{
|
||||
onClick,
|
||||
onKeyUp,
|
||||
}: {
|
||||
onClick: React.MouseEventHandler;
|
||||
onKeyUp: React.KeyboardEventHandler;
|
||||
},
|
||||
) => {
|
||||
(item: HistoryItem, index: number, onClick: React.MouseEventHandler) => {
|
||||
const formattedDate = (
|
||||
<RelativeTimestamp
|
||||
timestamp={item.get('created_at') as string}
|
||||
@@ -98,7 +88,7 @@ export const EditedTimestamp: React.FC<{
|
||||
className='dropdown-menu__item edited-timestamp__history__item'
|
||||
key={item.get('created_at') as string}
|
||||
>
|
||||
<button data-index={index} onClick={onClick} onKeyUp={onKeyUp}>
|
||||
<button data-index={index} onClick={onClick} type='button'>
|
||||
{label}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -8,13 +8,14 @@ import classNames from 'classnames';
|
||||
import { quoteComposeById } from '@/flavours/glitch/actions/compose_typed';
|
||||
import { toggleReblog } from '@/flavours/glitch/actions/interactions';
|
||||
import { openModal } from '@/flavours/glitch/actions/modal';
|
||||
import { fetchStatus } from '@/flavours/glitch/actions/statuses';
|
||||
import { quickBoosting } from '@/flavours/glitch/initial_state';
|
||||
import type { ActionMenuItem } from '@/flavours/glitch/models/dropdown_menu';
|
||||
import type { Status } from '@/flavours/glitch/models/status';
|
||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
import type { SomeRequired } from '@/flavours/glitch/utils/types';
|
||||
|
||||
import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu';
|
||||
import type { RenderItemFn } from '../dropdown_menu';
|
||||
import { Dropdown, DropdownMenuItemContent } from '../dropdown_menu';
|
||||
import { IconButton } from '../icon_button';
|
||||
|
||||
@@ -74,18 +75,12 @@ const StandaloneBoostButton: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderMenuItem: RenderItemFn<ActionMenuItem> = (
|
||||
item,
|
||||
index,
|
||||
handlers,
|
||||
focusRefCallback,
|
||||
) => (
|
||||
const renderMenuItem: RenderItemFn<ActionMenuItem> = (item, index, onClick) => (
|
||||
<ReblogMenuItem
|
||||
index={index}
|
||||
item={item}
|
||||
handlers={handlers}
|
||||
onClick={onClick}
|
||||
key={`${item.text}-${index}`}
|
||||
focusRefCallback={focusRefCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -117,6 +112,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
|
||||
const statusId = status.get('id') as string;
|
||||
const wasBoosted = !!status.get('reblogged');
|
||||
const quoteApproval = status.get('quote_approval');
|
||||
|
||||
const showLoginPrompt = useCallback(() => {
|
||||
dispatch(
|
||||
@@ -173,9 +169,16 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
dispatch(toggleReblog(status.get('id'), true));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (quoteApproval === null) {
|
||||
dispatch(
|
||||
fetchStatus(statusId, { forceFetch: true, alsoFetchContext: false }),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[dispatch, isLoggedIn, showLoginPrompt, status],
|
||||
[dispatch, isLoggedIn, showLoginPrompt, status, quoteApproval, statusId],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -208,16 +211,10 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
interface ReblogMenuItemProps {
|
||||
item: ActionMenuItem;
|
||||
index: number;
|
||||
handlers: RenderItemFnHandlers;
|
||||
focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void;
|
||||
onClick: React.MouseEventHandler;
|
||||
}
|
||||
|
||||
const ReblogMenuItem: FC<ReblogMenuItemProps> = ({
|
||||
index,
|
||||
item,
|
||||
handlers,
|
||||
focusRefCallback,
|
||||
}) => {
|
||||
const ReblogMenuItem: FC<ReblogMenuItemProps> = ({ index, item, onClick }) => {
|
||||
const { text, highlighted, disabled } = item;
|
||||
|
||||
return (
|
||||
@@ -227,12 +224,7 @@ const ReblogMenuItem: FC<ReblogMenuItemProps> = ({
|
||||
})}
|
||||
key={`${text}-${index}`}
|
||||
>
|
||||
<button
|
||||
{...handlers}
|
||||
ref={focusRefCallback}
|
||||
aria-disabled={disabled}
|
||||
data-index={index}
|
||||
>
|
||||
<button onClick={onClick} aria-disabled={disabled} data-index={index}>
|
||||
<DropdownMenuItemContent item={item} />
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -374,6 +374,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<Dropdown
|
||||
scrollKey={scrollKey}
|
||||
status={status}
|
||||
needsStatusRefresh={quickBoosting && status.get('quote_approval') === null}
|
||||
items={menu}
|
||||
icon='ellipsis-h'
|
||||
size={18}
|
||||
|
||||
@@ -49,6 +49,7 @@ export const StatusBanner: React.FC<{
|
||||
|
||||
<button
|
||||
ref={buttonRef}
|
||||
type='button'
|
||||
className='link-button'
|
||||
onClick={onClick}
|
||||
aria-describedby={descriptionId}
|
||||
|
||||
@@ -169,9 +169,13 @@ const localeOptionsSelector = createSelector(
|
||||
},
|
||||
};
|
||||
// Use the default locale as a target to translate language names.
|
||||
const intlLocale = new Intl.DisplayNames(intl.locale, {
|
||||
type: 'language',
|
||||
});
|
||||
const intlLocale =
|
||||
// Intl.DisplayNames can be undefined in old browsers
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
Intl.DisplayNames &&
|
||||
(new Intl.DisplayNames(intl.locale, {
|
||||
type: 'language',
|
||||
}) as Intl.DisplayNames | undefined);
|
||||
for (const { translations } of rules) {
|
||||
for (const locale in translations) {
|
||||
if (langs[locale]) {
|
||||
@@ -179,7 +183,7 @@ const localeOptionsSelector = createSelector(
|
||||
}
|
||||
langs[locale] = {
|
||||
value: locale,
|
||||
text: intlLocale.of(locale) ?? locale,
|
||||
text: intlLocale?.of(locale) ?? locale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
});
|
||||
}, [dispatch, setIsSaving, mediaId, onClose, position, description]);
|
||||
|
||||
const handleKeyUp = useCallback(
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -457,7 +457,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
id='description'
|
||||
value={isDetecting ? ' ' : description}
|
||||
onChange={handleDescriptionChange}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
lang={lang}
|
||||
placeholder={intl.formatMessage(
|
||||
type === 'audio'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useCallback, useState, useId } from 'react';
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -22,6 +22,8 @@ import { useAudioVisualizer } from 'flavours/glitch/hooks/useAudioVisualizer';
|
||||
import { displayMedia, useBlurhash } from 'flavours/glitch/initial_state';
|
||||
import { playerSettings } from 'flavours/glitch/settings';
|
||||
|
||||
import { AudioVisualizer } from './visualizer';
|
||||
|
||||
const messages = defineMessages({
|
||||
play: { id: 'video.play', defaultMessage: 'Play' },
|
||||
pause: { id: 'video.pause', defaultMessage: 'Pause' },
|
||||
@@ -116,7 +118,6 @@ export const Audio: React.FC<{
|
||||
const seekRef = useRef<HTMLDivElement>(null);
|
||||
const volumeRef = useRef<HTMLDivElement>(null);
|
||||
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
|
||||
const accessibilityId = useId();
|
||||
|
||||
const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } =
|
||||
useAudioContext({ audioElementRef: audioRef });
|
||||
@@ -538,19 +539,6 @@ export const Audio: React.FC<{
|
||||
[togglePlay, toggleMute],
|
||||
);
|
||||
|
||||
const springForBand0 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[0] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand1 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[1] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand2 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[2] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
|
||||
const progress = Math.min((currentTime / loadedDuration) * 100, 100);
|
||||
const effectivelyMuted = muted || volume === 0;
|
||||
|
||||
@@ -641,81 +629,7 @@ export const Audio: React.FC<{
|
||||
</div>
|
||||
|
||||
<div className='audio-player__controls__play'>
|
||||
<svg
|
||||
className='audio-player__visualizer'
|
||||
viewBox='0 0 124 124'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={57}
|
||||
cy={62.5}
|
||||
r={springForBand0.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={65}
|
||||
cy={57.5}
|
||||
r={springForBand1.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={63}
|
||||
cy={66.5}
|
||||
r={springForBand2.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
|
||||
<g clipPath={`url(#${accessibilityId}-clip)`}>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill={`url(#${accessibilityId}-pattern)`}
|
||||
/>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill='var(--player-background-color'
|
||||
opacity={0.45}
|
||||
/>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<pattern
|
||||
id={`${accessibilityId}-pattern`}
|
||||
patternContentUnits='objectBoundingBox'
|
||||
width='1'
|
||||
height='1'
|
||||
>
|
||||
<use href={`#${accessibilityId}-image`} />
|
||||
</pattern>
|
||||
|
||||
<clipPath id={`${accessibilityId}-clip`}>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
rx={48}
|
||||
fill='white'
|
||||
/>
|
||||
</clipPath>
|
||||
|
||||
<image
|
||||
id={`${accessibilityId}-image`}
|
||||
href={poster}
|
||||
width={1}
|
||||
height={1}
|
||||
preserveAspectRatio='none'
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
<AudioVisualizer frequencyBands={frequencyBands} poster={poster} />
|
||||
|
||||
<button
|
||||
type='button'
|
||||
|
||||
100
app/javascript/flavours/glitch/features/audio/visualizer.tsx
Normal file
100
app/javascript/flavours/glitch/features/audio/visualizer.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useId } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { animated, config, useSpring } from '@react-spring/web';
|
||||
|
||||
interface AudioVisualizerProps {
|
||||
frequencyBands?: number[];
|
||||
poster?: string;
|
||||
}
|
||||
|
||||
export const AudioVisualizer: FC<AudioVisualizerProps> = ({
|
||||
frequencyBands = [],
|
||||
poster,
|
||||
}) => {
|
||||
const accessibilityId = useId();
|
||||
|
||||
const springForBand0 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[0] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand1 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[1] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand2 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[2] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
|
||||
return (
|
||||
<svg
|
||||
className='audio-player__visualizer'
|
||||
viewBox='0 0 124 124'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={57}
|
||||
cy={62.5}
|
||||
r={springForBand0.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={65}
|
||||
cy={57.5}
|
||||
r={springForBand1.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={63}
|
||||
cy={66.5}
|
||||
r={springForBand2.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
|
||||
<g clipPath={`url(#${accessibilityId}-clip)`}>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill={`url(#${accessibilityId}-pattern)`}
|
||||
/>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill='var(--player-background-color'
|
||||
opacity={0.45}
|
||||
/>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<pattern
|
||||
id={`${accessibilityId}-pattern`}
|
||||
patternContentUnits='objectBoundingBox'
|
||||
width='1'
|
||||
height='1'
|
||||
>
|
||||
<use href={`#${accessibilityId}-image`} />
|
||||
</pattern>
|
||||
|
||||
<clipPath id={`${accessibilityId}-clip`}>
|
||||
<rect x={14} y={14} width={96} height={96} rx={48} fill='white' />
|
||||
</clipPath>
|
||||
|
||||
<image
|
||||
id={`${accessibilityId}-image`}
|
||||
href={poster}
|
||||
width={1}
|
||||
height={1}
|
||||
preserveAspectRatio='none'
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -155,7 +155,11 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct', overridePrivacy);
|
||||
this.props.onSubmit({
|
||||
missingAltTextModal: missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct',
|
||||
quoteToPrivate: this.props.quoteToPrivate,
|
||||
overridePrivacy,
|
||||
});
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { cancelPasteLinkCompose } from '@/flavours/glitch/actions/compose_typed';
|
||||
import { useAppDispatch } from '@/flavours/glitch/store';
|
||||
import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
|
||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import { Skeleton } from 'flavours/glitch/components/skeleton';
|
||||
|
||||
const messages = defineMessages({
|
||||
quote_cancel: { id: 'status.quote.cancel', defaultMessage: 'Cancel quote' },
|
||||
});
|
||||
|
||||
export const QuotePlaceholder: FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const handleQuoteCancel = useCallback(() => {
|
||||
dispatch(cancelPasteLinkCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className='status__quote'>
|
||||
<div className='status'>
|
||||
<div className='status__info'>
|
||||
<div className='status__avatar'>
|
||||
<Skeleton width='32px' height='32px' />
|
||||
</div>
|
||||
<div className='status__display-name'>
|
||||
<DisplayName />
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={handleQuoteCancel}
|
||||
className='status__quote-cancel'
|
||||
title={intl.formatMessage(messages.quote_cancel)}
|
||||
icon='cancel-fill'
|
||||
iconComponent={CancelFillIcon}
|
||||
/>
|
||||
</div>
|
||||
<div className='status__content'>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,11 +7,17 @@ import { quoteComposeCancel } from '@/flavours/glitch/actions/compose_typed';
|
||||
import { QuotedStatus } from '@/flavours/glitch/components/status_quoted';
|
||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||
|
||||
import { QuotePlaceholder } from './quote_placeholder';
|
||||
|
||||
export const ComposeQuotedStatus: FC = () => {
|
||||
const quotedStatusId = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') as string | null,
|
||||
);
|
||||
|
||||
const isFetchingLink = useAppSelector(
|
||||
(state) => !!state.compose.get('fetching_link'),
|
||||
);
|
||||
|
||||
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
|
||||
|
||||
const quote = useMemo(
|
||||
@@ -30,7 +36,9 @@ export const ComposeQuotedStatus: FC = () => {
|
||||
dispatch(quoteComposeCancel());
|
||||
}, [dispatch]);
|
||||
|
||||
if (!quote) {
|
||||
if (isFetchingLink && !quote) {
|
||||
return <QuotePlaceholder />;
|
||||
} else if (!quote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-20px/close.svg?react';
|
||||
import SoundIcon from '@/material-icons/400-24px/audio.svg?react';
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import { undoUploadCompose } from 'flavours/glitch/actions/compose';
|
||||
@@ -17,7 +18,18 @@ import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { Blurhash } from 'flavours/glitch/components/blurhash';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import type { MediaAttachment } from 'flavours/glitch/models/media_attachment';
|
||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||
import {
|
||||
createAppSelector,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from 'flavours/glitch/store';
|
||||
|
||||
import { AudioVisualizer } from '../../audio/visualizer';
|
||||
|
||||
const selectUserAvatar = createAppSelector(
|
||||
[(state) => state.accounts, (state) => state.meta.get('me') as string],
|
||||
(accounts, myId) => accounts.get(myId)?.avatar_static,
|
||||
);
|
||||
|
||||
export const Upload: React.FC<{
|
||||
id: string;
|
||||
@@ -38,6 +50,7 @@ export const Upload: React.FC<{
|
||||
const sensitive = useAppSelector(
|
||||
(state) => state.compose.get('sensitive') as boolean,
|
||||
);
|
||||
const userAvatar = useAppSelector(selectUserAvatar);
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
@@ -67,6 +80,8 @@ export const Upload: React.FC<{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
const preview_url = media.get('preview_url') as string | null;
|
||||
const blurhash = media.get('blurhash') as string | null;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -85,17 +100,19 @@ export const Upload: React.FC<{
|
||||
<div
|
||||
className='compose-form__upload__thumbnail'
|
||||
style={{
|
||||
backgroundImage: !sensitive
|
||||
? `url(${media.get('preview_url') as string})`
|
||||
: undefined,
|
||||
backgroundImage:
|
||||
!sensitive && preview_url ? `url(${preview_url})` : undefined,
|
||||
backgroundPosition: `${x}% ${y}%`,
|
||||
}}
|
||||
>
|
||||
{sensitive && (
|
||||
<Blurhash
|
||||
hash={media.get('blurhash') as string}
|
||||
className='compose-form__upload__preview'
|
||||
/>
|
||||
{sensitive && blurhash && (
|
||||
<Blurhash hash={blurhash} className='compose-form__upload__preview' />
|
||||
)}
|
||||
{!sensitive && !preview_url && (
|
||||
<div className='compose-form__upload__visualizer'>
|
||||
<AudioVisualizer poster={userAvatar} />
|
||||
<Icon id='sound' icon={SoundIcon} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='compose-form__upload__actions'>
|
||||
|
||||
@@ -5,8 +5,10 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { changeComposeVisibility } from '@/flavours/glitch/actions/compose';
|
||||
import { setComposeQuotePolicy } from '@/flavours/glitch/actions/compose_typed';
|
||||
import {
|
||||
changeComposeVisibility,
|
||||
setComposeQuotePolicy,
|
||||
} from '@/flavours/glitch/actions/compose_typed';
|
||||
import { openModal } from '@/flavours/glitch/actions/modal';
|
||||
import type { ApiQuotePolicy } from '@/flavours/glitch/api_types/quotes';
|
||||
import type { StatusVisibility } from '@/flavours/glitch/api_types/statuses';
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from 'flavours/glitch/actions/compose';
|
||||
import { pasteLinkCompose } from 'flavours/glitch/actions/compose_typed';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { PRIVATE_QUOTE_MODAL_ID } from 'flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify';
|
||||
import { privacyPreference } from 'flavours/glitch/utils/privacy_preference';
|
||||
|
||||
import ComposeForm from '../components/compose_form';
|
||||
@@ -52,6 +53,10 @@ const mapStateToProps = state => ({
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
|
||||
quoteToPrivate:
|
||||
!!state.getIn(['compose', 'quoted_status_id'])
|
||||
&& state.getIn(['compose', 'privacy']) === 'private'
|
||||
&& !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]),
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
sideArm: sideArmPrivacy(state),
|
||||
@@ -65,12 +70,17 @@ const mapDispatchToProps = (dispatch, props) => ({
|
||||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit (missingAltText, overridePrivacy = null) {
|
||||
onSubmit ({ missingAltText, quoteToPrivate, overridePrivacy = null }) {
|
||||
if (missingAltText) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_MISSING_ALT_TEXT',
|
||||
modalProps: { overridePrivacy },
|
||||
}));
|
||||
} else if (quoteToPrivate) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY',
|
||||
modalProps: {},
|
||||
}));
|
||||
} else {
|
||||
dispatch(submitCompose(overridePrivacy, (status) => {
|
||||
if (props.redirectOnSuccess) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { changeComposeVisibility } from '../../../actions/compose';
|
||||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
import { changeComposeVisibility } from '@/flavours/glitch/actions/compose_typed';
|
||||
|
||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flattenEmojiData } from 'emojibase';
|
||||
import type { CompactEmoji, FlatCompactEmoji } from 'emojibase';
|
||||
import type { CompactEmoji, FlatCompactEmoji, Locale } from 'emojibase';
|
||||
|
||||
import {
|
||||
putEmojiData,
|
||||
@@ -43,9 +43,8 @@ async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
if (locale === 'custom') {
|
||||
url.pathname = '/api/v1/custom_emojis';
|
||||
} else {
|
||||
// This doesn't use isDevelopment() as that module loads initial state
|
||||
// which breaks workers, as they cannot access the DOM.
|
||||
url.pathname = `/packs${import.meta.env.DEV ? '-dev' : ''}/emoji/${locale}.json`;
|
||||
const modulePath = await localeToPath(locale);
|
||||
url.pathname = modulePath;
|
||||
}
|
||||
|
||||
const oldEtag = await loadLatestEtag(locale);
|
||||
@@ -80,3 +79,19 @@ async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const modules = import.meta.glob<string>(
|
||||
'../../../../../../node_modules/emojibase-data/**/compact.json',
|
||||
{
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
},
|
||||
);
|
||||
|
||||
function localeToPath(locale: Locale) {
|
||||
const key = `../../../../../../node_modules/emojibase-data/${locale}/compact.json`;
|
||||
if (!modules[key] || typeof modules[key] !== 'function') {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
return modules[key]();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { dismissAnnouncement } from '@/flavours/glitch/actions/announcements';
|
||||
import type { ApiAnnouncementJSON } from '@/flavours/glitch/api_types/announcements';
|
||||
import { AnimateEmojiProvider } from '@/flavours/glitch/components/emoji/context';
|
||||
import { EmojiHTML } from '@/flavours/glitch/components/emoji/html';
|
||||
import { useAppDispatch } from '@/flavours/glitch/store';
|
||||
|
||||
import { ReactionsBar } from './reactions';
|
||||
|
||||
@@ -22,13 +24,23 @@ export const Announcement: FC<AnnouncementProps> = ({
|
||||
announcement,
|
||||
selected,
|
||||
}) => {
|
||||
const [unread, setUnread] = useState(!announcement.read);
|
||||
const { read, id } = announcement;
|
||||
|
||||
// Dismiss announcement when it becomes active.
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
// Only update `unread` marker once the announcement is out of view
|
||||
if (!selected && unread !== !announcement.read) {
|
||||
setUnread(!announcement.read);
|
||||
if (selected && !read) {
|
||||
dispatch(dismissAnnouncement(id));
|
||||
}
|
||||
}, [announcement.read, selected, unread]);
|
||||
}, [selected, id, dispatch, read]);
|
||||
|
||||
// But visually show the announcement as read only when it goes out of view.
|
||||
const [unread, setUnread] = useState(!read);
|
||||
useEffect(() => {
|
||||
if (!selected && unread !== !read) {
|
||||
setUnread(!read);
|
||||
}
|
||||
}, [selected, unread, read]);
|
||||
|
||||
return (
|
||||
<AnimateEmojiProvider className='announcements__item'>
|
||||
|
||||
@@ -445,6 +445,7 @@ export const DetailedStatus: React.FC<{
|
||||
<QuotedStatus
|
||||
quote={status.get('quote')}
|
||||
parentQuotePostId={status.get('id')}
|
||||
contextType='thread'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -295,7 +295,7 @@ export const RefreshController: React.FC<{
|
||||
if (loadingState === 'loading') {
|
||||
return (
|
||||
<div
|
||||
className='load-more load-gap'
|
||||
className='load-more load-more--large'
|
||||
aria-busy
|
||||
aria-live='polite'
|
||||
aria-label={intl.formatMessage(messages.loadingInitial)}
|
||||
|
||||
@@ -161,7 +161,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount () {
|
||||
attachFullscreenListener(this.onFullScreenChange);
|
||||
this.props.dispatch(fetchStatus(this.props.params.statusId));
|
||||
this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true }));
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ class Status extends ImmutablePureComponent {
|
||||
let updated = false;
|
||||
|
||||
if (props.params.statusId && state.statusId !== props.params.statusId) {
|
||||
props.dispatch(fetchStatus(props.params.statusId));
|
||||
props.dispatch(fetchStatus(props.params.statusId, { forceFetch: true }));
|
||||
update.threadExpanded = undefined;
|
||||
update.statusId = props.params.statusId;
|
||||
updated = true;
|
||||
@@ -329,6 +329,12 @@ class Status extends ImmutablePureComponent {
|
||||
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } }));
|
||||
};
|
||||
|
||||
handleQuote = (status) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(quoteComposeById(status.get('id')));
|
||||
};
|
||||
|
||||
handleEditClick = (status) => {
|
||||
const { dispatch, askReplyConfirmation } = this.props;
|
||||
|
||||
@@ -659,6 +665,7 @@ class Status extends ImmutablePureComponent {
|
||||
onDelete={this.handleDeleteClick}
|
||||
onRevokeQuote={this.handleRevokeQuoteClick}
|
||||
onQuotePolicyChange={this.handleQuotePolicyChange}
|
||||
onQuote={this.handleQuote}
|
||||
onEdit={this.handleEditClick}
|
||||
onDirect={this.handleDirectClick}
|
||||
onMention={this.handleMentionClick}
|
||||
|
||||
@@ -26,6 +26,7 @@ export const ConfirmationModal: React.FC<
|
||||
onSecondary?: () => void;
|
||||
onConfirm: () => void;
|
||||
closeWhenConfirm?: boolean;
|
||||
extraContent?: React.ReactNode;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({
|
||||
title,
|
||||
@@ -37,6 +38,7 @@ export const ConfirmationModal: React.FC<
|
||||
secondary,
|
||||
onSecondary,
|
||||
closeWhenConfirm = true,
|
||||
extraContent,
|
||||
}) => {
|
||||
const handleClick = useCallback(() => {
|
||||
if (closeWhenConfirm) {
|
||||
@@ -57,6 +59,8 @@ export const ConfirmationModal: React.FC<
|
||||
<div className='safety-action-modal__confirmation'>
|
||||
<h1>{title}</h1>
|
||||
{message && <p>{message}</p>}
|
||||
|
||||
{extraContent}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { forwardRef, useCallback, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { submitCompose } from '@/flavours/glitch/actions/compose';
|
||||
import { changeSetting } from '@/flavours/glitch/actions/settings';
|
||||
import { CheckBox } from '@/flavours/glitch/components/check_box';
|
||||
import { useAppDispatch } from '@/flavours/glitch/store';
|
||||
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import classes from './styles.module.css';
|
||||
|
||||
export const PRIVATE_QUOTE_MODAL_ID = 'quote/private_notify';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.private_quote_notify.title',
|
||||
defaultMessage: 'Share with followers and mentioned users?',
|
||||
},
|
||||
message: {
|
||||
id: 'confirmations.private_quote_notify.message',
|
||||
defaultMessage:
|
||||
'The person you are quoting and other mentions ' +
|
||||
"will be notified and will be able to view your post, even if they're not following you.",
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.private_quote_notify.confirm',
|
||||
defaultMessage: 'Publish post',
|
||||
},
|
||||
cancel: {
|
||||
id: 'confirmations.private_quote_notify.cancel',
|
||||
defaultMessage: 'Back to editing',
|
||||
},
|
||||
});
|
||||
|
||||
export const PrivateQuoteNotify = forwardRef<
|
||||
HTMLDivElement,
|
||||
BaseConfirmationModalProps
|
||||
>(
|
||||
(
|
||||
{ onClose },
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_ref,
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [dismiss, setDismissed] = useState(false);
|
||||
const handleDismissToggle = useCallback(() => {
|
||||
setDismissed((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleConfirm = useCallback(() => {
|
||||
dispatch(submitCompose());
|
||||
if (dismiss) {
|
||||
dispatch(
|
||||
changeSetting(['dismissed_banners', PRIVATE_QUOTE_MODAL_ID], true),
|
||||
);
|
||||
}
|
||||
}, [dismiss, dispatch]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
message={intl.formatMessage(messages.message)}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
cancel={intl.formatMessage(messages.cancel)}
|
||||
onConfirm={handleConfirm}
|
||||
onClose={onClose}
|
||||
extraContent={
|
||||
<label className={classes.checkbox_wrapper}>
|
||||
<CheckBox
|
||||
value='hide'
|
||||
checked={dismiss}
|
||||
onChange={handleDismissToggle}
|
||||
/>{' '}
|
||||
<FormattedMessage
|
||||
id='confirmations.private_quote_notify.do_not_show_again'
|
||||
defaultMessage="Don't show me this message again"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
PrivateQuoteNotify.displayName = 'PrivateQuoteNotify';
|
||||
@@ -0,0 +1,7 @@
|
||||
.checkbox_wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import MediaModal from './media_modal';
|
||||
import { ModalPlaceholder } from './modal_placeholder';
|
||||
import VideoModal from './video_modal';
|
||||
import { VisibilityModal } from './visibility_modal';
|
||||
import { PrivateQuoteNotify } from './confirmation_modals/private_quote_notify';
|
||||
|
||||
export const MODAL_COMPONENTS = {
|
||||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||
@@ -72,6 +73,7 @@ export const MODAL_COMPONENTS = {
|
||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
|
||||
'MUTE': MuteModal,
|
||||
|
||||
@@ -128,9 +128,12 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
const disableVisibility = !!statusId;
|
||||
const disableQuotePolicy =
|
||||
visibility === 'private' || visibility === 'direct';
|
||||
const disablePublicVisibilities: boolean = useAppSelector(
|
||||
const disablePublicVisibilities = useAppSelector(
|
||||
selectDisablePublicVisibilities,
|
||||
);
|
||||
const isQuotePost = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') !== null,
|
||||
);
|
||||
|
||||
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(() => {
|
||||
const items: SelectItem<StatusVisibility>[] = [
|
||||
@@ -315,6 +318,21 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
id={quoteDescriptionId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isQuotePost && visibility === 'direct' && (
|
||||
<div className='visibility-modal__quote-warning'>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.direct_quote_warning.title'
|
||||
defaultMessage="Quotes can't be embedded in private mentions"
|
||||
tagName='h3'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.direct_quote_warning.text'
|
||||
defaultMessage='If you save the current settings, the embedded quote will be converted to a link.'
|
||||
tagName='p'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='dialog-modal__content__actions'>
|
||||
<Button onClick={onClose} secondary>
|
||||
|
||||
@@ -37,7 +37,7 @@ interface InitialStateMeta {
|
||||
streaming_api_base_url: string;
|
||||
local_live_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
remote_live_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
local_topic_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
local_topic_feed_access: 'public' | 'authenticated';
|
||||
remote_topic_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
title: string;
|
||||
show_trends: boolean;
|
||||
@@ -156,17 +156,21 @@ export const statusPageUrl = getMeta('status_page_url');
|
||||
export const sso_redirect = getMeta('sso_redirect');
|
||||
export const termsOfServiceEnabled = getMeta('terms_of_service_enabled');
|
||||
|
||||
const displayNames = new Intl.DisplayNames(getMeta('locale'), {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
languageDisplay: 'standard',
|
||||
});
|
||||
const displayNames =
|
||||
// Intl.DisplayNames can be undefined in old browsers
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
Intl.DisplayNames &&
|
||||
(new Intl.DisplayNames(getMeta('locale'), {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
languageDisplay: 'standard',
|
||||
}) as Intl.DisplayNames | undefined);
|
||||
|
||||
export const languages = initialState?.languages.map((lang) => {
|
||||
// zh-YUE is not a valid CLDR unicode_language_id
|
||||
return [
|
||||
lang[0],
|
||||
displayNames.of(lang[0].replace('zh-YUE', 'yue')) ?? lang[1],
|
||||
displayNames?.of(lang[0].replace('zh-YUE', 'yue')) ?? lang[1],
|
||||
lang[2],
|
||||
];
|
||||
});
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
changeComposeVisibility,
|
||||
changeUploadCompose,
|
||||
quoteCompose,
|
||||
quoteComposeCancel,
|
||||
setComposeQuotePolicy,
|
||||
} from 'flavours/glitch/actions/compose_typed';
|
||||
pasteLinkCompose,
|
||||
cancelPasteLinkCompose,
|
||||
} from '@/flavours/glitch/actions/compose_typed';
|
||||
import { timelineDelete } from 'flavours/glitch/actions/timelines_typed';
|
||||
|
||||
import {
|
||||
@@ -39,7 +42,6 @@ import {
|
||||
COMPOSE_SENSITIVITY_CHANGE,
|
||||
COMPOSE_SPOILERNESS_CHANGE,
|
||||
COMPOSE_SPOILER_TEXT_CHANGE,
|
||||
COMPOSE_VISIBILITY_CHANGE,
|
||||
COMPOSE_LANGUAGE_CHANGE,
|
||||
COMPOSE_COMPOSING_CHANGE,
|
||||
COMPOSE_CONTENT_TYPE_CHANGE,
|
||||
@@ -119,6 +121,7 @@ const initialState = ImmutableMap({
|
||||
quoted_status_id: null,
|
||||
quote_policy: 'public',
|
||||
default_quote_policy: 'public', // Set in hydration.
|
||||
fetching_link: null,
|
||||
});
|
||||
|
||||
const initialPoll = ImmutableMap({
|
||||
@@ -391,7 +394,11 @@ const calculateProgress = (loaded, total) => Math.min(Math.round((loaded / total
|
||||
|
||||
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
|
||||
export const composeReducer = (state = initialState, action) => {
|
||||
if (changeUploadCompose.fulfilled.match(action)) {
|
||||
if (changeComposeVisibility.match(action)) {
|
||||
return state
|
||||
.set('privacy', action.payload)
|
||||
.set('idempotencyKey', uuid());
|
||||
} else if (changeUploadCompose.fulfilled.match(action)) {
|
||||
return state
|
||||
.set('is_changing_upload', false)
|
||||
.update('media_attachments', list => list.map(item => {
|
||||
@@ -407,15 +414,27 @@ export const composeReducer = (state = initialState, action) => {
|
||||
return state.set('is_changing_upload', false);
|
||||
} else if (quoteCompose.match(action)) {
|
||||
const status = action.payload;
|
||||
const isDirect = state.get('privacy') === 'direct';
|
||||
return state
|
||||
.set('quoted_status_id', status.get('id'))
|
||||
.set('quoted_status_id', isDirect ? null : status.get('id'))
|
||||
.set('spoiler', status.get('sensitive'))
|
||||
.set('spoiler_text', status.get('spoiler_text'))
|
||||
.update('privacy', (visibility) => ['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private' ? 'private' : visibility);
|
||||
.update('privacy', (visibility) => {
|
||||
if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') {
|
||||
return 'private';
|
||||
}
|
||||
return visibility;
|
||||
});
|
||||
} else if (quoteComposeCancel.match(action)) {
|
||||
return state.set('quoted_status_id', null);
|
||||
} else if (setComposeQuotePolicy.match(action)) {
|
||||
return state.set('quote_policy', action.payload);
|
||||
} else if (pasteLinkCompose.pending.match(action)) {
|
||||
return state.set('fetching_link', action.meta.requestId);
|
||||
} else if (pasteLinkCompose.fulfilled.match(action) || pasteLinkCompose.rejected.match(action)) {
|
||||
return action.meta.requestId === state.get('fetching_link') ? state.set('fetching_link', null) : state;
|
||||
} else if (cancelPasteLinkCompose.match(action)) {
|
||||
return state.set('fetching_link', null);
|
||||
}
|
||||
|
||||
switch(action.type) {
|
||||
@@ -462,10 +481,6 @@ export const composeReducer = (state = initialState, action) => {
|
||||
return state
|
||||
.set('spoiler_text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_VISIBILITY_CHANGE:
|
||||
return state
|
||||
.set('privacy', action.value)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_CONTENT_TYPE_CHANGE:
|
||||
return state
|
||||
.set('content_type', action.value)
|
||||
@@ -490,6 +505,7 @@ export const composeReducer = (state = initialState, action) => {
|
||||
map.set('caretPosition', null);
|
||||
map.set('preselectDate', new Date());
|
||||
map.set('idempotencyKey', uuid());
|
||||
map.set('quoted_status_id', null);
|
||||
|
||||
map.update('media_attachments', list => list.filter(media => media.get('unattached')));
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ function getStatusResultFunction(
|
||||
};
|
||||
}
|
||||
|
||||
if (statusBase.get('isLoading')) {
|
||||
if (statusBase.get('isLoading') && !statusBase.get('content')) {
|
||||
return {
|
||||
status: null,
|
||||
loadingState: 'loading',
|
||||
@@ -75,7 +75,7 @@ function getStatusResultFunction(
|
||||
map.set('matched_filters', filtered);
|
||||
map.set('matched_media_filters', mediaFiltered);
|
||||
}),
|
||||
loadingState: 'complete'
|
||||
loadingState: statusBase.get('isLoading') ? 'loading' : 'complete'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ interface AppThunkConfig {
|
||||
}
|
||||
export type AppThunkApi = Pick<
|
||||
GetThunkAPI<AppThunkConfig>,
|
||||
'getState' | 'dispatch'
|
||||
'getState' | 'dispatch' | 'requestId'
|
||||
>;
|
||||
|
||||
interface AppThunkOptions<Arg> {
|
||||
@@ -60,7 +60,7 @@ type AppThunk<Arg = void, Returned = void> = (
|
||||
|
||||
type AppThunkCreator<Arg = void, Returned = void, ExtraArg = unknown> = (
|
||||
arg: Arg,
|
||||
api: AppThunkApi,
|
||||
api: Pick<AppThunkApi, 'getState' | 'dispatch'>,
|
||||
extra?: ExtraArg,
|
||||
) => Returned;
|
||||
|
||||
@@ -143,10 +143,10 @@ export function createAsyncThunk<Arg = void, Returned = void>(
|
||||
name,
|
||||
async (
|
||||
arg: Arg,
|
||||
{ getState, dispatch, fulfillWithValue, rejectWithValue },
|
||||
{ getState, dispatch, requestId, fulfillWithValue, rejectWithValue },
|
||||
) => {
|
||||
try {
|
||||
const result = await creator(arg, { dispatch, getState });
|
||||
const result = await creator(arg, { dispatch, getState, requestId });
|
||||
|
||||
return fulfillWithValue(result, {
|
||||
useLoadingBar: options.useLoadingBar,
|
||||
@@ -280,10 +280,11 @@ export function createDataLoadingThunk<
|
||||
|
||||
return createAsyncThunk<Args, Returned>(
|
||||
name,
|
||||
async (arg, { getState, dispatch }) => {
|
||||
async (arg, { getState, dispatch, requestId }) => {
|
||||
const data = await loadData(arg, {
|
||||
dispatch,
|
||||
getState,
|
||||
requestId,
|
||||
});
|
||||
|
||||
if (!onData) return data as Returned;
|
||||
@@ -291,6 +292,7 @@ export function createDataLoadingThunk<
|
||||
const result = await onData(data, {
|
||||
dispatch,
|
||||
getState,
|
||||
requestId,
|
||||
discardLoadData: discardLoadDataInPayload,
|
||||
actionArg: arg,
|
||||
});
|
||||
|
||||
@@ -1330,6 +1330,10 @@ a.sparkline {
|
||||
line-height: 1;
|
||||
width: 100%;
|
||||
animation: skeleton 1.2s ease-in-out infinite;
|
||||
|
||||
.reduce-motion & {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
|
||||
@@ -775,16 +775,43 @@
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
&__preview,
|
||||
&__visualizer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
border-radius: 6px;
|
||||
inset-inline-start: 0;
|
||||
}
|
||||
|
||||
&__visualizer {
|
||||
padding: 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
.audio-player__visualizer {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
inset-inline-start: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0.75;
|
||||
color: var(--player-foreground-color);
|
||||
filter: var(--overlay-icon-shadow);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
&__thumbnail {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -3217,20 +3244,21 @@ a.account__display-name {
|
||||
}
|
||||
|
||||
.column__alert {
|
||||
--alert-height: 54px;
|
||||
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
grid-template-rows: minmax(var(--alert-height), max-content);
|
||||
align-items: end;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
padding: 1rem;
|
||||
margin: auto auto 0;
|
||||
overflow: clip;
|
||||
|
||||
&:empty {
|
||||
padding: 0;
|
||||
}
|
||||
pointer-events: none;
|
||||
|
||||
@media (max-width: #{$mobile-menu-breakpoint - 1}) {
|
||||
// Compensate for mobile menubar
|
||||
@@ -3241,6 +3269,7 @@ a.account__display-name {
|
||||
// Make all nested alerts occupy the same space
|
||||
// rather than stack
|
||||
grid-area: 1 / 1;
|
||||
pointer-events: initial;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4542,13 +4571,19 @@ a.status-card {
|
||||
box-sizing: border-box;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--on-surface-color);
|
||||
&--large {
|
||||
padding-block: 32px;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $ui-button-focus-outline-color;
|
||||
outline-offset: -2px;
|
||||
&:is(button) {
|
||||
&:hover {
|
||||
background: var(--on-surface-color);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $ui-button-focus-outline-color;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
@@ -5980,6 +6015,34 @@ a.status-card {
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-modal {
|
||||
&__quote-warning {
|
||||
color: var(--nested-card-text);
|
||||
background:
|
||||
/* This is a bit of a silly hack for layering two background colours
|
||||
* since --nested-card-background is too transparent for a tooltip */
|
||||
linear-gradient(
|
||||
var(--nested-card-background),
|
||||
var(--nested-card-background)
|
||||
),
|
||||
linear-gradient(var(--background-color), var(--background-color));
|
||||
border: var(--nested-card-border);
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
|
||||
h3 {
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
color: $darker-text-color;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.8em;
|
||||
color: $dark-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.visibility-dropdown {
|
||||
&__overlay[data-popper-placement] {
|
||||
z-index: 9999;
|
||||
|
||||
@@ -56,7 +56,6 @@ export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
|
||||
export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
|
||||
export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
|
||||
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
|
||||
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
|
||||
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
|
||||
export const COMPOSE_LANGUAGE_CHANGE = 'COMPOSE_LANGUAGE_CHANGE';
|
||||
|
||||
@@ -796,13 +795,6 @@ export function changeComposeSpoilerText(text) {
|
||||
};
|
||||
}
|
||||
|
||||
export function changeComposeVisibility(value) {
|
||||
return {
|
||||
type: COMPOSE_VISIBILITY_CHANGE,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function insertEmojiCompose(position, emoji, needsSpace) {
|
||||
return {
|
||||
type: COMPOSE_EMOJI_INSERT,
|
||||
|
||||
@@ -13,10 +13,11 @@ import {
|
||||
} from 'mastodon/store/typed_functions';
|
||||
|
||||
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||
import type { Status } from '../models/status';
|
||||
import type { Status, StatusVisibility } from '../models/status';
|
||||
import type { RootState } from '../store';
|
||||
|
||||
import { showAlert } from './alerts';
|
||||
import { focusCompose } from './compose';
|
||||
import { changeCompose, focusCompose } from './compose';
|
||||
import { importFetchedStatuses } from './importer';
|
||||
import { openModal } from './modal';
|
||||
|
||||
@@ -41,6 +42,10 @@ const messages = defineMessages({
|
||||
id: 'quote_error.unauthorized',
|
||||
defaultMessage: 'You are not authorized to quote this post.',
|
||||
},
|
||||
quoteErrorPrivateMention: {
|
||||
id: 'quote_error.private_mentions',
|
||||
defaultMessage: 'Quoting is not allowed with direct mentions.',
|
||||
},
|
||||
});
|
||||
|
||||
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
||||
@@ -67,6 +72,39 @@ const simulateModifiedApiResponse = (
|
||||
return data;
|
||||
};
|
||||
|
||||
export const changeComposeVisibility = createAppThunk(
|
||||
'compose/visibility_change',
|
||||
(visibility: StatusVisibility, { dispatch, getState }) => {
|
||||
if (visibility !== 'direct') {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const quotedStatusId = state.compose.get('quoted_status_id') as
|
||||
| string
|
||||
| null;
|
||||
if (!quotedStatusId) {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
// Remove the quoted status
|
||||
dispatch(quoteComposeCancel());
|
||||
const quotedStatus = state.statuses.get(quotedStatusId) as Status | null;
|
||||
if (!quotedStatus) {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
// Append the quoted status URL to the compose text
|
||||
const url = quotedStatus.get('url') as string;
|
||||
const text = state.compose.get('text') as string;
|
||||
if (!text.includes(url)) {
|
||||
const newText = text.trim() ? `${text}\n\n${url}` : url;
|
||||
dispatch(changeCompose(newText));
|
||||
}
|
||||
return visibility;
|
||||
},
|
||||
);
|
||||
|
||||
export const changeUploadCompose = createDataLoadingThunk(
|
||||
'compose/changeUpload',
|
||||
async (
|
||||
@@ -130,6 +168,8 @@ export const quoteComposeByStatus = createAppThunk(
|
||||
|
||||
if (composeState.get('id')) {
|
||||
dispatch(showAlert({ message: messages.quoteErrorEdit }));
|
||||
} else if (composeState.get('privacy') === 'direct') {
|
||||
dispatch(showAlert({ message: messages.quoteErrorPrivateMention }));
|
||||
} else if (composeState.get('poll')) {
|
||||
dispatch(showAlert({ message: messages.quoteErrorPoll }));
|
||||
} else if (
|
||||
@@ -173,6 +213,17 @@ export const quoteComposeById = createAppThunk(
|
||||
},
|
||||
);
|
||||
|
||||
const composeStateForbidsLink = (composeState: RootState['compose']) => {
|
||||
return (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id') ||
|
||||
composeState.get('privacy') === 'direct'
|
||||
);
|
||||
};
|
||||
|
||||
export const pasteLinkCompose = createDataLoadingThunk(
|
||||
'compose/pasteLink',
|
||||
async ({ url }: { url: string }) => {
|
||||
@@ -183,15 +234,12 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
limit: 2,
|
||||
});
|
||||
},
|
||||
(data, { dispatch, getState }) => {
|
||||
(data, { dispatch, getState, requestId }) => {
|
||||
const composeState = getState().compose;
|
||||
|
||||
if (
|
||||
composeState.get('quoted_status_id') ||
|
||||
composeState.get('is_submitting') ||
|
||||
composeState.get('poll') ||
|
||||
composeState.get('is_uploading') ||
|
||||
composeState.get('id')
|
||||
composeStateForbidsLink(composeState) ||
|
||||
composeState.get('fetching_link') !== requestId // Request has been cancelled
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -207,6 +255,17 @@ export const pasteLinkCompose = createDataLoadingThunk(
|
||||
dispatch(quoteComposeById(data.statuses[0].id));
|
||||
}
|
||||
},
|
||||
{
|
||||
useLoadingBar: false,
|
||||
condition: (_, { getState }) =>
|
||||
!getState().compose.get('fetching_link') &&
|
||||
!composeStateForbidsLink(getState().compose),
|
||||
},
|
||||
);
|
||||
|
||||
// Ideally this would cancel the action and the HTTP request, but this is good enough
|
||||
export const cancelPasteLinkCompose = createAction(
|
||||
'compose/cancelPasteLinkCompose',
|
||||
);
|
||||
|
||||
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
||||
|
||||
@@ -46,11 +46,11 @@ export function importFetchedAccounts(accounts) {
|
||||
return importAccounts({ accounts: normalAccounts });
|
||||
}
|
||||
|
||||
export function importFetchedStatus(status) {
|
||||
return importFetchedStatuses([status]);
|
||||
export function importFetchedStatus(status, options = {}) {
|
||||
return importFetchedStatuses([status], options);
|
||||
}
|
||||
|
||||
export function importFetchedStatuses(statuses) {
|
||||
export function importFetchedStatuses(statuses, options = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const accounts = [];
|
||||
const normalStatuses = [];
|
||||
@@ -58,7 +58,7 @@ export function importFetchedStatuses(statuses) {
|
||||
const filters = [];
|
||||
|
||||
function processStatus(status) {
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
|
||||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), options));
|
||||
pushUnique(accounts, status.account);
|
||||
|
||||
if (status.filtered) {
|
||||
|
||||
@@ -27,9 +27,12 @@ function stripQuoteFallback(text) {
|
||||
return wrapper.innerHTML;
|
||||
}
|
||||
|
||||
export function normalizeStatus(status, normalOldStatus) {
|
||||
export function normalizeStatus(status, normalOldStatus, { bogusQuotePolicy = false }) {
|
||||
const normalStatus = { ...status };
|
||||
|
||||
if (bogusQuotePolicy)
|
||||
normalStatus.quote_approval = null;
|
||||
|
||||
normalStatus.account = status.account.id;
|
||||
|
||||
if (status.reblog && status.reblog.id) {
|
||||
@@ -109,6 +112,8 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
}
|
||||
|
||||
if (normalOldStatus) {
|
||||
normalStatus.quote_approval ||= normalOldStatus.quote_approval;
|
||||
|
||||
const list = normalOldStatus.get('media_attachments');
|
||||
if (normalStatus.media_attachments && list) {
|
||||
normalStatus.media_attachments.forEach(item => {
|
||||
|
||||
@@ -203,8 +203,8 @@ export function deleteStatusFail(id, error) {
|
||||
};
|
||||
}
|
||||
|
||||
export const updateStatus = status => dispatch =>
|
||||
dispatch(importFetchedStatus(status));
|
||||
export const updateStatus = (status, { bogusQuotePolicy }) => dispatch =>
|
||||
dispatch(importFetchedStatus(status, { bogusQuotePolicy }));
|
||||
|
||||
export function muteStatus(id) {
|
||||
return (dispatch) => {
|
||||
|
||||
@@ -52,6 +52,9 @@ const randomUpTo = max =>
|
||||
export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) => {
|
||||
const { messages } = getLocale();
|
||||
|
||||
// Public streams are currently not returning personalized quote policies
|
||||
const bogusQuotePolicy = channelName.startsWith('public') || channelName.startsWith('hashtag');
|
||||
|
||||
return connectStream(channelName, params, (dispatch, getState) => {
|
||||
// @ts-ignore
|
||||
const locale = getState().getIn(['meta', 'locale']);
|
||||
@@ -97,11 +100,11 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
||||
switch (data.event) {
|
||||
case 'update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), { accept: options.accept, bogusQuotePolicy }));
|
||||
break;
|
||||
case 'status.update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateStatus(JSON.parse(data.payload)));
|
||||
dispatch(updateStatus(JSON.parse(data.payload), { bogusQuotePolicy }));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
|
||||
@@ -32,7 +32,7 @@ export const loadPending = timeline => ({
|
||||
timeline,
|
||||
});
|
||||
|
||||
export function updateTimeline(timeline, status, accept) {
|
||||
export function updateTimeline(timeline, status, { accept = undefined, bogusQuotePolicy = false } = {}) {
|
||||
return (dispatch, getState) => {
|
||||
if (typeof accept === 'function' && !accept(status)) {
|
||||
return;
|
||||
@@ -45,7 +45,7 @@ export function updateTimeline(timeline, status, accept) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(importFetchedStatus(status));
|
||||
dispatch(importFetchedStatus(status, { bogusQuotePolicy }));
|
||||
|
||||
dispatch({
|
||||
type: TIMELINE_UPDATE,
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
closeDropdownMenu,
|
||||
} from 'mastodon/actions/dropdown_menu';
|
||||
import { openModal, closeModal } from 'mastodon/actions/modal';
|
||||
import { fetchStatus } from 'mastodon/actions/statuses';
|
||||
import { CircularProgress } from 'mastodon/components/circular_progress';
|
||||
import { isUserTouching } from 'mastodon/is_mobile';
|
||||
import {
|
||||
@@ -42,16 +43,10 @@ import { IconButton } from './icon_button';
|
||||
|
||||
let id = 0;
|
||||
|
||||
export interface RenderItemFnHandlers {
|
||||
onClick: React.MouseEventHandler;
|
||||
onKeyUp: React.KeyboardEventHandler;
|
||||
}
|
||||
|
||||
export type RenderItemFn<Item = MenuItem> = (
|
||||
item: Item,
|
||||
index: number,
|
||||
handlers: RenderItemFnHandlers,
|
||||
focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void,
|
||||
onClick: React.MouseEventHandler,
|
||||
) => React.ReactNode;
|
||||
|
||||
type ItemClickFn<Item = MenuItem> = (item: Item, index: number) => void;
|
||||
@@ -101,7 +96,6 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
onItemClick,
|
||||
}: DropdownMenuProps<Item>) => {
|
||||
const nodeRef = useRef<HTMLDivElement>(null);
|
||||
const focusedItemRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleDocumentClick = (e: MouseEvent) => {
|
||||
@@ -163,8 +157,11 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
document.addEventListener('click', handleDocumentClick, { capture: true });
|
||||
document.addEventListener('keydown', handleKeyDown, { capture: true });
|
||||
|
||||
if (focusedItemRef.current && openedViaKeyboard) {
|
||||
focusedItemRef.current.focus({ preventScroll: true });
|
||||
if (openedViaKeyboard) {
|
||||
const firstMenuItem = nodeRef.current?.querySelector<
|
||||
HTMLAnchorElement | HTMLButtonElement
|
||||
>('li:first-child > :is(a, button)');
|
||||
firstMenuItem?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
return () => {
|
||||
@@ -175,13 +172,6 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
};
|
||||
}, [onClose, openedViaKeyboard]);
|
||||
|
||||
const handleFocusedItemRef = useCallback(
|
||||
(c: HTMLAnchorElement | HTMLButtonElement | null) => {
|
||||
focusedItemRef.current = c as HTMLElement;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
@@ -207,15 +197,6 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
[onClose, onItemClick, items],
|
||||
);
|
||||
|
||||
const handleItemKeyUp = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
handleItemClick(e);
|
||||
}
|
||||
},
|
||||
[handleItemClick],
|
||||
);
|
||||
|
||||
const nativeRenderItem = (option: Item, i: number) => {
|
||||
if (!isMenuItem(option)) {
|
||||
return null;
|
||||
@@ -232,9 +213,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
if (isActionItem(option)) {
|
||||
element = (
|
||||
<button
|
||||
ref={i === 0 ? handleFocusedItemRef : undefined}
|
||||
onClick={handleItemClick}
|
||||
onKeyUp={handleItemKeyUp}
|
||||
data-index={i}
|
||||
aria-disabled={disabled}
|
||||
>
|
||||
@@ -248,9 +227,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
target={option.target ?? '_target'}
|
||||
data-method={option.method}
|
||||
rel='noopener'
|
||||
ref={i === 0 ? handleFocusedItemRef : undefined}
|
||||
onClick={handleItemClick}
|
||||
onKeyUp={handleItemKeyUp}
|
||||
data-index={i}
|
||||
>
|
||||
<DropdownMenuItemContent item={option} />
|
||||
@@ -258,13 +235,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
);
|
||||
} else {
|
||||
element = (
|
||||
<Link
|
||||
to={option.to}
|
||||
ref={i === 0 ? handleFocusedItemRef : undefined}
|
||||
onClick={handleItemClick}
|
||||
onKeyUp={handleItemKeyUp}
|
||||
data-index={i}
|
||||
>
|
||||
<Link to={option.to} onClick={handleItemClick} data-index={i}>
|
||||
<DropdownMenuItemContent item={option} />
|
||||
</Link>
|
||||
);
|
||||
@@ -307,15 +278,7 @@ export const DropdownMenu = <Item = MenuItem,>({
|
||||
})}
|
||||
>
|
||||
{items.map((option, i) =>
|
||||
renderItemMethod(
|
||||
option,
|
||||
i,
|
||||
{
|
||||
onClick: handleItemClick,
|
||||
onKeyUp: handleItemKeyUp,
|
||||
},
|
||||
i === 0 ? handleFocusedItemRef : undefined,
|
||||
),
|
||||
renderItemMethod(option, i, handleItemClick),
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
@@ -340,6 +303,7 @@ interface DropdownProps<Item extends object | null = MenuItem> {
|
||||
*/
|
||||
scrollKey?: string;
|
||||
status?: ImmutableMap<string, unknown>;
|
||||
needsStatusRefresh?: boolean;
|
||||
forceDropdown?: boolean;
|
||||
renderItem?: RenderItemFn<Item>;
|
||||
renderHeader?: RenderHeaderFn<Item>;
|
||||
@@ -363,6 +327,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
placement = 'bottom',
|
||||
offset = [5, 5],
|
||||
status,
|
||||
needsStatusRefresh,
|
||||
forceDropdown = false,
|
||||
renderItem,
|
||||
renderHeader,
|
||||
@@ -382,6 +347,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
const prefetchAccountId = status
|
||||
? status.getIn(['account', 'id'])
|
||||
: undefined;
|
||||
const statusId = status?.get('id') as string | undefined;
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
if (buttonRef.current) {
|
||||
@@ -399,7 +365,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
}, [dispatch, currentId]);
|
||||
|
||||
const handleItemClick = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
(e: React.MouseEvent) => {
|
||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const item = items?.[i];
|
||||
|
||||
@@ -420,10 +386,20 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
[handleClose, onItemClick, items],
|
||||
);
|
||||
|
||||
const toggleDropdown = useCallback(
|
||||
(e: React.MouseEvent | React.KeyboardEvent) => {
|
||||
const { type } = e;
|
||||
const isKeypressRef = useRef(false);
|
||||
|
||||
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
isKeypressRef.current = true;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const unsetIsKeypress = useCallback(() => {
|
||||
isKeypressRef.current = false;
|
||||
}, []);
|
||||
|
||||
const toggleDropdown = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
if (open) {
|
||||
handleClose();
|
||||
} else {
|
||||
@@ -436,6 +412,15 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
dispatch(fetchRelationships([prefetchAccountId]));
|
||||
}
|
||||
|
||||
if (needsStatusRefresh && statusId) {
|
||||
dispatch(
|
||||
fetchStatus(statusId, {
|
||||
forceFetch: true,
|
||||
alsoFetchContext: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (isUserTouching() && !forceDropdown) {
|
||||
dispatch(
|
||||
openModal({
|
||||
@@ -450,10 +435,11 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
dispatch(
|
||||
openDropdownMenu({
|
||||
id: currentId,
|
||||
keyboard: type !== 'click',
|
||||
keyboard: isKeypressRef.current,
|
||||
scrollKey,
|
||||
}),
|
||||
);
|
||||
isKeypressRef.current = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -468,6 +454,8 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
items,
|
||||
forceDropdown,
|
||||
handleClose,
|
||||
statusId,
|
||||
needsStatusRefresh,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -484,6 +472,9 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
||||
const buttonProps = {
|
||||
disabled,
|
||||
onClick: toggleDropdown,
|
||||
onKeyDown: handleKeyDown,
|
||||
onKeyUp: unsetIsKeypress,
|
||||
onBlur: unsetIsKeypress,
|
||||
'aria-expanded': open,
|
||||
'aria-controls': menuId,
|
||||
ref: buttonRef,
|
||||
|
||||
@@ -58,17 +58,7 @@ export const EditedTimestamp: React.FC<{
|
||||
}, []);
|
||||
|
||||
const renderItem = useCallback(
|
||||
(
|
||||
item: HistoryItem,
|
||||
index: number,
|
||||
{
|
||||
onClick,
|
||||
onKeyUp,
|
||||
}: {
|
||||
onClick: React.MouseEventHandler;
|
||||
onKeyUp: React.KeyboardEventHandler;
|
||||
},
|
||||
) => {
|
||||
(item: HistoryItem, index: number, onClick: React.MouseEventHandler) => {
|
||||
const formattedDate = (
|
||||
<RelativeTimestamp
|
||||
timestamp={item.get('created_at') as string}
|
||||
@@ -98,7 +88,7 @@ export const EditedTimestamp: React.FC<{
|
||||
className='dropdown-menu__item edited-timestamp__history__item'
|
||||
key={item.get('created_at') as string}
|
||||
>
|
||||
<button data-index={index} onClick={onClick} onKeyUp={onKeyUp}>
|
||||
<button data-index={index} onClick={onClick} type='button'>
|
||||
{label}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -8,13 +8,14 @@ import classNames from 'classnames';
|
||||
import { quoteComposeById } from '@/mastodon/actions/compose_typed';
|
||||
import { toggleReblog } from '@/mastodon/actions/interactions';
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import { fetchStatus } from '@/mastodon/actions/statuses';
|
||||
import { quickBoosting } from '@/mastodon/initial_state';
|
||||
import type { ActionMenuItem } from '@/mastodon/models/dropdown_menu';
|
||||
import type { Status } from '@/mastodon/models/status';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
import type { SomeRequired } from '@/mastodon/utils/types';
|
||||
|
||||
import type { RenderItemFn, RenderItemFnHandlers } from '../dropdown_menu';
|
||||
import type { RenderItemFn } from '../dropdown_menu';
|
||||
import { Dropdown, DropdownMenuItemContent } from '../dropdown_menu';
|
||||
import { IconButton } from '../icon_button';
|
||||
|
||||
@@ -74,18 +75,12 @@ const StandaloneBoostButton: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderMenuItem: RenderItemFn<ActionMenuItem> = (
|
||||
item,
|
||||
index,
|
||||
handlers,
|
||||
focusRefCallback,
|
||||
) => (
|
||||
const renderMenuItem: RenderItemFn<ActionMenuItem> = (item, index, onClick) => (
|
||||
<ReblogMenuItem
|
||||
index={index}
|
||||
item={item}
|
||||
handlers={handlers}
|
||||
onClick={onClick}
|
||||
key={`${item.text}-${index}`}
|
||||
focusRefCallback={focusRefCallback}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -117,6 +112,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
|
||||
const statusId = status.get('id') as string;
|
||||
const wasBoosted = !!status.get('reblogged');
|
||||
const quoteApproval = status.get('quote_approval');
|
||||
|
||||
const showLoginPrompt = useCallback(() => {
|
||||
dispatch(
|
||||
@@ -173,9 +169,16 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
dispatch(toggleReblog(status.get('id'), true));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (quoteApproval === null) {
|
||||
dispatch(
|
||||
fetchStatus(statusId, { forceFetch: true, alsoFetchContext: false }),
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
[dispatch, isLoggedIn, showLoginPrompt, status],
|
||||
[dispatch, isLoggedIn, showLoginPrompt, status, quoteApproval, statusId],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -208,16 +211,10 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
interface ReblogMenuItemProps {
|
||||
item: ActionMenuItem;
|
||||
index: number;
|
||||
handlers: RenderItemFnHandlers;
|
||||
focusRefCallback?: (c: HTMLAnchorElement | HTMLButtonElement | null) => void;
|
||||
onClick: React.MouseEventHandler;
|
||||
}
|
||||
|
||||
const ReblogMenuItem: FC<ReblogMenuItemProps> = ({
|
||||
index,
|
||||
item,
|
||||
handlers,
|
||||
focusRefCallback,
|
||||
}) => {
|
||||
const ReblogMenuItem: FC<ReblogMenuItemProps> = ({ index, item, onClick }) => {
|
||||
const { text, highlighted, disabled } = item;
|
||||
|
||||
return (
|
||||
@@ -227,12 +224,7 @@ const ReblogMenuItem: FC<ReblogMenuItemProps> = ({
|
||||
})}
|
||||
key={`${text}-${index}`}
|
||||
>
|
||||
<button
|
||||
{...handlers}
|
||||
ref={focusRefCallback}
|
||||
aria-disabled={disabled}
|
||||
data-index={index}
|
||||
>
|
||||
<button onClick={onClick} aria-disabled={disabled} data-index={index}>
|
||||
<DropdownMenuItemContent item={item} />
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -404,6 +404,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<Dropdown
|
||||
scrollKey={scrollKey}
|
||||
status={status}
|
||||
needsStatusRefresh={quickBoosting && status.get('quote_approval') === null}
|
||||
items={menu}
|
||||
icon='ellipsis-h'
|
||||
iconComponent={MoreHorizIcon}
|
||||
|
||||
@@ -49,6 +49,7 @@ export const StatusBanner: React.FC<{
|
||||
|
||||
<button
|
||||
ref={buttonRef}
|
||||
type='button'
|
||||
className='link-button'
|
||||
onClick={onClick}
|
||||
aria-describedby={descriptionId}
|
||||
|
||||
@@ -169,9 +169,13 @@ const localeOptionsSelector = createSelector(
|
||||
},
|
||||
};
|
||||
// Use the default locale as a target to translate language names.
|
||||
const intlLocale = new Intl.DisplayNames(intl.locale, {
|
||||
type: 'language',
|
||||
});
|
||||
const intlLocale =
|
||||
// Intl.DisplayNames can be undefined in old browsers
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
Intl.DisplayNames &&
|
||||
(new Intl.DisplayNames(intl.locale, {
|
||||
type: 'language',
|
||||
}) as Intl.DisplayNames | undefined);
|
||||
for (const { translations } of rules) {
|
||||
for (const locale in translations) {
|
||||
if (langs[locale]) {
|
||||
@@ -179,7 +183,7 @@ const localeOptionsSelector = createSelector(
|
||||
}
|
||||
langs[locale] = {
|
||||
value: locale,
|
||||
text: intlLocale.of(locale) ?? locale,
|
||||
text: intlLocale?.of(locale) ?? locale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
});
|
||||
}, [dispatch, setIsSaving, mediaId, onClose, position, description]);
|
||||
|
||||
const handleKeyUp = useCallback(
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
@@ -457,7 +457,7 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
||||
id='description'
|
||||
value={isDetecting ? ' ' : description}
|
||||
onChange={handleDescriptionChange}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
lang={lang}
|
||||
placeholder={intl.formatMessage(
|
||||
type === 'audio'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useCallback, useState, useId } from 'react';
|
||||
import { useEffect, useRef, useCallback, useState } from 'react';
|
||||
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -22,6 +22,8 @@ import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer';
|
||||
import { displayMedia, useBlurhash } from 'mastodon/initial_state';
|
||||
import { playerSettings } from 'mastodon/settings';
|
||||
|
||||
import { AudioVisualizer } from './visualizer';
|
||||
|
||||
const messages = defineMessages({
|
||||
play: { id: 'video.play', defaultMessage: 'Play' },
|
||||
pause: { id: 'video.pause', defaultMessage: 'Pause' },
|
||||
@@ -116,7 +118,6 @@ export const Audio: React.FC<{
|
||||
const seekRef = useRef<HTMLDivElement>(null);
|
||||
const volumeRef = useRef<HTMLDivElement>(null);
|
||||
const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>();
|
||||
const accessibilityId = useId();
|
||||
|
||||
const { audioContextRef, sourceRef, gainNodeRef, playAudio, pauseAudio } =
|
||||
useAudioContext({ audioElementRef: audioRef });
|
||||
@@ -538,19 +539,6 @@ export const Audio: React.FC<{
|
||||
[togglePlay, toggleMute],
|
||||
);
|
||||
|
||||
const springForBand0 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[0] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand1 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[1] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand2 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[2] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
|
||||
const progress = Math.min((currentTime / loadedDuration) * 100, 100);
|
||||
const effectivelyMuted = muted || volume === 0;
|
||||
|
||||
@@ -641,81 +629,7 @@ export const Audio: React.FC<{
|
||||
</div>
|
||||
|
||||
<div className='audio-player__controls__play'>
|
||||
<svg
|
||||
className='audio-player__visualizer'
|
||||
viewBox='0 0 124 124'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={57}
|
||||
cy={62.5}
|
||||
r={springForBand0.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={65}
|
||||
cy={57.5}
|
||||
r={springForBand1.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={63}
|
||||
cy={66.5}
|
||||
r={springForBand2.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
|
||||
<g clipPath={`url(#${accessibilityId}-clip)`}>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill={`url(#${accessibilityId}-pattern)`}
|
||||
/>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill='var(--player-background-color'
|
||||
opacity={0.45}
|
||||
/>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<pattern
|
||||
id={`${accessibilityId}-pattern`}
|
||||
patternContentUnits='objectBoundingBox'
|
||||
width='1'
|
||||
height='1'
|
||||
>
|
||||
<use href={`#${accessibilityId}-image`} />
|
||||
</pattern>
|
||||
|
||||
<clipPath id={`${accessibilityId}-clip`}>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
rx={48}
|
||||
fill='white'
|
||||
/>
|
||||
</clipPath>
|
||||
|
||||
<image
|
||||
id={`${accessibilityId}-image`}
|
||||
href={poster}
|
||||
width={1}
|
||||
height={1}
|
||||
preserveAspectRatio='none'
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
<AudioVisualizer frequencyBands={frequencyBands} poster={poster} />
|
||||
|
||||
<button
|
||||
type='button'
|
||||
|
||||
100
app/javascript/mastodon/features/audio/visualizer.tsx
Normal file
100
app/javascript/mastodon/features/audio/visualizer.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { useId } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { animated, config, useSpring } from '@react-spring/web';
|
||||
|
||||
interface AudioVisualizerProps {
|
||||
frequencyBands?: number[];
|
||||
poster?: string;
|
||||
}
|
||||
|
||||
export const AudioVisualizer: FC<AudioVisualizerProps> = ({
|
||||
frequencyBands = [],
|
||||
poster,
|
||||
}) => {
|
||||
const accessibilityId = useId();
|
||||
|
||||
const springForBand0 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[0] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand1 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[1] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
const springForBand2 = useSpring({
|
||||
to: { r: 50 + (frequencyBands[2] ?? 0) * 10 },
|
||||
config: config.wobbly,
|
||||
});
|
||||
|
||||
return (
|
||||
<svg
|
||||
className='audio-player__visualizer'
|
||||
viewBox='0 0 124 124'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={57}
|
||||
cy={62.5}
|
||||
r={springForBand0.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={65}
|
||||
cy={57.5}
|
||||
r={springForBand1.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
<animated.circle
|
||||
opacity={0.5}
|
||||
cx={63}
|
||||
cy={66.5}
|
||||
r={springForBand2.r}
|
||||
fill='var(--player-accent-color)'
|
||||
/>
|
||||
|
||||
<g clipPath={`url(#${accessibilityId}-clip)`}>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill={`url(#${accessibilityId}-pattern)`}
|
||||
/>
|
||||
<rect
|
||||
x={14}
|
||||
y={14}
|
||||
width={96}
|
||||
height={96}
|
||||
fill='var(--player-background-color'
|
||||
opacity={0.45}
|
||||
/>
|
||||
</g>
|
||||
|
||||
<defs>
|
||||
<pattern
|
||||
id={`${accessibilityId}-pattern`}
|
||||
patternContentUnits='objectBoundingBox'
|
||||
width='1'
|
||||
height='1'
|
||||
>
|
||||
<use href={`#${accessibilityId}-image`} />
|
||||
</pattern>
|
||||
|
||||
<clipPath id={`${accessibilityId}-clip`}>
|
||||
<rect x={14} y={14} width={96} height={96} rx={48} fill='white' />
|
||||
</clipPath>
|
||||
|
||||
<image
|
||||
id={`${accessibilityId}-image`}
|
||||
href={poster}
|
||||
width={1}
|
||||
height={1}
|
||||
preserveAspectRatio='none'
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -140,7 +140,10 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.onSubmit(missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct');
|
||||
this.props.onSubmit({
|
||||
missingAltText: missingAltTextModal && this.props.missingAltText && this.props.privacy !== 'direct',
|
||||
quoteToPrivate: this.props.quoteToPrivate,
|
||||
});
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { cancelPasteLinkCompose } from '@/mastodon/actions/compose_typed';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
import CancelFillIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
|
||||
const messages = defineMessages({
|
||||
quote_cancel: { id: 'status.quote.cancel', defaultMessage: 'Cancel quote' },
|
||||
});
|
||||
|
||||
export const QuotePlaceholder: FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const handleQuoteCancel = useCallback(() => {
|
||||
dispatch(cancelPasteLinkCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<div className='status__quote'>
|
||||
<div className='status'>
|
||||
<div className='status__info'>
|
||||
<div className='status__avatar'>
|
||||
<Skeleton width='32px' height='32px' />
|
||||
</div>
|
||||
<div className='status__display-name'>
|
||||
<DisplayName />
|
||||
</div>
|
||||
<IconButton
|
||||
onClick={handleQuoteCancel}
|
||||
className='status__quote-cancel'
|
||||
title={intl.formatMessage(messages.quote_cancel)}
|
||||
icon='cancel-fill'
|
||||
iconComponent={CancelFillIcon}
|
||||
/>
|
||||
</div>
|
||||
<div className='status__content'>
|
||||
<Skeleton />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,11 +7,17 @@ import { quoteComposeCancel } from '@/mastodon/actions/compose_typed';
|
||||
import { QuotedStatus } from '@/mastodon/components/status_quoted';
|
||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||
|
||||
import { QuotePlaceholder } from './quote_placeholder';
|
||||
|
||||
export const ComposeQuotedStatus: FC = () => {
|
||||
const quotedStatusId = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') as string | null,
|
||||
);
|
||||
|
||||
const isFetchingLink = useAppSelector(
|
||||
(state) => !!state.compose.get('fetching_link'),
|
||||
);
|
||||
|
||||
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
|
||||
|
||||
const quote = useMemo(
|
||||
@@ -30,7 +36,9 @@ export const ComposeQuotedStatus: FC = () => {
|
||||
dispatch(quoteComposeCancel());
|
||||
}, [dispatch]);
|
||||
|
||||
if (!quote) {
|
||||
if (isFetchingLink && !quote) {
|
||||
return <QuotePlaceholder />;
|
||||
} else if (!quote) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-20px/close.svg?react';
|
||||
import SoundIcon from '@/material-icons/400-24px/audio.svg?react';
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import WarningIcon from '@/material-icons/400-24px/warning.svg?react';
|
||||
import { undoUploadCompose } from 'mastodon/actions/compose';
|
||||
@@ -17,7 +18,18 @@ import { openModal } from 'mastodon/actions/modal';
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import type { MediaAttachment } from 'mastodon/models/media_attachment';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
import {
|
||||
createAppSelector,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from 'mastodon/store';
|
||||
|
||||
import { AudioVisualizer } from '../../audio/visualizer';
|
||||
|
||||
const selectUserAvatar = createAppSelector(
|
||||
[(state) => state.accounts, (state) => state.meta.get('me') as string],
|
||||
(accounts, myId) => accounts.get(myId)?.avatar_static,
|
||||
);
|
||||
|
||||
export const Upload: React.FC<{
|
||||
id: string;
|
||||
@@ -38,6 +50,7 @@ export const Upload: React.FC<{
|
||||
const sensitive = useAppSelector(
|
||||
(state) => state.compose.get('spoiler') as boolean,
|
||||
);
|
||||
const userAvatar = useAppSelector(selectUserAvatar);
|
||||
|
||||
const handleUndoClick = useCallback(() => {
|
||||
dispatch(undoUploadCompose(id));
|
||||
@@ -67,6 +80,8 @@ export const Upload: React.FC<{
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
};
|
||||
const preview_url = media.get('preview_url') as string | null;
|
||||
const blurhash = media.get('blurhash') as string | null;
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -85,17 +100,19 @@ export const Upload: React.FC<{
|
||||
<div
|
||||
className='compose-form__upload__thumbnail'
|
||||
style={{
|
||||
backgroundImage: !sensitive
|
||||
? `url(${media.get('preview_url') as string})`
|
||||
: undefined,
|
||||
backgroundImage:
|
||||
!sensitive && preview_url ? `url(${preview_url})` : undefined,
|
||||
backgroundPosition: `${x}% ${y}%`,
|
||||
}}
|
||||
>
|
||||
{sensitive && (
|
||||
<Blurhash
|
||||
hash={media.get('blurhash') as string}
|
||||
className='compose-form__upload__preview'
|
||||
/>
|
||||
{sensitive && blurhash && (
|
||||
<Blurhash hash={blurhash} className='compose-form__upload__preview' />
|
||||
)}
|
||||
{!sensitive && !preview_url && (
|
||||
<div className='compose-form__upload__visualizer'>
|
||||
<AudioVisualizer poster={userAvatar} />
|
||||
<Icon id='sound' icon={SoundIcon} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='compose-form__upload__actions'>
|
||||
|
||||
@@ -5,8 +5,10 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { changeComposeVisibility } from '@/mastodon/actions/compose';
|
||||
import { setComposeQuotePolicy } from '@/mastodon/actions/compose_typed';
|
||||
import {
|
||||
changeComposeVisibility,
|
||||
setComposeQuotePolicy,
|
||||
} from '@/mastodon/actions/compose_typed';
|
||||
import { openModal } from '@/mastodon/actions/modal';
|
||||
import type { ApiQuotePolicy } from '@/mastodon/api_types/quotes';
|
||||
import type { StatusVisibility } from '@/mastodon/api_types/statuses';
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from 'mastodon/actions/compose';
|
||||
import { pasteLinkCompose } from 'mastodon/actions/compose_typed';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { PRIVATE_QUOTE_MODAL_ID } from 'mastodon/features/ui/components/confirmation_modals/private_quote_notify';
|
||||
|
||||
import ComposeForm from '../components/compose_form';
|
||||
|
||||
@@ -32,6 +33,10 @@ const mapStateToProps = state => ({
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
|
||||
quoteToPrivate:
|
||||
!!state.getIn(['compose', 'quoted_status_id'])
|
||||
&& state.getIn(['compose', 'privacy']) === 'private'
|
||||
&& !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]),
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
|
||||
@@ -43,12 +48,17 @@ const mapDispatchToProps = (dispatch, props) => ({
|
||||
dispatch(changeCompose(text));
|
||||
},
|
||||
|
||||
onSubmit (missingAltText) {
|
||||
onSubmit ({ missingAltText, quoteToPrivate }) {
|
||||
if (missingAltText) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_MISSING_ALT_TEXT',
|
||||
modalProps: {},
|
||||
}));
|
||||
} else if (quoteToPrivate) {
|
||||
dispatch(openModal({
|
||||
modalType: 'CONFIRM_PRIVATE_QUOTE_NOTIFY',
|
||||
modalProps: {},
|
||||
}));
|
||||
} else {
|
||||
dispatch(submitCompose((status) => {
|
||||
if (props.redirectOnSuccess) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { changeComposeVisibility } from '../../../actions/compose';
|
||||
import { openModal, closeModal } from '../../../actions/modal';
|
||||
import { isUserTouching } from '../../../is_mobile';
|
||||
import { changeComposeVisibility } from '@/mastodon/actions/compose_typed';
|
||||
|
||||
import PrivacyDropdown from '../components/privacy_dropdown';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flattenEmojiData } from 'emojibase';
|
||||
import type { CompactEmoji, FlatCompactEmoji } from 'emojibase';
|
||||
import type { CompactEmoji, FlatCompactEmoji, Locale } from 'emojibase';
|
||||
|
||||
import {
|
||||
putEmojiData,
|
||||
@@ -43,9 +43,8 @@ async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
if (locale === 'custom') {
|
||||
url.pathname = '/api/v1/custom_emojis';
|
||||
} else {
|
||||
// This doesn't use isDevelopment() as that module loads initial state
|
||||
// which breaks workers, as they cannot access the DOM.
|
||||
url.pathname = `/packs${import.meta.env.DEV ? '-dev' : ''}/emoji/${locale}.json`;
|
||||
const modulePath = await localeToPath(locale);
|
||||
url.pathname = modulePath;
|
||||
}
|
||||
|
||||
const oldEtag = await loadLatestEtag(locale);
|
||||
@@ -80,3 +79,19 @@ async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const modules = import.meta.glob<string>(
|
||||
'../../../../../node_modules/emojibase-data/**/compact.json',
|
||||
{
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
},
|
||||
);
|
||||
|
||||
function localeToPath(locale: Locale) {
|
||||
const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`;
|
||||
if (!modules[key] || typeof modules[key] !== 'function') {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
return modules[key]();
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { dismissAnnouncement } from '@/mastodon/actions/announcements';
|
||||
import type { ApiAnnouncementJSON } from '@/mastodon/api_types/announcements';
|
||||
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
|
||||
import { ReactionsBar } from './reactions';
|
||||
|
||||
@@ -22,13 +24,23 @@ export const Announcement: FC<AnnouncementProps> = ({
|
||||
announcement,
|
||||
selected,
|
||||
}) => {
|
||||
const [unread, setUnread] = useState(!announcement.read);
|
||||
const { read, id } = announcement;
|
||||
|
||||
// Dismiss announcement when it becomes active.
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
// Only update `unread` marker once the announcement is out of view
|
||||
if (!selected && unread !== !announcement.read) {
|
||||
setUnread(!announcement.read);
|
||||
if (selected && !read) {
|
||||
dispatch(dismissAnnouncement(id));
|
||||
}
|
||||
}, [announcement.read, selected, unread]);
|
||||
}, [selected, id, dispatch, read]);
|
||||
|
||||
// But visually show the announcement as read only when it goes out of view.
|
||||
const [unread, setUnread] = useState(!read);
|
||||
useEffect(() => {
|
||||
if (!selected && unread !== !read) {
|
||||
setUnread(!read);
|
||||
}
|
||||
}, [selected, unread, read]);
|
||||
|
||||
return (
|
||||
<AnimateEmojiProvider className='announcements__item'>
|
||||
|
||||
@@ -417,6 +417,7 @@ export const DetailedStatus: React.FC<{
|
||||
<QuotedStatus
|
||||
quote={status.get('quote')}
|
||||
parentQuotePostId={status.get('id')}
|
||||
contextType='thread'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -295,7 +295,7 @@ export const RefreshController: React.FC<{
|
||||
if (loadingState === 'loading') {
|
||||
return (
|
||||
<div
|
||||
className='load-more load-gap'
|
||||
className='load-more load-more--large'
|
||||
aria-busy
|
||||
aria-live='polite'
|
||||
aria-label={intl.formatMessage(messages.loadingInitial)}
|
||||
|
||||
@@ -159,7 +159,7 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
UNSAFE_componentWillMount () {
|
||||
this.props.dispatch(fetchStatus(this.props.params.statusId));
|
||||
this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true }));
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -170,7 +170,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||
this.props.dispatch(fetchStatus(nextProps.params.statusId));
|
||||
this.props.dispatch(fetchStatus(nextProps.params.statusId, { forceFetch: true }));
|
||||
}
|
||||
|
||||
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
|
||||
@@ -299,6 +299,12 @@ class Status extends ImmutablePureComponent {
|
||||
dispatch(openModal({ modalType: 'COMPOSE_PRIVACY', modalProps: { statusId, onChange: handleChange } }));
|
||||
};
|
||||
|
||||
handleQuote = (status) => {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
dispatch(quoteComposeById(status.get('id')));
|
||||
};
|
||||
|
||||
handleEditClick = (status) => {
|
||||
const { dispatch, askReplyConfirmation } = this.props;
|
||||
|
||||
@@ -625,6 +631,7 @@ class Status extends ImmutablePureComponent {
|
||||
onDelete={this.handleDeleteClick}
|
||||
onRevokeQuote={this.handleRevokeQuoteClick}
|
||||
onQuotePolicyChange={this.handleQuotePolicyChange}
|
||||
onQuote={this.handleQuote}
|
||||
onEdit={this.handleEditClick}
|
||||
onDirect={this.handleDirectClick}
|
||||
onMention={this.handleMentionClick}
|
||||
|
||||
@@ -18,6 +18,7 @@ export const ConfirmationModal: React.FC<
|
||||
onSecondary?: () => void;
|
||||
onConfirm: () => void;
|
||||
closeWhenConfirm?: boolean;
|
||||
extraContent?: React.ReactNode;
|
||||
} & BaseConfirmationModalProps
|
||||
> = ({
|
||||
title,
|
||||
@@ -29,6 +30,7 @@ export const ConfirmationModal: React.FC<
|
||||
secondary,
|
||||
onSecondary,
|
||||
closeWhenConfirm = true,
|
||||
extraContent,
|
||||
}) => {
|
||||
const handleClick = useCallback(() => {
|
||||
if (closeWhenConfirm) {
|
||||
@@ -49,6 +51,8 @@ export const ConfirmationModal: React.FC<
|
||||
<div className='safety-action-modal__confirmation'>
|
||||
<h1>{title}</h1>
|
||||
{message && <p>{message}</p>}
|
||||
|
||||
{extraContent}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { forwardRef, useCallback, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { submitCompose } from '@/mastodon/actions/compose';
|
||||
import { changeSetting } from '@/mastodon/actions/settings';
|
||||
import { CheckBox } from '@/mastodon/components/check_box';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
|
||||
import { ConfirmationModal } from './confirmation_modal';
|
||||
import type { BaseConfirmationModalProps } from './confirmation_modal';
|
||||
import classes from './styles.module.css';
|
||||
|
||||
export const PRIVATE_QUOTE_MODAL_ID = 'quote/private_notify';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'confirmations.private_quote_notify.title',
|
||||
defaultMessage: 'Share with followers and mentioned users?',
|
||||
},
|
||||
message: {
|
||||
id: 'confirmations.private_quote_notify.message',
|
||||
defaultMessage:
|
||||
'The person you are quoting and other mentions ' +
|
||||
"will be notified and will be able to view your post, even if they're not following you.",
|
||||
},
|
||||
confirm: {
|
||||
id: 'confirmations.private_quote_notify.confirm',
|
||||
defaultMessage: 'Publish post',
|
||||
},
|
||||
cancel: {
|
||||
id: 'confirmations.private_quote_notify.cancel',
|
||||
defaultMessage: 'Back to editing',
|
||||
},
|
||||
});
|
||||
|
||||
export const PrivateQuoteNotify = forwardRef<
|
||||
HTMLDivElement,
|
||||
BaseConfirmationModalProps
|
||||
>(
|
||||
(
|
||||
{ onClose },
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_ref,
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [dismiss, setDismissed] = useState(false);
|
||||
const handleDismissToggle = useCallback(() => {
|
||||
setDismissed((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleConfirm = useCallback(() => {
|
||||
dispatch(submitCompose());
|
||||
if (dismiss) {
|
||||
dispatch(
|
||||
changeSetting(['dismissed_banners', PRIVATE_QUOTE_MODAL_ID], true),
|
||||
);
|
||||
}
|
||||
}, [dismiss, dispatch]);
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
title={intl.formatMessage(messages.title)}
|
||||
message={intl.formatMessage(messages.message)}
|
||||
confirm={intl.formatMessage(messages.confirm)}
|
||||
cancel={intl.formatMessage(messages.cancel)}
|
||||
onConfirm={handleConfirm}
|
||||
onClose={onClose}
|
||||
extraContent={
|
||||
<label className={classes.checkbox_wrapper}>
|
||||
<CheckBox
|
||||
value='hide'
|
||||
checked={dismiss}
|
||||
onChange={handleDismissToggle}
|
||||
/>{' '}
|
||||
<FormattedMessage
|
||||
id='confirmations.private_quote_notify.do_not_show_again'
|
||||
defaultMessage="Don't show me this message again"
|
||||
/>
|
||||
</label>
|
||||
}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
PrivateQuoteNotify.displayName = 'PrivateQuoteNotify';
|
||||
@@ -0,0 +1,7 @@
|
||||
.checkbox_wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 1rem 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import MediaModal from './media_modal';
|
||||
import { ModalPlaceholder } from './modal_placeholder';
|
||||
import VideoModal from './video_modal';
|
||||
import { VisibilityModal } from './visibility_modal';
|
||||
import { PrivateQuoteNotify } from './confirmation_modals/private_quote_notify';
|
||||
|
||||
export const MODAL_COMPONENTS = {
|
||||
'MEDIA': () => Promise.resolve({ default: MediaModal }),
|
||||
@@ -66,6 +67,7 @@ export const MODAL_COMPONENTS = {
|
||||
'CONFIRM_LOG_OUT': () => Promise.resolve({ default: ConfirmLogOutModal }),
|
||||
'CONFIRM_FOLLOW_TO_LIST': () => Promise.resolve({ default: ConfirmFollowToListModal }),
|
||||
'CONFIRM_MISSING_ALT_TEXT': () => Promise.resolve({ default: ConfirmMissingAltTextModal }),
|
||||
'CONFIRM_PRIVATE_QUOTE_NOTIFY': () => Promise.resolve({ default: PrivateQuoteNotify }),
|
||||
'CONFIRM_REVOKE_QUOTE': () => Promise.resolve({ default: ConfirmRevokeQuoteModal }),
|
||||
'CONFIRM_QUIET_QUOTE': () => Promise.resolve({ default: QuietPostQuoteInfoModal }),
|
||||
'MUTE': MuteModal,
|
||||
|
||||
@@ -128,9 +128,12 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
const disableVisibility = !!statusId;
|
||||
const disableQuotePolicy =
|
||||
visibility === 'private' || visibility === 'direct';
|
||||
const disablePublicVisibilities: boolean = useAppSelector(
|
||||
const disablePublicVisibilities = useAppSelector(
|
||||
selectDisablePublicVisibilities,
|
||||
);
|
||||
const isQuotePost = useAppSelector(
|
||||
(state) => state.compose.get('quoted_status_id') !== null,
|
||||
);
|
||||
|
||||
const visibilityItems = useMemo<SelectItem<StatusVisibility>[]>(() => {
|
||||
const items: SelectItem<StatusVisibility>[] = [
|
||||
@@ -315,6 +318,21 @@ export const VisibilityModal: FC<VisibilityModalProps> = forwardRef(
|
||||
id={quoteDescriptionId}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isQuotePost && visibility === 'direct' && (
|
||||
<div className='visibility-modal__quote-warning'>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.direct_quote_warning.title'
|
||||
defaultMessage="Quotes can't be embedded in private mentions"
|
||||
tagName='h3'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='visibility_modal.direct_quote_warning.text'
|
||||
defaultMessage='If you save the current settings, the embedded quote will be converted to a link.'
|
||||
tagName='p'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='dialog-modal__content__actions'>
|
||||
<Button onClick={onClose} secondary>
|
||||
|
||||
@@ -35,7 +35,7 @@ interface InitialStateMeta {
|
||||
streaming_api_base_url: string;
|
||||
local_live_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
remote_live_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
local_topic_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
local_topic_feed_access: 'public' | 'authenticated';
|
||||
remote_topic_feed_access: 'public' | 'authenticated' | 'disabled';
|
||||
title: string;
|
||||
show_trends: boolean;
|
||||
@@ -129,17 +129,21 @@ export const statusPageUrl = getMeta('status_page_url');
|
||||
export const sso_redirect = getMeta('sso_redirect');
|
||||
export const termsOfServiceEnabled = getMeta('terms_of_service_enabled');
|
||||
|
||||
const displayNames = new Intl.DisplayNames(getMeta('locale'), {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
languageDisplay: 'standard',
|
||||
});
|
||||
const displayNames =
|
||||
// Intl.DisplayNames can be undefined in old browsers
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
Intl.DisplayNames &&
|
||||
(new Intl.DisplayNames(getMeta('locale'), {
|
||||
type: 'language',
|
||||
fallback: 'none',
|
||||
languageDisplay: 'standard',
|
||||
}) as Intl.DisplayNames | undefined);
|
||||
|
||||
export const languages = initialState?.languages.map((lang) => {
|
||||
// zh-YUE is not a valid CLDR unicode_language_id
|
||||
return [
|
||||
lang[0],
|
||||
displayNames.of(lang[0].replace('zh-YUE', 'yue')) ?? lang[1],
|
||||
displayNames?.of(lang[0].replace('zh-YUE', 'yue')) ?? lang[1],
|
||||
lang[2],
|
||||
];
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"account.blocking": "Блакіраванне",
|
||||
"account.cancel_follow_request": "Скасаваць запыт на падпіску",
|
||||
"account.copy": "Скапіраваць спасылку на профіль",
|
||||
"account.direct": "Згадаць асабіста @{name}",
|
||||
"account.direct": "Згадаць прыватна @{name}",
|
||||
"account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}",
|
||||
"account.domain_blocking": "Блакіраванне дамена",
|
||||
"account.edit_profile": "Рэдагаваць профіль",
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Усё адно апублікаваць",
|
||||
"confirmations.missing_alt_text.title": "Дадаць альтэрнатыўны тэкст?",
|
||||
"confirmations.mute.confirm": "Ігнараваць",
|
||||
"confirmations.private_quote_notify.cancel": "Звяртацца да рэдагавання",
|
||||
"confirmations.private_quote_notify.confirm": "Апублікаваць допіс",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Больш не паказваць мне гэтае паведамленне",
|
||||
"confirmations.private_quote_notify.message": "Асоба, якую Вы цытуеце, і іншыя, хто быў узгаданы, атрымаюць апавяшчэнні і змогуць пабачыць Ваш допіс, нават калі яны не падпісаныя на Вас.",
|
||||
"confirmations.private_quote_notify.title": "Падзяліцца з падпісчыкамі і ўзгаданымі карыстальнікамі?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Не нагадваць зноў",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Зразумела",
|
||||
"confirmations.quiet_post_quote_info.message": "Калі будзеце цытаваць ціхі публічны допіс, Ваш допіс будзе схаваны ад трэндавых стужак.",
|
||||
@@ -759,6 +764,7 @@
|
||||
"privacy_policy.title": "Палітыка канфідэнцыйнасці",
|
||||
"quote_error.edit": "Нельга дадаваць цытаты пры рэдагаванні допісаў.",
|
||||
"quote_error.poll": "Нельга цытаваць з апытаннямі.",
|
||||
"quote_error.private_mentions": "Цытаванне не дазваляецца ў прамых узгадваннях.",
|
||||
"quote_error.quote": "За раз дазволена рабіць толькі адну цытату.",
|
||||
"quote_error.unauthorized": "Вы не ўвайшлі, каб цытаваць гэты допіс.",
|
||||
"quote_error.upload": "Нельга цытаваць з медыя далучэннямі.",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Паменшыць гучнасць",
|
||||
"video.volume_up": "Павялічыць гучнасць",
|
||||
"visibility_modal.button_title": "Вызначыць бачнасць",
|
||||
"visibility_modal.direct_quote_warning.text": "Калі Вы захавайце бягучыя налады, прымацаваная цытата будзе пераробленая ў спасылку.",
|
||||
"visibility_modal.direct_quote_warning.title": "Цытаты нельга далучаць да прыватных узгадванняў",
|
||||
"visibility_modal.header": "Бачнасць і ўзаемадзеянне",
|
||||
"visibility_modal.helper.direct_quoting": "Прыватныя згадванні, створаныя на Mastodon, нельга цытаваць іншым людзям.",
|
||||
"visibility_modal.helper.privacy_editing": "Бачнасць нельга змяніць у апублікаваным допісе.",
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
"community.column_settings.local_only": "Само локално",
|
||||
"community.column_settings.media_only": "Само мултимедия",
|
||||
"community.column_settings.remote_only": "Само отдалечено",
|
||||
"compose.error.blank_post": "Публикацията не може да е празна.",
|
||||
"compose.language.change": "Смяна на езика",
|
||||
"compose.language.search": "Търсене на езици...",
|
||||
"compose.published.body": "Публикувано.",
|
||||
@@ -242,6 +243,9 @@
|
||||
"confirmations.missing_alt_text.secondary": "Все пак да се публикува",
|
||||
"confirmations.missing_alt_text.title": "Добавяте ли алтернативен текст?",
|
||||
"confirmations.mute.confirm": "Заглушаване",
|
||||
"confirmations.private_quote_notify.cancel": "Назад към редактирането",
|
||||
"confirmations.private_quote_notify.confirm": "Издаване на публикация",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Без показване пак на това съобщение",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Без друго напомняне",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Схванах",
|
||||
"confirmations.quiet_post_quote_info.title": "Цитиране на публикации за тиха публика",
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
"column.edit_list": "Edita la llista",
|
||||
"column.favourites": "Favorits",
|
||||
"column.firehose": "Tuts en directe",
|
||||
"column.firehose_local": "Canal en directe per a aquest servidor",
|
||||
"column.firehose_singular": "Canal en directe",
|
||||
"column.follow_requests": "Peticions de seguir-te",
|
||||
"column.home": "Inici",
|
||||
"column.list_members": "Gestiona els membres de la llista",
|
||||
@@ -192,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Només local",
|
||||
"community.column_settings.media_only": "Només contingut",
|
||||
"community.column_settings.remote_only": "Només remot",
|
||||
"compose.error.blank_post": "La publicació no pot estar en blanc.",
|
||||
"compose.language.change": "Canvia d'idioma",
|
||||
"compose.language.search": "Cerca idiomes...",
|
||||
"compose.published.body": "Tut publicat.",
|
||||
@@ -244,8 +247,13 @@
|
||||
"confirmations.missing_alt_text.secondary": "Publica-la igualment",
|
||||
"confirmations.missing_alt_text.title": "Hi voleu afegir text alternatiu?",
|
||||
"confirmations.mute.confirm": "Silencia",
|
||||
"confirmations.private_quote_notify.cancel": "Torna a l'edició",
|
||||
"confirmations.private_quote_notify.message": "La persona que citeu i altres mencionades rebran una notificació i podran veure la vostra publicació, encara que no us segueixen.",
|
||||
"confirmations.private_quote_notify.title": "Voleu compartir amb seguidors i usuaris mencionats?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "No m'ho tornis a recordar",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Entesos",
|
||||
"confirmations.quiet_post_quote_info.message": "Quan citeu una publicació pública en mode silenciós, la vostra publicació s'amagarà de les línies de temps de tendències.",
|
||||
"confirmations.quiet_post_quote_info.title": "Citació d'una publicació pública en mode silenciós",
|
||||
"confirmations.redraft.confirm": "Esborra i reescriu",
|
||||
"confirmations.redraft.message": "Segur que vols eliminar aquest tut i tornar a escriure'l? Es perdran tots els impulsos i els favorits, i les respostes al tut original quedaran aïllades.",
|
||||
"confirmations.redraft.title": "Esborrar i reescriure la publicació?",
|
||||
@@ -331,6 +339,7 @@
|
||||
"empty_column.bookmarked_statuses": "Encara no has marcat cap tut. Quan en marquis un, apareixerà aquí.",
|
||||
"empty_column.community": "La línia de temps local és buida. Escriu alguna cosa públicament per posar-ho tot en marxa!",
|
||||
"empty_column.direct": "Encara no tens mencions privades. Quan n'enviïs o en rebis una, et sortirà aquí.",
|
||||
"empty_column.disabled_feed": "Aquest canal ha estat desactivat per l'administració del vostre servidor.",
|
||||
"empty_column.domain_blocks": "Encara no hi ha dominis blocats.",
|
||||
"empty_column.explore_statuses": "No hi ha res en tendència ara mateix. Revisa-ho més tard!",
|
||||
"empty_column.favourited_statuses": "Encara no has afavorit cap tut. Quan ho facis, apareixerà aquí.",
|
||||
@@ -458,6 +467,7 @@
|
||||
"ignore_notifications_modal.not_following_title": "Voleu ignorar les notificacions de qui no seguiu?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Voleu ignorar les notificacions de mencions privades no sol·licitades?",
|
||||
"info_button.label": "Ajuda",
|
||||
"info_button.what_is_alt_text": "<h1>Què és el text alternatiu?</h1> <p>El text alternatiu proporciona descripcions d'imatges per a persones amb discapacitat visual, connexions de poca amplada de banda o aquelles que busquen un context addicional.</p> <p>Podeu millorar l'accessibilitat i la comprensió per a tothom escrivint un text alternatiu clar, concís i objectiu.</p> <ul> <li>Descriviu els elements importants</li> <li>Utilitzeu frases senzilles</li> <li>Resumiu el text en imatges</li> <li>Eviteu la informació redundant</li> <li>Centreu-vos en les tendències i els aspectes clau dels elements visuals complexos (com ara diagrames o mapes)</li> </ul>",
|
||||
"interaction_modal.action": "Per a interactuar amb la publicació de {name} cal que inicieu la sessió en el servidor que feu servir.",
|
||||
"interaction_modal.go": "Endavant",
|
||||
"interaction_modal.no_account_yet": "Encara no teniu cap compte?",
|
||||
@@ -749,7 +759,9 @@
|
||||
"privacy.unlisted.short": "Públic silenciós",
|
||||
"privacy_policy.last_updated": "Darrera actualització {date}",
|
||||
"privacy_policy.title": "Política de Privacitat",
|
||||
"quote_error.edit": "No es poden afegir cites en editar una publicació.",
|
||||
"quote_error.poll": "Amb les enquestes no es permeten cites.",
|
||||
"quote_error.private_mentions": "Amb mencions directes no es permeten cites.",
|
||||
"quote_error.quote": "Només es permet una cita alhora.",
|
||||
"quote_error.unauthorized": "No se us permet de citar aquesta publicació.",
|
||||
"quote_error.upload": "Amb media adjunts no es permeten cites.",
|
||||
@@ -871,6 +883,7 @@
|
||||
"status.contains_quote": "Conté una cita",
|
||||
"status.context.loading": "Es carreguen més respostes",
|
||||
"status.context.loading_error": "No s'han pogut carregar respostes noves",
|
||||
"status.context.loading_success": "S'han carregat les noves respostes",
|
||||
"status.context.more_replies_found": "S'han trobat més respostes",
|
||||
"status.context.retry": "Torna-ho a provar",
|
||||
"status.context.show": "Mostra",
|
||||
@@ -902,9 +915,12 @@
|
||||
"status.pin": "Fixa en el perfil",
|
||||
"status.quote": "Cita",
|
||||
"status.quote.cancel": "Canceŀlar la citació",
|
||||
"status.quote_error.blocked_account_hint.title": "Aquesta publicació està amagada perquè heu blocat a @{name}.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Aquesta publicació està amagada perquè heu blocat a {domain}.",
|
||||
"status.quote_error.filtered": "No es mostra a causa d'un dels vostres filtres",
|
||||
"status.quote_error.limited_account_hint.action": "Mostra-la igualment",
|
||||
"status.quote_error.limited_account_hint.title": "Aquest perfil l'han amagat els moderadors de {domain}.",
|
||||
"status.quote_error.muted_account_hint.title": "Aquesta publicació està amagada perquè heu silenciat a @{name}.",
|
||||
"status.quote_error.not_available": "Publicació no disponible",
|
||||
"status.quote_error.pending_approval": "Publicació pendent",
|
||||
"status.quote_error.pending_approval_popout.body": "A Mastodon pots controlar si algú et pot citar. Aquesta publicació està pendent mentre esperem l'aprovació de l'autor original.",
|
||||
@@ -999,10 +1015,15 @@
|
||||
"video.volume_down": "Abaixa el volum",
|
||||
"video.volume_up": "Apuja el volum",
|
||||
"visibility_modal.button_title": "Establiu la visibilitat",
|
||||
"visibility_modal.direct_quote_warning.text": "Si deseu la configuració actual, la cita incrustada es convertirà en un enllaç.",
|
||||
"visibility_modal.direct_quote_warning.title": "Les cites no es poden incrustar a les mencions privades",
|
||||
"visibility_modal.header": "Visibilitat i interacció",
|
||||
"visibility_modal.helper.direct_quoting": "No es poden citar mencions privades fetes a Mastondon.",
|
||||
"visibility_modal.helper.privacy_editing": "La visibilitat no es pot canviar després de publicar una publicació.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Les autocites de publicacions privades no es poden fer públiques.",
|
||||
"visibility_modal.helper.private_quoting": "No es poden citar publicacions fetes a Mastodon només per a seguidors.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Quan la gent et citi les seves publicacions estaran amagades de les línies de temps de tendències.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Quan la gent us citi, les seves publicacions quedaran amagades de les línies de temps de tendències.",
|
||||
"visibility_modal.instructions": "Controleu qui pot interactuar amb aquesta publicació. També podeu aplicar la configuració a totes les publicacions futures navegant a <link>Preferències > Valors per defecte de publicació</link>.",
|
||||
"visibility_modal.privacy_label": "Visibilitat",
|
||||
"visibility_modal.quote_followers": "Només seguidors",
|
||||
"visibility_modal.quote_label": "Qui pot citar",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Pouze místní",
|
||||
"community.column_settings.media_only": "Pouze média",
|
||||
"community.column_settings.remote_only": "Pouze vzdálené",
|
||||
"compose.error.blank_post": "Příspěvek nemůže být prázdný.",
|
||||
"compose.language.change": "Změnit jazyk",
|
||||
"compose.language.search": "Prohledat jazyky...",
|
||||
"compose.published.body": "Příspěvek zveřejněn.",
|
||||
@@ -246,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Přesto odeslat",
|
||||
"confirmations.missing_alt_text.title": "Přidat popisek?",
|
||||
"confirmations.mute.confirm": "Skrýt",
|
||||
"confirmations.private_quote_notify.cancel": "Zpět k úpravám",
|
||||
"confirmations.private_quote_notify.confirm": "Publikovat příspěvek",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Nezobrazujte mi znovu tuto zprávu",
|
||||
"confirmations.private_quote_notify.message": "Osoba, kterou citujete, a další zmínění budou upozorněni a budou moci si zobrazit váš příspěvek, i pokud vás nesledují.",
|
||||
"confirmations.private_quote_notify.title": "Sdílet se sledujícími a zmíněnými uživateli?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Znovu nepřípomínat",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Rozumím",
|
||||
"confirmations.quiet_post_quote_info.message": "Při citování ztišeného veřejného příspěvku, váš příspěvek bude skrytý z os populárních příspěvků.",
|
||||
@@ -758,6 +764,7 @@
|
||||
"privacy_policy.title": "Zásady ochrany osobních údajů",
|
||||
"quote_error.edit": "Citáty nemohou být přidány při úpravě příspěvku.",
|
||||
"quote_error.poll": "Citování není u dotazníků povoleno.",
|
||||
"quote_error.private_mentions": "Citování není povoleno s přímými zmínkami.",
|
||||
"quote_error.quote": "Je povoleno citovat pouze jednou.",
|
||||
"quote_error.unauthorized": "Nemáte oprávnění citovat tento příspěvek.",
|
||||
"quote_error.upload": "Není povoleno citovat s přílohami.",
|
||||
@@ -1011,6 +1018,8 @@
|
||||
"video.volume_down": "Snížit hlasitost",
|
||||
"video.volume_up": "Zvýšit hlasitost",
|
||||
"visibility_modal.button_title": "Nastavit viditelnost",
|
||||
"visibility_modal.direct_quote_warning.text": "Pokud uložíte aktuální nastavení, vložená citace bude převedena na odkaz.",
|
||||
"visibility_modal.direct_quote_warning.title": "Citace nemohou být vloženy do soukromých zmínek",
|
||||
"visibility_modal.header": "Viditelnost a interakce",
|
||||
"visibility_modal.helper.direct_quoting": "Soukromé zmínky, které jsou vytvořeny na Mastodonu, nemohou být citovány ostatními.",
|
||||
"visibility_modal.helper.privacy_editing": "Viditelnost nelze změnit po publikování příspěvku.",
|
||||
|
||||
@@ -173,6 +173,8 @@
|
||||
"column.edit_list": "Golygu rhestr",
|
||||
"column.favourites": "Ffefrynnau",
|
||||
"column.firehose": "Ffrydiau byw",
|
||||
"column.firehose_local": "Ffrwd fyw ar gyfer y gweinydd hwn",
|
||||
"column.firehose_singular": "Ffrwd fyw",
|
||||
"column.follow_requests": "Ceisiadau dilyn",
|
||||
"column.home": "Cartref",
|
||||
"column.list_members": "Rheoli aelodau rhestr",
|
||||
@@ -192,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Lleol yn unig",
|
||||
"community.column_settings.media_only": "Cyfryngau yn unig",
|
||||
"community.column_settings.remote_only": "Pell yn unig",
|
||||
"compose.error.blank_post": "Gall postiad ddim bod yn wag.",
|
||||
"compose.language.change": "Newid iaith",
|
||||
"compose.language.search": "Chwilio ieithoedd...",
|
||||
"compose.published.body": "Postiad wedi ei gyhoeddi.",
|
||||
@@ -244,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Postio beth bynnag",
|
||||
"confirmations.missing_alt_text.title": "Ychwanegu testun amgen?",
|
||||
"confirmations.mute.confirm": "Tewi",
|
||||
"confirmations.private_quote_notify.cancel": "Nôl i olygu",
|
||||
"confirmations.private_quote_notify.confirm": "Cyhoeddi postiad",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Peidio dangos y neges hon i mi eto",
|
||||
"confirmations.private_quote_notify.message": "Bydd y person rydych chi'n ei ddyfynnu a chrybwylliadau eraill yn cael gwybod a bydd yn gallu gweld eich postiad, hyd yn oed os nad ydyn nhw'n eich dilyn chi.",
|
||||
"confirmations.private_quote_notify.title": "Rhannu gyda dilynwyr a defnyddwyr sy'n cael eu crybwyll?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Peidio fy atgoff eto",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Iawn",
|
||||
"confirmations.quiet_post_quote_info.message": "Wrth ddyfynnu postiad cyhoeddus tawel, bydd eich postiad yn cael ei guddio rhag llinellau amser sy'n trendio.",
|
||||
@@ -756,6 +764,7 @@
|
||||
"privacy_policy.title": "Polisi Preifatrwydd",
|
||||
"quote_error.edit": "Does dim modd ychwanegu dyfyniadau wrth olygu postiad.",
|
||||
"quote_error.poll": "Dyw dyfynnu ddim yn cael ei ganiatáu gyda pholau.",
|
||||
"quote_error.private_mentions": "Does dim caniatâd i ddyfynnu gyda chrybwylliadau uniongyrchol.",
|
||||
"quote_error.quote": "Dim ond un dyfyniad ar y tro sy'n cael ei ganiatáu.",
|
||||
"quote_error.unauthorized": "Does gennych chi ddim awdurdod i ddyfynnu'r postiad hwn.",
|
||||
"quote_error.upload": "Dyw dyfynnu ddim yn cael ei ganiatáu gydag atodiadau cyfryngau.",
|
||||
@@ -909,9 +918,12 @@
|
||||
"status.pin": "Pinio ar y proffil",
|
||||
"status.quote": "Dyfynnu",
|
||||
"status.quote.cancel": "Diddymu'r dyfyniad",
|
||||
"status.quote_error.blocked_account_hint.title": "Mae'r postiad hwn wedi'i guddio oherwydd eich bod wedi rhwystro @{name}.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Mae'r postiad hwn wedi'i guddio oherwydd eich bod wedi rhwystro {domain}.",
|
||||
"status.quote_error.filtered": "Wedi'i guddio oherwydd un o'ch hidlwyr",
|
||||
"status.quote_error.limited_account_hint.action": "Dangos beth bynnag",
|
||||
"status.quote_error.limited_account_hint.title": "Mae'r cyfrif hwn wedi'i guddio gan gymedrolwyr {domain}.",
|
||||
"status.quote_error.muted_account_hint.title": "Mae'r postiad hwn wedi'i guddio oherwydd eich bod wedi mudo @{name}.",
|
||||
"status.quote_error.not_available": "Postiad ddim ar gael",
|
||||
"status.quote_error.pending_approval": "Postiad yn yr arfaeth",
|
||||
"status.quote_error.pending_approval_popout.body": "Ar Mastodon, gallwch reoli os yw rhywun yn gallu eich dyfynnu. Mae'r postiad hwn yn cael ei ddal nôl tra'n bod yn cael cymeradwyaeth yr awdur gwreiddiol.",
|
||||
@@ -1006,6 +1018,8 @@
|
||||
"video.volume_down": "Lefel sain i lawr",
|
||||
"video.volume_up": "Lefel sain i fyny",
|
||||
"visibility_modal.button_title": "Gosod gwelededd",
|
||||
"visibility_modal.direct_quote_warning.text": "Os byddwch chi'n cadw'r gosodiadau cyfredol, bydd y dyfyniad sydd wedi'i fewnosod yn cael ei drawsnewid yn ddolen.",
|
||||
"visibility_modal.direct_quote_warning.title": "Does dim modd mewnblannu dyfyniadau mewn crybwylliadau preifat",
|
||||
"visibility_modal.header": "Gwelededd a rhyngweithio",
|
||||
"visibility_modal.helper.direct_quoting": "Does dim modd dyfynnu crybwylliadau preifat ysgrifennwyd ar Mastodon.",
|
||||
"visibility_modal.helper.privacy_editing": "Does dim modd newid gwelededd ar ôl i bostiad gael ei gyhoeddi.",
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
"compose_form.hashtag_warning": "Da indlægget ikke er offentligt, vises det ikke under noget hashtag, da kun offentlige indlæg er søgbare via hashtags.",
|
||||
"compose_form.lock_disclaimer": "Din konto er ikke {locked}. Enhver kan følge dig og se indlæg kun beregnet for følgere.",
|
||||
"compose_form.lock_disclaimer.lock": "låst",
|
||||
"compose_form.placeholder": "Hvad har du på hjertet?",
|
||||
"compose_form.placeholder": "Hvad har du på hjerte?",
|
||||
"compose_form.poll.duration": "Afstemningens varighed",
|
||||
"compose_form.poll.multiple": "Multivalg",
|
||||
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Læg op alligevel",
|
||||
"confirmations.missing_alt_text.title": "Tilføj alt-tekst?",
|
||||
"confirmations.mute.confirm": "Skjul",
|
||||
"confirmations.private_quote_notify.cancel": "Tilbage til redigering",
|
||||
"confirmations.private_quote_notify.confirm": "Offentliggør indlæg",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Vis ikke denne besked igen",
|
||||
"confirmations.private_quote_notify.message": "Den person, du citerer og andre omtalte vil blive underrettet, og vil være i stand til at se dit indlæg, selv om de ikke følger dig.",
|
||||
"confirmations.private_quote_notify.title": "Del med følgere og omtalte brugere?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Påmind mig ikke igen",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Forstået",
|
||||
"confirmations.quiet_post_quote_info.message": "Når du citerer et stille offentligt indlæg, vil dit indlæg blive skjult fra trendtidslinjer.",
|
||||
@@ -282,7 +287,7 @@
|
||||
"directory.recently_active": "Aktive for nyligt",
|
||||
"disabled_account_banner.account_settings": "Kontoindstillinger",
|
||||
"disabled_account_banner.text": "Din konto {disabledAccount} er pt. deaktiveret.",
|
||||
"dismissable_banner.community_timeline": "Disse er de seneste offentlige indlæg fra personer med konti hostet af {domain}.",
|
||||
"dismissable_banner.community_timeline": "Dette er de seneste offentlige indlæg fra personer med konti hostet af {domain}.",
|
||||
"dismissable_banner.dismiss": "Afvis",
|
||||
"dismissable_banner.public_timeline": "Dette er de seneste offentlige indlæg fra personer på fediverset, som folk på {domain} følger.",
|
||||
"domain_block_modal.block": "Blokér server",
|
||||
@@ -759,6 +764,7 @@
|
||||
"privacy_policy.title": "Privatlivspolitik",
|
||||
"quote_error.edit": "Citater kan ikke tilføjes ved redigering af et indlæg.",
|
||||
"quote_error.poll": "Citering ikke tilladt i afstemninger.",
|
||||
"quote_error.private_mentions": "Citering er ikke tilladt med direkte omtaler.",
|
||||
"quote_error.quote": "Kun ét citat ad gangen er tilladt.",
|
||||
"quote_error.unauthorized": "Du har ikke tilladelse til at citere dette indlæg.",
|
||||
"quote_error.upload": "Citering ikke tilladt ved medievedhæftninger.",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Lydstyrke ned",
|
||||
"video.volume_up": "Lydstyrke op",
|
||||
"visibility_modal.button_title": "Indstil synlighed",
|
||||
"visibility_modal.direct_quote_warning.text": "Hvis du gemmer de aktuelle indstillinger, vil det indlejrede citat blive konverteret til et link.",
|
||||
"visibility_modal.direct_quote_warning.title": "Citater kan ikke indlejres i private omtaler",
|
||||
"visibility_modal.header": "Synlighed og interaktion",
|
||||
"visibility_modal.helper.direct_quoting": "Private omtaler forfattet på Mastodon kan ikke citeres af andre.",
|
||||
"visibility_modal.helper.privacy_editing": "Synlighed kan ikke ændres, efter at et indlæg er offentliggjort.",
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"account.follow_back": "Ebenfalls folgen",
|
||||
"account.follow_back_short": "Ebenfalls folgen",
|
||||
"account.follow_request": "Anfrage zum Folgen",
|
||||
"account.follow_request_cancel": "Anfrage zurückziehen",
|
||||
"account.follow_request_cancel": "Anfrage abbrechen",
|
||||
"account.follow_request_cancel_short": "Abbrechen",
|
||||
"account.follow_request_short": "Anfragen",
|
||||
"account.followers": "Follower",
|
||||
@@ -247,9 +247,14 @@
|
||||
"confirmations.missing_alt_text.secondary": "Trotzdem veröffentlichen",
|
||||
"confirmations.missing_alt_text.title": "Bildbeschreibung hinzufügen?",
|
||||
"confirmations.mute.confirm": "Stummschalten",
|
||||
"confirmations.private_quote_notify.cancel": "Zurück zum Bearbeiten",
|
||||
"confirmations.private_quote_notify.confirm": "Beitrag veröffentlichen",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Diesen Hinweis nicht mehr anzeigen",
|
||||
"confirmations.private_quote_notify.message": "Dein Beitrag wird von dem zitierten sowie den erwähnten Profilen gesehen werden können, auch wenn sie dir nicht folgen.",
|
||||
"confirmations.private_quote_notify.title": "Mit Followern und erwähnten Profilen teilen?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Nicht mehr anzeigen",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Verstanden",
|
||||
"confirmations.quiet_post_quote_info.message": "Beim Zitieren eines Beitrags mit der Sichtbarkeit „Öffentlich (still)“ wird dein zitierter Beitrag ebenfalls nicht in den Trends und öffentlichen Timelines angezeigt.",
|
||||
"confirmations.quiet_post_quote_info.message": "Beim Zitieren eines Beitrags, dessen Sichtbarkeit „Öffentlich (still)“ ist, wird auch dein Beitrag, der das Zitat enthält, aus den Trends und öffentlichen Timelines ausgeblendet.",
|
||||
"confirmations.quiet_post_quote_info.title": "Zitieren eines Beitrags mit der Sichtbarkeit „Öffentlich (still)“",
|
||||
"confirmations.redraft.confirm": "Löschen und neu erstellen",
|
||||
"confirmations.redraft.message": "Möchtest du diesen Beitrag wirklich löschen und neu verfassen? Alle Favoriten sowie die bisher geteilten Beiträge werden verloren gehen und Antworten auf den ursprünglichen Beitrag verlieren den Zusammenhang.",
|
||||
@@ -257,15 +262,15 @@
|
||||
"confirmations.remove_from_followers.confirm": "Follower entfernen",
|
||||
"confirmations.remove_from_followers.message": "{name} wird dir nicht länger folgen. Bist du dir sicher?",
|
||||
"confirmations.remove_from_followers.title": "Follower entfernen?",
|
||||
"confirmations.revoke_quote.confirm": "Beitrag entfernen",
|
||||
"confirmations.revoke_quote.confirm": "Zitat entfernen",
|
||||
"confirmations.revoke_quote.message": "Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"confirmations.revoke_quote.title": "Beitrag entfernen?",
|
||||
"confirmations.revoke_quote.title": "Zitieren meines Beitrags entfernen?",
|
||||
"confirmations.unblock.confirm": "Nicht mehr blockieren",
|
||||
"confirmations.unblock.title": "{name} nicht mehr blockieren?",
|
||||
"confirmations.unfollow.confirm": "Entfolgen",
|
||||
"confirmations.unfollow.title": "{name} entfolgen?",
|
||||
"confirmations.withdraw_request.confirm": "Anfrage zurückziehen",
|
||||
"confirmations.withdraw_request.title": "Anfrage zum Folgen von {name} zurückziehen?",
|
||||
"confirmations.withdraw_request.title": "Anfrage zum Folgen von {name} widerrufen?",
|
||||
"content_warning.hide": "Beitrag ausblenden",
|
||||
"content_warning.show": "Trotzdem anzeigen",
|
||||
"content_warning.show_more": "Beitrag anzeigen",
|
||||
@@ -743,22 +748,23 @@
|
||||
"poll_button.add_poll": "Umfrage erstellen",
|
||||
"poll_button.remove_poll": "Umfrage entfernen",
|
||||
"privacy.change": "Sichtbarkeit anpassen",
|
||||
"privacy.direct.long": "Alle in diesem Beitrag erwähnten Profile",
|
||||
"privacy.direct.long": "Nur in diesem Beitrag erwähnte Profile",
|
||||
"privacy.direct.short": "Private Erwähnung",
|
||||
"privacy.private.long": "Nur deine Follower",
|
||||
"privacy.private.long": "Nur deine eigenen Follower",
|
||||
"privacy.private.short": "Follower",
|
||||
"privacy.public.long": "Alle in und außerhalb von Mastodon",
|
||||
"privacy.public.long": "Alle innerhalb und außerhalb von Mastodon",
|
||||
"privacy.public.short": "Öffentlich",
|
||||
"privacy.quote.anyone": "{visibility} – alle dürfen zitieren",
|
||||
"privacy.quote.disabled": "{visibility} – niemand darf zitieren",
|
||||
"privacy.quote.limited": "{visibility} – eingeschränktes Zitieren",
|
||||
"privacy.unlisted.additional": "Das Verhalten ist wie bei „Öffentlich“, jedoch gibt es einige Einschränkungen. Der Beitrag wird nicht in „Live-Feeds“, „Erkunden“, Hashtags oder über die Mastodon-Suchfunktion auffindbar sein – selbst wenn die zugehörige Einstellung aktiviert wurde.",
|
||||
"privacy.unlisted.long": "Wird nicht in den Suchergebnissen, Trends oder öffentlichen Timelines von Mastodon angezeigt",
|
||||
"privacy.unlisted.long": "Verborgen vor Suchergebnissen, Trends und öffentlichen Timelines auf Mastodon",
|
||||
"privacy.unlisted.short": "Öffentlich (still)",
|
||||
"privacy_policy.last_updated": "Stand: {date}",
|
||||
"privacy_policy.title": "Datenschutzerklärung",
|
||||
"quote_error.edit": "Beim Bearbeiten eines Beitrags können keine Zitate hinzugefügt werden.",
|
||||
"quote_error.poll": "Zitieren ist bei Umfragen nicht gestattet.",
|
||||
"quote_error.private_mentions": "Das Zitieren ist bei privaten Erwähnungen nicht erlaubt.",
|
||||
"quote_error.quote": "Es ist jeweils nur ein Zitat zulässig.",
|
||||
"quote_error.unauthorized": "Du bist nicht berechtigt, diesen Beitrag zu zitieren.",
|
||||
"quote_error.upload": "Zitieren ist mit Medien-Anhängen nicht möglich.",
|
||||
@@ -875,7 +881,7 @@
|
||||
"status.block": "@{name} blockieren",
|
||||
"status.bookmark": "Lesezeichen setzen",
|
||||
"status.cancel_reblog_private": "Beitrag nicht mehr teilen",
|
||||
"status.cannot_quote": "Dir ist es nicht gestattet, diesen Beitrag zu zitieren",
|
||||
"status.cannot_quote": "Beitrag kann nicht zitiert werden",
|
||||
"status.cannot_reblog": "Dieser Beitrag kann nicht geteilt werden",
|
||||
"status.contains_quote": "Enthält Zitat",
|
||||
"status.context.loading": "Weitere Antworten laden",
|
||||
@@ -896,7 +902,7 @@
|
||||
"status.edited_x_times": "{count, plural, one {{count}-mal} other {{count}-mal}} bearbeitet",
|
||||
"status.embed": "Code zum Einbetten",
|
||||
"status.favourite": "Favorisieren",
|
||||
"status.favourites": "{count, plural, one {Mal favorisiert} other {Mal favorisiert}}",
|
||||
"status.favourites": "{count, plural, one {× favorisiert} other {× favorisiert}}",
|
||||
"status.filter": "Beitrag filtern",
|
||||
"status.history.created": "{name} erstellte {date}",
|
||||
"status.history.edited": "{name} bearbeitete {date}",
|
||||
@@ -911,7 +917,7 @@
|
||||
"status.open": "Beitrag öffnen",
|
||||
"status.pin": "Im Profil anheften",
|
||||
"status.quote": "Zitieren",
|
||||
"status.quote.cancel": "Zitat abbrechen",
|
||||
"status.quote.cancel": "Zitat entfernen",
|
||||
"status.quote_error.blocked_account_hint.title": "Dieser Beitrag wurde ausgeblendet, weil du @{name} blockiert hast.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Dieser Beitrag wurde ausgeblendet, weil du {domain} blockiert hast.",
|
||||
"status.quote_error.filtered": "Ausgeblendet wegen eines deiner Filter",
|
||||
@@ -926,9 +932,9 @@
|
||||
"status.quote_manual_review": "Zitierte*r überprüft manuell",
|
||||
"status.quote_noun": "Zitat",
|
||||
"status.quote_policy_change": "Ändern, wer zitieren darf",
|
||||
"status.quote_post_author": "Zitierte einen Beitrag von @{name}",
|
||||
"status.quote_post_author": "Zitierter Beitrag von @{name}",
|
||||
"status.quote_private": "Private Beiträge können nicht zitiert werden",
|
||||
"status.quotes": "{count, plural, one {Mal zitiert} other {Mal zitiert}}",
|
||||
"status.quotes": "{count, plural, one {× zitiert} other {× zitiert}}",
|
||||
"status.quotes.empty": "Diesen Beitrag hat bisher noch niemand zitiert. Sobald es jemand tut, wird das Profil hier erscheinen.",
|
||||
"status.quotes.local_other_disclaimer": "Durch Autor*in abgelehnte Zitate werden nicht angezeigt.",
|
||||
"status.quotes.remote_other_disclaimer": "Nur Zitate von {domain} werden hier garantiert angezeigt. Durch Autor*in abgelehnte Zitate werden nicht angezeigt.",
|
||||
@@ -937,7 +943,7 @@
|
||||
"status.reblog_or_quote": "Teilen oder zitieren",
|
||||
"status.reblog_private": "Erneut mit deinen Followern teilen",
|
||||
"status.reblogged_by": "{name} teilte",
|
||||
"status.reblogs": "{count, plural, one {Mal geteilt} other {Mal geteilt}}",
|
||||
"status.reblogs": "{count, plural, one {× geteilt} other {× geteilt}}",
|
||||
"status.reblogs.empty": "Diesen Beitrag hat bisher noch niemand geteilt. Sobald es jemand tut, wird das Profil hier erscheinen.",
|
||||
"status.redraft": "Löschen und neu erstellen",
|
||||
"status.remove_bookmark": "Lesezeichen entfernen",
|
||||
@@ -949,7 +955,7 @@
|
||||
"status.replyAll": "Allen antworten",
|
||||
"status.report": "@{name} melden",
|
||||
"status.request_quote": "Anfrage zum Zitieren",
|
||||
"status.revoke_quote": "Meinen zitierten Beitrag aus dem Beitrag von @{name} entfernen",
|
||||
"status.revoke_quote": "Zitat bei @{name} entfernen",
|
||||
"status.sensitive_warning": "Inhaltswarnung",
|
||||
"status.share": "Teilen",
|
||||
"status.show_less_all": "Alles einklappen",
|
||||
@@ -1012,7 +1018,9 @@
|
||||
"video.volume_down": "Leiser",
|
||||
"video.volume_up": "Lauter",
|
||||
"visibility_modal.button_title": "Sichtbarkeit festlegen",
|
||||
"visibility_modal.header": "Sichtbarkeit und Interaktion",
|
||||
"visibility_modal.direct_quote_warning.text": "Wenn diese Einstellungen gespeichert werden, wird das eingebettete Zitat in einen Link umgewandelt.",
|
||||
"visibility_modal.direct_quote_warning.title": "Zitate können in privaten Erwähnungen nicht eingebettet werden",
|
||||
"visibility_modal.header": "Sichtbarkeit und Zitate",
|
||||
"visibility_modal.helper.direct_quoting": "Private Erwähnungen, die auf Mastodon verfasst wurden, können nicht von anderen zitiert werden.",
|
||||
"visibility_modal.helper.privacy_editing": "Die Sichtbarkeit eines bereits veröffentlichten Beitrags kann nachträglich nicht mehr geändert werden.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Beiträge mit privaten Erwähnungen können öffentlich nicht zitiert werden.",
|
||||
@@ -1021,7 +1029,7 @@
|
||||
"visibility_modal.instructions": "Lege fest, wer mit diesem Beitrag interagieren darf. Du hast auch die Möglichkeit, diese Einstellung auf alle zukünftigen Beiträge anzuwenden. Gehe zu: <link>Einstellungen > Standardeinstellungen für Beiträge</link>",
|
||||
"visibility_modal.privacy_label": "Sichtbarkeit",
|
||||
"visibility_modal.quote_followers": "Nur Follower",
|
||||
"visibility_modal.quote_label": "Wer zitieren darf",
|
||||
"visibility_modal.quote_label": "Wer darf mich zitieren?",
|
||||
"visibility_modal.quote_nobody": "Nur ich",
|
||||
"visibility_modal.quote_public": "Alle",
|
||||
"visibility_modal.save": "Speichern"
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Τοπικά μόνο",
|
||||
"community.column_settings.media_only": "Μόνο πολυμέσα",
|
||||
"community.column_settings.remote_only": "Απομακρυσμένα μόνο",
|
||||
"compose.error.blank_post": "Η ανάρτηση δεν μπορεί να είναι κενή.",
|
||||
"compose.language.change": "Αλλαγή γλώσσας",
|
||||
"compose.language.search": "Αναζήτηση γλωσσών...",
|
||||
"compose.published.body": "Η ανάρτηση δημοσιεύτηκε.",
|
||||
@@ -246,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Δημοσίευση όπως και να ΄χει",
|
||||
"confirmations.missing_alt_text.title": "Προσθήκη εναλλακτικού κειμένου;",
|
||||
"confirmations.mute.confirm": "Αποσιώπηση",
|
||||
"confirmations.private_quote_notify.cancel": "Πίσω στην επεξεργασία",
|
||||
"confirmations.private_quote_notify.confirm": "Δημοσίευση ανάρτησης",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Να μην εμφανιστεί ξανά αυτό το μήνυμα",
|
||||
"confirmations.private_quote_notify.message": "Το άτομο που παραθέτετε και άλλες επισημάνσεις θα ειδοποιηθούν και θα μπορούν να δουν την ανάρτησή σας, ακόμη και αν δεν σας ακολουθούν.",
|
||||
"confirmations.private_quote_notify.title": "Κοινοποίηση με τους ακολούθους και τους επισημασμένους χρήστες;",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Μη μου το ξαναθυμίσεις",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Το κατάλαβα",
|
||||
"confirmations.quiet_post_quote_info.message": "Όταν παραθέτετε μια ήσυχη δημόσια ανάρτηση, η ανάρτηση σας θα είναι κρυμμένη από τις δημοφιλείς ροές.",
|
||||
@@ -758,6 +764,7 @@
|
||||
"privacy_policy.title": "Πολιτική Απορρήτου",
|
||||
"quote_error.edit": "Δεν μπορούν να προστεθούν παραθέσεις κατά την επεξεργασία μιας ανάρτησης.",
|
||||
"quote_error.poll": "Η παράθεση δεν επιτρέπεται με δημοσκοπήσεις.",
|
||||
"quote_error.private_mentions": "Η παράθεση δεν επιτρέπεται με άμεσες επισημάνσεις.",
|
||||
"quote_error.quote": "Επιτρέπεται μόνο μία παράθεση τη φορά.",
|
||||
"quote_error.unauthorized": "Δεν είστε εξουσιοδοτημένοι να παραθέσετε αυτή την ανάρτηση.",
|
||||
"quote_error.upload": "Η παράθεση δεν επιτρέπεται με συνημμένα πολυμέσων.",
|
||||
@@ -1011,6 +1018,8 @@
|
||||
"video.volume_down": "Μείωση έντασης",
|
||||
"video.volume_up": "Αύξηση έντασης",
|
||||
"visibility_modal.button_title": "Ορισμός ορατότητας",
|
||||
"visibility_modal.direct_quote_warning.text": "Εάν αποθηκεύσετε τις τρέχουσες ρυθμίσεις, η ενσωματωμένη παράθεση θα μετατραπεί σε σύνδεσμο.",
|
||||
"visibility_modal.direct_quote_warning.title": "Οι παραθέσεις δεν μπορούν να ενσωματωθούν σε ιδιωτικές επισημάνσεις",
|
||||
"visibility_modal.header": "Ορατότητα και αλληλεπίδραση",
|
||||
"visibility_modal.helper.direct_quoting": "Ιδιωτικές αναφορές που έχουν συνταχθεί στο Mastodon δεν μπορούν να γίνουν παράθεση από άλλους.",
|
||||
"visibility_modal.helper.privacy_editing": "Η ορατότητα δεν μπορεί να αλλάξει μετά τη δημοσίευση μιας ανάρτησης.",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"account.disable_notifications": "Stop notifying me when @{name} posts",
|
||||
"account.domain_blocking": "Blocking domain",
|
||||
"account.edit_profile": "Edit profile",
|
||||
"account.edit_profile_short": "Edit",
|
||||
"account.enable_notifications": "Notify me when @{name} posts",
|
||||
"account.endorse": "Feature on profile",
|
||||
"account.familiar_followers_many": "Followed by {name1}, {name2}, and {othersCount, plural, one {one other you know} other {# others you know}}",
|
||||
@@ -40,6 +41,11 @@
|
||||
"account.featured_tags.last_status_never": "No posts",
|
||||
"account.follow": "Follow",
|
||||
"account.follow_back": "Follow back",
|
||||
"account.follow_back_short": "Follow back",
|
||||
"account.follow_request": "Request to follow",
|
||||
"account.follow_request_cancel": "Cancel request",
|
||||
"account.follow_request_cancel_short": "Cancel",
|
||||
"account.follow_request_short": "Request",
|
||||
"account.followers": "Followers",
|
||||
"account.followers.empty": "No one follows this user yet.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}",
|
||||
@@ -167,6 +173,8 @@
|
||||
"column.edit_list": "Edit list",
|
||||
"column.favourites": "Favourites",
|
||||
"column.firehose": "Live feeds",
|
||||
"column.firehose_local": "Live feed for this server",
|
||||
"column.firehose_singular": "Live feed",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.home": "Home",
|
||||
"column.list_members": "Manage list members",
|
||||
@@ -186,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Local only",
|
||||
"community.column_settings.media_only": "Media Only",
|
||||
"community.column_settings.remote_only": "Remote only",
|
||||
"compose.error.blank_post": "Post can't be blank.",
|
||||
"compose.language.change": "Change language",
|
||||
"compose.language.search": "Search languages...",
|
||||
"compose.published.body": "Post published.",
|
||||
@@ -238,13 +247,30 @@
|
||||
"confirmations.missing_alt_text.secondary": "Post anyway",
|
||||
"confirmations.missing_alt_text.title": "Add alt text?",
|
||||
"confirmations.mute.confirm": "Mute",
|
||||
"confirmations.private_quote_notify.cancel": "Back to editing",
|
||||
"confirmations.private_quote_notify.confirm": "Publish post",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Don't show me this message again",
|
||||
"confirmations.private_quote_notify.message": "The person you are quoting and other mentions will be notified and will be able to view your post, even if they're not following you.",
|
||||
"confirmations.private_quote_notify.title": "Share with followers and mentioned users?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Don't remind me again",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Got it",
|
||||
"confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.",
|
||||
"confirmations.quiet_post_quote_info.title": "Quoting quiet public posts",
|
||||
"confirmations.redraft.confirm": "Delete & redraft",
|
||||
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
|
||||
"confirmations.redraft.title": "Delete & redraft post?",
|
||||
"confirmations.remove_from_followers.confirm": "Remove follower",
|
||||
"confirmations.remove_from_followers.message": "{name} will stop following you. Are you sure you want to proceed?",
|
||||
"confirmations.remove_from_followers.title": "Remove follower?",
|
||||
"confirmations.revoke_quote.confirm": "Remove post",
|
||||
"confirmations.revoke_quote.message": "This action cannot be undone.",
|
||||
"confirmations.revoke_quote.title": "Remove post?",
|
||||
"confirmations.unblock.confirm": "Unblock",
|
||||
"confirmations.unblock.title": "Unblock {name}?",
|
||||
"confirmations.unfollow.confirm": "Unfollow",
|
||||
"confirmations.unfollow.title": "Unfollow {name}?",
|
||||
"confirmations.withdraw_request.confirm": "Withdraw request",
|
||||
"confirmations.withdraw_request.title": "Withdraw request to follow {name}?",
|
||||
"content_warning.hide": "Hide post",
|
||||
"content_warning.show": "Show anyway",
|
||||
"content_warning.show_more": "Show more",
|
||||
@@ -286,6 +312,7 @@
|
||||
"domain_pill.your_handle": "Your handle:",
|
||||
"domain_pill.your_server": "Your digital home, where all of your posts live. Don’t like this one? Transfer servers at any time and bring your followers, too.",
|
||||
"domain_pill.your_username": "Your unique identifier on this server. It’s possible to find users with the same username on different servers.",
|
||||
"dropdown.empty": "Select an option",
|
||||
"embed.instructions": "Embed this post on your website by copying the code below.",
|
||||
"embed.preview": "Here is what it will look like:",
|
||||
"emoji_button.activity": "Activity",
|
||||
@@ -314,6 +341,7 @@
|
||||
"empty_column.bookmarked_statuses": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
|
||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
||||
"empty_column.direct": "You don't have any private mentions yet. When you send or receive one, it will show up here.",
|
||||
"empty_column.disabled_feed": "This feed has been disabled by your server administrators.",
|
||||
"empty_column.domain_blocks": "There are no blocked domains yet.",
|
||||
"empty_column.explore_statuses": "Nothing is trending right now. Check back later!",
|
||||
"empty_column.favourited_statuses": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
|
||||
@@ -442,10 +470,12 @@
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignore notifications from unsolicited Private Mentions?",
|
||||
"info_button.label": "Help",
|
||||
"info_button.what_is_alt_text": "<h1>What is alt text?</h1> <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p> <p>You can improve accessibility and understanding for everyone by writing clear, concise, and objective alt text.</p> <ul> <li>Capture important elements</li> <li>Summarise text in images</li> <li>Use regular sentence structure</li> <li>Avoid redundant information</li> <li>Focus on trends and key findings in complex visuals (like diagrams or maps)</li> </ul>",
|
||||
"interaction_modal.action": "To interact with {name}'s post, you need to sign into your account on whatever Mastodon server you use.",
|
||||
"interaction_modal.go": "Go",
|
||||
"interaction_modal.no_account_yet": "Don't have an account yet?",
|
||||
"interaction_modal.on_another_server": "On a different server",
|
||||
"interaction_modal.on_this_server": "On this server",
|
||||
"interaction_modal.title": "Sign in to continue",
|
||||
"interaction_modal.username_prompt": "E.g. {example}",
|
||||
"intervals.full.days": "{number, plural, one {# day} other {# days}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}",
|
||||
@@ -466,6 +496,7 @@
|
||||
"keyboard_shortcuts.home": "Open home timeline",
|
||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||
"keyboard_shortcuts.legend": "to display this legend",
|
||||
"keyboard_shortcuts.load_more": "Focus \"Load more\" button",
|
||||
"keyboard_shortcuts.local": "to open local timeline",
|
||||
"keyboard_shortcuts.mention": "to mention author",
|
||||
"keyboard_shortcuts.muted": "to open muted users list",
|
||||
@@ -474,6 +505,7 @@
|
||||
"keyboard_shortcuts.open_media": "to open media",
|
||||
"keyboard_shortcuts.pinned": "to open pinned posts list",
|
||||
"keyboard_shortcuts.profile": "to open author's profile",
|
||||
"keyboard_shortcuts.quote": "Quote post",
|
||||
"keyboard_shortcuts.reply": "to reply",
|
||||
"keyboard_shortcuts.requests": "to open follow requests list",
|
||||
"keyboard_shortcuts.search": "to focus search",
|
||||
@@ -485,6 +517,8 @@
|
||||
"keyboard_shortcuts.translate": "to translate a post",
|
||||
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
|
||||
"keyboard_shortcuts.up": "Move up in the list",
|
||||
"learn_more_link.got_it": "Got it",
|
||||
"learn_more_link.learn_more": "Learn more",
|
||||
"lightbox.close": "Close",
|
||||
"lightbox.next": "Next",
|
||||
"lightbox.previous": "Previous",
|
||||
@@ -585,6 +619,7 @@
|
||||
"notification.label.mention": "Mention",
|
||||
"notification.label.private_mention": "Private mention",
|
||||
"notification.label.private_reply": "Private reply",
|
||||
"notification.label.quote": "{name} quoted your post",
|
||||
"notification.label.reply": "Reply",
|
||||
"notification.mention": "Mention",
|
||||
"notification.mentioned_you": "{name} mentioned you",
|
||||
@@ -599,6 +634,7 @@
|
||||
"notification.moderation_warning.action_suspend": "Your account has been suspended.",
|
||||
"notification.own_poll": "Your poll has ended",
|
||||
"notification.poll": "A poll you voted in has ended",
|
||||
"notification.quoted_update": "{name} edited a post you have quoted",
|
||||
"notification.reblog": "{name} boosted your post",
|
||||
"notification.reblog.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> boosted your post",
|
||||
"notification.relationships_severance_event": "Lost connections with {name}",
|
||||
@@ -642,6 +678,7 @@
|
||||
"notifications.column_settings.mention": "Mentions:",
|
||||
"notifications.column_settings.poll": "Poll results:",
|
||||
"notifications.column_settings.push": "Push notifications",
|
||||
"notifications.column_settings.quote": "Quotes:",
|
||||
"notifications.column_settings.reblog": "Boosts:",
|
||||
"notifications.column_settings.show": "Show in column",
|
||||
"notifications.column_settings.sound": "Play sound",
|
||||
@@ -717,10 +754,18 @@
|
||||
"privacy.private.short": "Followers",
|
||||
"privacy.public.long": "Anyone on and off Mastodon",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.quote.anyone": "{visibility}, anyone can quote",
|
||||
"privacy.quote.disabled": "{visibility}, quotes disabled",
|
||||
"privacy.quote.limited": "{visibility}, quotes limited",
|
||||
"privacy.unlisted.additional": "This behaves exactly like public, except the post will not appear in live feeds or hashtags, explore, or Mastodon search, even if you are opted-in account-wide.",
|
||||
"privacy.unlisted.long": "Hidden from Mastodon search results, trending, and public timelines",
|
||||
"privacy.unlisted.short": "Quiet public",
|
||||
"privacy_policy.last_updated": "Last updated {date}",
|
||||
"privacy_policy.title": "Privacy Policy",
|
||||
"quote_error.edit": "Quotes cannot be added when editing a post.",
|
||||
"quote_error.poll": "Quoting is not allowed with polls.",
|
||||
"quote_error.private_mentions": "Quoting is not allowed with direct mentions.",
|
||||
"quote_error.quote": "Only one quote at a time is allowed.",
|
||||
"recommended": "Recommended",
|
||||
"refresh": "Refresh",
|
||||
"regeneration_indicator.please_stand_by": "Please stand by.",
|
||||
@@ -929,5 +974,13 @@
|
||||
"video.skip_forward": "Skip forward",
|
||||
"video.unmute": "Unmute",
|
||||
"video.volume_down": "Volume down",
|
||||
"video.volume_up": "Volume up"
|
||||
"video.volume_up": "Volume up",
|
||||
"visibility_modal.helper.unlisted_quoting": "When people quote you, their post will also be hidden from trending timelines.",
|
||||
"visibility_modal.instructions": "Control who can interact with this post. You can also apply settings to all future posts by navigating to <link>Preferences > Posting defaults</link>.",
|
||||
"visibility_modal.privacy_label": "Visibility",
|
||||
"visibility_modal.quote_followers": "Followers only",
|
||||
"visibility_modal.quote_label": "Who can quote",
|
||||
"visibility_modal.quote_nobody": "Just me",
|
||||
"visibility_modal.quote_public": "Anyone",
|
||||
"visibility_modal.save": "Save"
|
||||
}
|
||||
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Post anyway",
|
||||
"confirmations.missing_alt_text.title": "Add alt text?",
|
||||
"confirmations.mute.confirm": "Mute",
|
||||
"confirmations.private_quote_notify.cancel": "Back to editing",
|
||||
"confirmations.private_quote_notify.confirm": "Publish post",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Don't show me this message again",
|
||||
"confirmations.private_quote_notify.message": "The person you are quoting and other mentions will be notified and will be able to view your post, even if they're not following you.",
|
||||
"confirmations.private_quote_notify.title": "Share with followers and mentioned users?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Don't remind me again",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Got it",
|
||||
"confirmations.quiet_post_quote_info.message": "When quoting a quiet public post, your post will be hidden from trending timelines.",
|
||||
@@ -759,6 +764,7 @@
|
||||
"privacy_policy.title": "Privacy Policy",
|
||||
"quote_error.edit": "Quotes cannot be added when editing a post.",
|
||||
"quote_error.poll": "Quoting is not allowed with polls.",
|
||||
"quote_error.private_mentions": "Quoting is not allowed with direct mentions.",
|
||||
"quote_error.quote": "Only one quote at a time is allowed.",
|
||||
"quote_error.unauthorized": "You are not authorized to quote this post.",
|
||||
"quote_error.upload": "Quoting is not allowed with media attachments.",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Volume down",
|
||||
"video.volume_up": "Volume up",
|
||||
"visibility_modal.button_title": "Set visibility",
|
||||
"visibility_modal.direct_quote_warning.text": "If you save the current settings, the embedded quote will be converted to a link.",
|
||||
"visibility_modal.direct_quote_warning.title": "Quotes can't be embedded in private mentions",
|
||||
"visibility_modal.header": "Visibility and interaction",
|
||||
"visibility_modal.helper.direct_quoting": "Private mentions authored on Mastodon can't be quoted by others.",
|
||||
"visibility_modal.helper.privacy_editing": "Visibility can't be changed after a post is published.",
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
"account.featured_tags.last_status_never": "Neniu afiŝo",
|
||||
"account.follow": "Sekvi",
|
||||
"account.follow_back": "Sekvu reen",
|
||||
"account.follow_back_short": "Sekvu reen",
|
||||
"account.followers": "Sekvantoj",
|
||||
"account.followers.empty": "Ankoraŭ neniu sekvas ĉi tiun uzanton.",
|
||||
"account.followers_counter": "{count, plural, one{{counter} sekvanto} other {{counter} sekvantoj}}",
|
||||
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Enviar de todos modos",
|
||||
"confirmations.missing_alt_text.title": "¿Agregar texto alternativo?",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.private_quote_notify.cancel": "Volver a editar",
|
||||
"confirmations.private_quote_notify.confirm": "Enviar mensaje",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "No mostrarme este mensaje de nuevo",
|
||||
"confirmations.private_quote_notify.message": "La cuenta a la que estás citando y otras menciones serán notificadas y podrán ver tu mensaje, incluso si no te están siguiendo.",
|
||||
"confirmations.private_quote_notify.title": "¿Compartir con seguidores y usuarios mencionados?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "No recordar de nuevo",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Entendido",
|
||||
"confirmations.quiet_post_quote_info.message": "Al citar un mensaje público pero silencioso, tu mensaje se ocultará de las líneas temporales de tendencias.",
|
||||
@@ -736,10 +741,10 @@
|
||||
"poll.refresh": "Refrescar",
|
||||
"poll.reveal": "Ver resultados",
|
||||
"poll.total_people": "{count, plural, one {# persona} other {# personas}}",
|
||||
"poll.total_votes": "{count, plural, one {# voto} other {# votos}}",
|
||||
"poll.total_votes": "{count, plural, one {voto} other {votos}}",
|
||||
"poll.vote": "Votar",
|
||||
"poll.voted": "Votaste esta opción",
|
||||
"poll.votes": "{votes, plural, one {# voto} other {# votos}}",
|
||||
"poll.votes": "{votes, plural, one {voto} other {votos}}",
|
||||
"poll_button.add_poll": "Agregar encuesta",
|
||||
"poll_button.remove_poll": "Quitar encuesta",
|
||||
"privacy.change": "Configurar privacidad del mensaje",
|
||||
@@ -754,11 +759,12 @@
|
||||
"privacy.quote.limited": "{visibility}, citas limitadas",
|
||||
"privacy.unlisted.additional": "Esto se comporta exactamente igual que con la configuración de privacidad de mensaje «Público», excepto que el mensaje no aparecerá en las líneas temporales en vivo, ni en las etiquetas, ni en la línea temporal «Explorá», ni en la búsqueda de Mastodon; incluso si optaste por hacer tu cuenta visible.",
|
||||
"privacy.unlisted.long": "Oculto de los resultados de búsqueda, tendencias y líneas temporales públicas de Mastodon",
|
||||
"privacy.unlisted.short": "Público silencioso",
|
||||
"privacy.unlisted.short": "Público pero silencioso",
|
||||
"privacy_policy.last_updated": "Última actualización: {date}",
|
||||
"privacy_policy.title": "Política de privacidad",
|
||||
"quote_error.edit": "Las citas no se pueden agregar al editar un mensaje.",
|
||||
"quote_error.poll": "No se permite citar encuestas.",
|
||||
"quote_error.private_mentions": "No se permite citar con menciones directas.",
|
||||
"quote_error.quote": "Solo se permite una cita a la vez.",
|
||||
"quote_error.unauthorized": "No tenés autorización para citar este mensaje.",
|
||||
"quote_error.upload": "No se permite citar con archivos multimedia.",
|
||||
@@ -928,7 +934,7 @@
|
||||
"status.quote_policy_change": "Cambiá quién puede citar",
|
||||
"status.quote_post_author": "Se citó un mensaje de @{name}",
|
||||
"status.quote_private": "No se pueden citar los mensajes privados",
|
||||
"status.quotes": "{count, plural, one {# voto} other {# votos}}",
|
||||
"status.quotes": "{count, plural, one {voto} other {votos}}",
|
||||
"status.quotes.empty": "Todavía nadie citó este mensaje. Cuando alguien lo haga, se mostrará acá.",
|
||||
"status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no serán mostradas.",
|
||||
"status.quotes.remote_other_disclaimer": "Solo las citas de {domain} están garantizadas de ser mostradas acá. Las citas rechazadas por el autor no serán mostradas.",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Bajar volumen",
|
||||
"video.volume_up": "Subir volumen",
|
||||
"visibility_modal.button_title": "Establecer visibilidad",
|
||||
"visibility_modal.direct_quote_warning.text": "Si guardás la configuración actual, la cita insertada se convertirá en un enlace.",
|
||||
"visibility_modal.direct_quote_warning.title": "Las citas no pueden ser insertadas en menciones privadas",
|
||||
"visibility_modal.header": "Visibilidad e interacción",
|
||||
"visibility_modal.helper.direct_quoting": "Las menciones privadas redactadas en Mastodon no pueden ser citadas por otras cuentas.",
|
||||
"visibility_modal.helper.privacy_editing": "La visibilidad no se puede cambiar después de que se haya enviado un mensaje.",
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
"account.follow": "Seguir",
|
||||
"account.follow_back": "Seguir también",
|
||||
"account.follow_back_short": "Seguir también",
|
||||
"account.follow_request": "Solicitud de seguimiento",
|
||||
"account.follow_request": "Solicitar seguimiento",
|
||||
"account.follow_request_cancel": "Cancelar solicitud",
|
||||
"account.follow_request_cancel_short": "Cancelar",
|
||||
"account.follow_request_short": "Solicitar",
|
||||
@@ -172,9 +172,9 @@
|
||||
"column.domain_blocks": "Dominios ocultados",
|
||||
"column.edit_list": "Editar lista",
|
||||
"column.favourites": "Favoritos",
|
||||
"column.firehose": "Cronologías",
|
||||
"column.firehose_local": "Cronología para este servidor",
|
||||
"column.firehose_singular": "Cronología",
|
||||
"column.firehose": "Feeds en vivo",
|
||||
"column.firehose_local": "Feed en vivo para este servidor",
|
||||
"column.firehose_singular": "Feed en vivo",
|
||||
"column.follow_requests": "Solicitudes de seguimiento",
|
||||
"column.home": "Inicio",
|
||||
"column.list_members": "Administrar miembros de la lista",
|
||||
@@ -194,7 +194,7 @@
|
||||
"community.column_settings.local_only": "Solo local",
|
||||
"community.column_settings.media_only": "Solo media",
|
||||
"community.column_settings.remote_only": "Solo remoto",
|
||||
"compose.error.blank_post": "El mensaje no puede estar en blanco.",
|
||||
"compose.error.blank_post": "La publicación no puede estar vacía.",
|
||||
"compose.language.change": "Cambiar idioma",
|
||||
"compose.language.search": "Buscar idiomas...",
|
||||
"compose.published.body": "Publicado.",
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Publicar de todas maneras",
|
||||
"confirmations.missing_alt_text.title": "¿Añadir texto alternativo?",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.private_quote_notify.cancel": "Seguir editando",
|
||||
"confirmations.private_quote_notify.confirm": "Publicar",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "No mostrar este mensaje de nuevo",
|
||||
"confirmations.private_quote_notify.message": "Tu publicación será notificada y podrá ser vista por la persona a la que mencionas y otras menciones, aún si no te siguen.",
|
||||
"confirmations.private_quote_notify.title": "¿Compartir con seguidores y usuarios mencionados?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "No me lo recuerdes otra vez",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Entendido",
|
||||
"confirmations.quiet_post_quote_info.message": "Al citar una publicación pública discreta, tu publicación se ocultará de las cronologías de tendencias.",
|
||||
@@ -264,8 +269,8 @@
|
||||
"confirmations.unblock.title": "¿Desbloquear a {name}?",
|
||||
"confirmations.unfollow.confirm": "Dejar de seguir",
|
||||
"confirmations.unfollow.title": "¿Dejar de seguir a {name}?",
|
||||
"confirmations.withdraw_request.confirm": "Retirar solicitud",
|
||||
"confirmations.withdraw_request.title": "¿Retirar solicitud de seguimiento a {name}?",
|
||||
"confirmations.withdraw_request.confirm": "Cancelar solicitud",
|
||||
"confirmations.withdraw_request.title": "¿Cancelar solicitud para seguir a {name}?",
|
||||
"content_warning.hide": "Ocultar publicación",
|
||||
"content_warning.show": "Mostrar de todos modos",
|
||||
"content_warning.show_more": "Mostrar más",
|
||||
@@ -336,7 +341,7 @@
|
||||
"empty_column.bookmarked_statuses": "Aún no tienes ninguna publicación guardada como marcador. Cuando guardes una, se mostrará aquí.",
|
||||
"empty_column.community": "La cronología local está vacía. ¡Escribe algo públicamente para ponerla en marcha!",
|
||||
"empty_column.direct": "Aún no tienes menciones privadas. Cuando envíes o recibas una, aparecerán aquí.",
|
||||
"empty_column.disabled_feed": "Esta cronología ha sido desactivada por los administradores del servidor.",
|
||||
"empty_column.disabled_feed": "Este feed fue desactivado por los administradores de tu servidor.",
|
||||
"empty_column.domain_blocks": "Todavía no hay dominios ocultos.",
|
||||
"empty_column.explore_statuses": "Nada es tendencia en este momento. ¡Revisa más tarde!",
|
||||
"empty_column.favourited_statuses": "Todavía no tienes publicaciones favoritas. Cuando le des favorito a una publicación se mostrarán acá.",
|
||||
@@ -757,8 +762,9 @@
|
||||
"privacy.unlisted.short": "Pública, pero discreta",
|
||||
"privacy_policy.last_updated": "Actualizado por última vez {date}",
|
||||
"privacy_policy.title": "Política de Privacidad",
|
||||
"quote_error.edit": "No se pueden añadir citas cuando se edita una publicación.",
|
||||
"quote_error.edit": "No se pueden añadir citas mientras un post está siendo editado.",
|
||||
"quote_error.poll": "No se permite citar encuestas.",
|
||||
"quote_error.private_mentions": "Citar no está disponible sin menciones directas.",
|
||||
"quote_error.quote": "Solo se permite una cita a la vez.",
|
||||
"quote_error.unauthorized": "No estás autorizado a citar esta publicación.",
|
||||
"quote_error.upload": "No se permite citar con archivos multimedia.",
|
||||
@@ -798,7 +804,7 @@
|
||||
"report.forward": "Reenviar a {target}",
|
||||
"report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?",
|
||||
"report.mute": "Silenciar",
|
||||
"report.mute_explanation": "No veras sus publiaciones. Todavía pueden seguirte y ver tus publicaciones y no sabrán que están silenciados.",
|
||||
"report.mute_explanation": "No verás sus publicaciones. Todavía pueden seguirte y ver tus publicaciones y no sabrán que están silenciados.",
|
||||
"report.next": "Siguiente",
|
||||
"report.placeholder": "Comentarios adicionales",
|
||||
"report.reasons.dislike": "No me gusta",
|
||||
@@ -880,7 +886,7 @@
|
||||
"status.contains_quote": "Contiene cita",
|
||||
"status.context.loading": "Cargando más respuestas",
|
||||
"status.context.loading_error": "No se pudieron cargar nuevas respuestas",
|
||||
"status.context.loading_success": "Cargadas nuevas respuestas",
|
||||
"status.context.loading_success": "Nuevas respuestas cargadas",
|
||||
"status.context.more_replies_found": "Se han encontrado más respuestas",
|
||||
"status.context.retry": "Reintentar",
|
||||
"status.context.show": "Mostrar",
|
||||
@@ -912,12 +918,12 @@
|
||||
"status.pin": "Fijar",
|
||||
"status.quote": "Citar",
|
||||
"status.quote.cancel": "Cancelar cita",
|
||||
"status.quote_error.blocked_account_hint.title": "Esta publicación está oculta porque has bloqueado a @{name}.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Esta publicación está oculta porque has bloqueado @{domain}.",
|
||||
"status.quote_error.blocked_account_hint.title": "Esta publicación se ocultó porque bloqueaste a @{name}.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Este post está oculto porque bloqueaste {domain}.",
|
||||
"status.quote_error.filtered": "Oculto debido a uno de tus filtros",
|
||||
"status.quote_error.limited_account_hint.action": "Mostrar de todas formas",
|
||||
"status.quote_error.limited_account_hint.title": "Esta cuenta ha sido ocultada por los moderadores de {domain}.",
|
||||
"status.quote_error.muted_account_hint.title": "Esta publicación está oculta porque has silenciado a @{name}.",
|
||||
"status.quote_error.muted_account_hint.title": "Esta publicación está oculta porque silenciaste a @{name}.",
|
||||
"status.quote_error.not_available": "Publicación no disponible",
|
||||
"status.quote_error.pending_approval": "Publicación pendiente",
|
||||
"status.quote_error.pending_approval_popout.body": "En Mastodon, puedes controlar si alguien puede citarte. Esta publicación está pendiente mientras obtenemos la aprobación del autor original.",
|
||||
@@ -930,8 +936,8 @@
|
||||
"status.quote_private": "Las publicaciones privadas no pueden citarse",
|
||||
"status.quotes": "{count, plural,one {cita} other {citas}}",
|
||||
"status.quotes.empty": "Nadie ha citado esta publicación todavía. Cuando alguien lo haga, aparecerá aquí.",
|
||||
"status.quotes.local_other_disclaimer": "Las citas rechazadas por el autor no se mostrarán.",
|
||||
"status.quotes.remote_other_disclaimer": "Solo se garantiza que se muestren las citas de {domain}. Las citas rechazadas por el autor no se mostrarán.",
|
||||
"status.quotes.local_other_disclaimer": "Las citas rechazadas pro el autor no serán mostradas.",
|
||||
"status.quotes.remote_other_disclaimer": "Solo las citas hechas por {domain} están garantizadas a ser vistas aquí. Las citas rechazadas por el autor no serán mostradas.",
|
||||
"status.read_more": "Leer más",
|
||||
"status.reblog": "Impulsar",
|
||||
"status.reblog_or_quote": "Impulsar o citar",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Bajar el volumen",
|
||||
"video.volume_up": "Subir el volumen",
|
||||
"visibility_modal.button_title": "Establece la visibilidad",
|
||||
"visibility_modal.direct_quote_warning.text": "Si guardas la siguiente configuración, se mostrará un enlace en vez de la cita incrustada.",
|
||||
"visibility_modal.direct_quote_warning.title": "No se pueden incrustar citas en menciones privadas",
|
||||
"visibility_modal.header": "Visibilidad e interacción",
|
||||
"visibility_modal.helper.direct_quoting": "Las menciones privadas creadas en Mastodon no pueden ser citadas por otros.",
|
||||
"visibility_modal.helper.privacy_editing": "La visibilidad no se puede cambiar después de que se haya hecho una publicación.",
|
||||
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Publicar de todos modos",
|
||||
"confirmations.missing_alt_text.title": "¿Deseas añadir texto alternativo?",
|
||||
"confirmations.mute.confirm": "Silenciar",
|
||||
"confirmations.private_quote_notify.cancel": "Volver a la edición",
|
||||
"confirmations.private_quote_notify.confirm": "Publicar",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "No mostrar este mensaje de nuevo",
|
||||
"confirmations.private_quote_notify.message": "La persona a la que estás citando y otras mencionadas serán notificadas y podrán ver tu publicación, incluso si no te siguen.",
|
||||
"confirmations.private_quote_notify.title": "¿Compartir con seguidores y usuarios mencionados?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "No me lo vuelvas a recordar",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Entendido",
|
||||
"confirmations.quiet_post_quote_info.message": "Cuando cites una publicación pública silenciosa, tu publicación se ocultará de las cronologías de tendencias.",
|
||||
@@ -759,6 +764,7 @@
|
||||
"privacy_policy.title": "Política de Privacidad",
|
||||
"quote_error.edit": "No se pueden añadir citas cuando se edita una publicación.",
|
||||
"quote_error.poll": "No es posible citar encuestas.",
|
||||
"quote_error.private_mentions": "No se permite citar con menciones privadas.",
|
||||
"quote_error.quote": "Solo se permite una cita a la vez.",
|
||||
"quote_error.unauthorized": "No tienes permiso para citar esta publicación.",
|
||||
"quote_error.upload": "No se permite citar con archivos multimedia.",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Bajar volumen",
|
||||
"video.volume_up": "Subir volumen",
|
||||
"visibility_modal.button_title": "Configura la visibilidad",
|
||||
"visibility_modal.direct_quote_warning.text": "Si guardas la configuración actual, la cita incrustada se convertirá en un enlace.",
|
||||
"visibility_modal.direct_quote_warning.title": "No se pueden incluir citas en menciones privadas",
|
||||
"visibility_modal.header": "Visibilidad e interacciones",
|
||||
"visibility_modal.helper.direct_quoting": "Las menciones privadas publicadas en Mastodon no pueden ser citadas por otros usuarios.",
|
||||
"visibility_modal.helper.privacy_editing": "La visibilidad no se puede cambiar después de que se haya hecho una publicación.",
|
||||
|
||||
@@ -64,10 +64,10 @@
|
||||
"account.media": "Meedia",
|
||||
"account.mention": "Maini @{name}",
|
||||
"account.moved_to": "{name} on teada andnud, et ta uus konto on nüüd:",
|
||||
"account.mute": "Vaigista @{name}",
|
||||
"account.mute_notifications_short": "Vaigista teavitused",
|
||||
"account.mute_short": "Vaigista",
|
||||
"account.muted": "Vaigistatud",
|
||||
"account.mute": "Summuta @{name}",
|
||||
"account.mute_notifications_short": "Summuta teavitused",
|
||||
"account.mute_short": "Summuta",
|
||||
"account.muted": "Summutatud",
|
||||
"account.muting": "Summutatud konto",
|
||||
"account.mutual": "Te jälgite teineteist",
|
||||
"account.no_bio": "Kirjeldust pole lisatud.",
|
||||
@@ -87,9 +87,9 @@
|
||||
"account.unblock_short": "Eemalda blokeering",
|
||||
"account.unendorse": "Ära kuva profiilil",
|
||||
"account.unfollow": "Jälgid",
|
||||
"account.unmute": "Ära vaigista @{name}",
|
||||
"account.unmute_notifications_short": "Tühista teadete vaigistamine",
|
||||
"account.unmute_short": "Lõpeta vaigistamine",
|
||||
"account.unmute": "Lõpeta {name} kasutaja summutamine",
|
||||
"account.unmute_notifications_short": "Lõpeta teavituste summutamine",
|
||||
"account.unmute_short": "Lõpeta summutamine",
|
||||
"account_note.placeholder": "Klõpsa märke lisamiseks",
|
||||
"admin.dashboard.daily_retention": "Kasutajate päevane allesjäämine peale registreerumist",
|
||||
"admin.dashboard.monthly_retention": "Kasutajate kuine allesjäämine peale registreerumist",
|
||||
@@ -173,11 +173,13 @@
|
||||
"column.edit_list": "Muuda loendit",
|
||||
"column.favourites": "Lemmikud",
|
||||
"column.firehose": "Postitused reaalajas",
|
||||
"column.firehose_local": "Selle serveri sisuvoog reaalajas",
|
||||
"column.firehose_singular": "Postitused reaalajas",
|
||||
"column.follow_requests": "Jälgimistaotlused",
|
||||
"column.home": "Kodu",
|
||||
"column.list_members": "Halda loendi liikmeid",
|
||||
"column.lists": "Loetelud",
|
||||
"column.mutes": "Vaigistatud kasutajad",
|
||||
"column.mutes": "Summutatud kasutajad",
|
||||
"column.notifications": "Teated",
|
||||
"column.pins": "Kinnitatud postitused",
|
||||
"column.public": "Föderatiivne ajajoon",
|
||||
@@ -192,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Ainult kohalik",
|
||||
"community.column_settings.media_only": "Ainult meedia",
|
||||
"community.column_settings.remote_only": "Ainult kaug",
|
||||
"compose.error.blank_post": "Postitus ei saa jääda tühjaks.",
|
||||
"compose.language.change": "Muuda keelt",
|
||||
"compose.language.search": "Otsi keeli...",
|
||||
"compose.published.body": "Postitus tehtud.",
|
||||
@@ -243,7 +246,12 @@
|
||||
"confirmations.missing_alt_text.message": "Sinu postituses on ilma alt-tekstita meediat. Kirjelduse lisamine aitab su sisu muuta ligipääsetavaks rohkematele inimestele.",
|
||||
"confirmations.missing_alt_text.secondary": "Postita siiski",
|
||||
"confirmations.missing_alt_text.title": "Lisada alt-tekst?",
|
||||
"confirmations.mute.confirm": "Vaigista",
|
||||
"confirmations.mute.confirm": "Summuta",
|
||||
"confirmations.private_quote_notify.cancel": "Tagasi muutmise juurde",
|
||||
"confirmations.private_quote_notify.confirm": "Avalda postitus",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Ära kuva enam seda sõnumit uuesti",
|
||||
"confirmations.private_quote_notify.message": "Nii see, keda sa tsiteerid, kui need, keda mainid, saavad asjakohase teavituse ja võivad vaadata sinu postitust ka siis, kui nad pole sinu jälgijad.",
|
||||
"confirmations.private_quote_notify.title": "Kas jagad jälgijate ja mainitud kasutajatega?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Ära tuleta enam meelde",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Sain aru",
|
||||
"confirmations.quiet_post_quote_info.message": "Vaikse, aga avaliku postituse tsiteerimisel sinu postitus on peidetud populaarsust koguvatel ajajoontel.",
|
||||
@@ -287,7 +295,7 @@
|
||||
"domain_block_modal.they_can_interact_with_old_posts": "Inimesed sellest serverist saavad suhestuda sinu vanade postitustega.",
|
||||
"domain_block_modal.they_cant_follow": "Sellest serverist ei saa keegi sind jälgida.",
|
||||
"domain_block_modal.they_wont_know": "Nad ei tea, et nad on blokeeritud.",
|
||||
"domain_block_modal.title": "Blokeerida domeen?",
|
||||
"domain_block_modal.title": "Kas blokeerid domeeni?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "Sult kaob {followersCount, plural, one {{followersCountDisplay} jälgija} other {{followersCountDisplay} jälgijat}} ja {followingCount, plural, one {{followingCountDisplay} inimene} other {{followingCountDisplay} inimest}}, keda sa ise jälgid.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Sa kaotad kõik oma jälgijad ja inimesed, kes sind jälgivad sellest serverist.",
|
||||
"domain_block_modal.you_wont_see_posts": "Sa ei näe selle serveri kasutajate postitusi ega teavitusi.",
|
||||
@@ -343,7 +351,7 @@
|
||||
"empty_column.hashtag": "Selle sildi all ei ole ühtegi postitust.",
|
||||
"empty_column.home": "Su koduajajoon on tühi. Jälgi rohkemaid inimesi, et seda täita {suggestions}",
|
||||
"empty_column.list": "Siin loetelus pole veel midagi. Kui loetelu liikmed teevad uusi postitusi, näed neid siin.",
|
||||
"empty_column.mutes": "Sa pole veel ühtegi kasutajat vaigistanud.",
|
||||
"empty_column.mutes": "Sa pole veel ühtegi kasutajat summutanud.",
|
||||
"empty_column.notification_requests": "Kõik tühi! Siin pole mitte midagi. Kui saad uusi teavitusi, ilmuvad need siin vastavalt sinu seadistustele.",
|
||||
"empty_column.notifications": "Ei ole veel teateid. Kui keegi suhtleb sinuga, näed seda siin.",
|
||||
"empty_column.public": "Siin pole midagi! Kirjuta midagi avalikku või jälgi ise kasutajaid täitmaks seda ruumi",
|
||||
@@ -431,7 +439,7 @@
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} postitust} other {{counter} postitust}} täna",
|
||||
"hashtag.feature": "Tõsta profiilis esile",
|
||||
"hashtag.follow": "Jälgi silti",
|
||||
"hashtag.mute": "Vaigista @#{hashtag}",
|
||||
"hashtag.mute": "Summuta teemaviide @#{hashtag}",
|
||||
"hashtag.unfeature": "Ära tõsta profiilis esile",
|
||||
"hashtag.unfollow": "Lõpeta sildi jälgimine",
|
||||
"hashtags.and_other": "…ja {count, plural, one {}other {# veel}}",
|
||||
@@ -491,7 +499,7 @@
|
||||
"keyboard_shortcuts.load_more": "Fookus „Laadi veel“ nupule",
|
||||
"keyboard_shortcuts.local": "Ava kohalik ajajoon",
|
||||
"keyboard_shortcuts.mention": "Maini autorit",
|
||||
"keyboard_shortcuts.muted": "Ava vaigistatud kasutajate loetelu",
|
||||
"keyboard_shortcuts.muted": "Ava summutatud kasutajate loetelu",
|
||||
"keyboard_shortcuts.my_profile": "Ava oma profiil",
|
||||
"keyboard_shortcuts.notifications": "Ava teadete veerg",
|
||||
"keyboard_shortcuts.open_media": "Ava meedia",
|
||||
@@ -552,11 +560,11 @@
|
||||
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Peida teavituste hulgast",
|
||||
"mute_modal.hide_options": "Peida valikud",
|
||||
"mute_modal.indefinite": "Kuni eemaldan neilt vaigistuse",
|
||||
"mute_modal.indefinite": "Kuni eemaldan neilt summutamise",
|
||||
"mute_modal.show_options": "Kuva valikud",
|
||||
"mute_modal.they_can_mention_and_follow": "Ta saab sind mainida ja sind jälgida, kuid sa ei näe teda.",
|
||||
"mute_modal.they_wont_know": "Ta ei tea, et ta on vaigistatud.",
|
||||
"mute_modal.title": "Vaigistada kasutaja?",
|
||||
"mute_modal.they_wont_know": "Ta ei tea, et ta on summutatud.",
|
||||
"mute_modal.title": "Kas summutad kasutaja?",
|
||||
"mute_modal.you_wont_see_mentions": "Sa ei näe postitusi, mis teda mainivad.",
|
||||
"mute_modal.you_wont_see_posts": "Ta näeb jätkuvalt sinu postitusi, kuid sa ei näe tema omi.",
|
||||
"navigation_bar.about": "Teave",
|
||||
@@ -569,7 +577,7 @@
|
||||
"navigation_bar.direct": "Privaatsed mainimised",
|
||||
"navigation_bar.domain_blocks": "Peidetud domeenid",
|
||||
"navigation_bar.favourites": "Lemmikud",
|
||||
"navigation_bar.filters": "Vaigistatud sõnad",
|
||||
"navigation_bar.filters": "Summutatud sõnad",
|
||||
"navigation_bar.follow_requests": "Jälgimistaotlused",
|
||||
"navigation_bar.followed_tags": "Jälgitavad märksõnad",
|
||||
"navigation_bar.follows_and_followers": "Jälgitavad ja jälgijad",
|
||||
@@ -580,7 +588,7 @@
|
||||
"navigation_bar.logout": "Logi välja",
|
||||
"navigation_bar.moderation": "Modereerimine",
|
||||
"navigation_bar.more": "Lisavalikud",
|
||||
"navigation_bar.mutes": "Vaigistatud kasutajad",
|
||||
"navigation_bar.mutes": "Summutatud kasutajad",
|
||||
"navigation_bar.opened_in_classic_interface": "Postitused, kontod ja teised spetsiaalsed lehed avatakse vaikimisi klassikalises veebiliideses.",
|
||||
"navigation_bar.preferences": "Eelistused",
|
||||
"navigation_bar.privacy_and_reach": "Privaatsus ja ulatus",
|
||||
@@ -756,6 +764,7 @@
|
||||
"privacy_policy.title": "Isikuandmete kaitse",
|
||||
"quote_error.edit": "Postituse muutmisel ei saa tsitaati lisada.",
|
||||
"quote_error.poll": "Tsiteerimine pole küsitlustes lubatud.",
|
||||
"quote_error.private_mentions": "Tsiteerimine pole otsemainimiste puhul lubatud.",
|
||||
"quote_error.quote": "Korraga on lubatud vaid üks tsitaat.",
|
||||
"quote_error.unauthorized": "Sul pole õigust seda postitust tsiteerida.",
|
||||
"quote_error.upload": "Tsiteerimine pole manuste puhul lubatud.",
|
||||
@@ -794,8 +803,8 @@
|
||||
"report.comment.title": "Kas arvad, et on veel midagi, mida me peaks teadma?",
|
||||
"report.forward": "Edasta ka {target} domeeni",
|
||||
"report.forward_hint": "See kasutaja on teisest serverist. Kas saadan anonümiseeritud koopia sellest teatest sinna ka?",
|
||||
"report.mute": "Vaigista",
|
||||
"report.mute_explanation": "Sa ei näe tema postitusi. Ta võib ikka sind jälgida ja su postitusi näha. Ta ei saa teada, et ta on vaigistatud.",
|
||||
"report.mute": "Summuta",
|
||||
"report.mute_explanation": "Sa ei näe tema postitusi. Ta võib ikka sind jälgida ja su postitusi näha. Ta ei saa teada, et ta on summutatud.",
|
||||
"report.next": "Järgmine",
|
||||
"report.placeholder": "Lisaks kommentaarid",
|
||||
"report.reasons.dislike": "Mulle ei meeldi see",
|
||||
@@ -903,15 +912,18 @@
|
||||
"status.media_hidden": "Meedia peidetud",
|
||||
"status.mention": "Maini @{name}'i",
|
||||
"status.more": "Veel",
|
||||
"status.mute": "Vaigista @{name}",
|
||||
"status.mute_conversation": "Vaigista vestlus",
|
||||
"status.mute": "Summuta @{name}",
|
||||
"status.mute_conversation": "Summuta vestlus",
|
||||
"status.open": "Laienda postitus",
|
||||
"status.pin": "Kinnita profiilile",
|
||||
"status.quote": "Tsiteeri",
|
||||
"status.quote.cancel": "Katkesta tsiteerimine",
|
||||
"status.quote_error.blocked_account_hint.title": "Kuna sa oled blokeerinud kasutaja @{name}, siis see postitus on peidetud.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Kuna sa oled blokeerinud domeeni @{domain}, siis see postitus on peidetud.",
|
||||
"status.quote_error.filtered": "Peidetud mõne kasutatud filtri tõttu",
|
||||
"status.quote_error.limited_account_hint.action": "Näita ikkagi",
|
||||
"status.quote_error.limited_account_hint.title": "See profiil on peidetud {domain} serveri moderaatorite poolt.",
|
||||
"status.quote_error.muted_account_hint.title": "Kuna sa oled summutanud kasutaja @{name}, siis see postitus on peidetud.",
|
||||
"status.quote_error.not_available": "Postitus pole saadaval",
|
||||
"status.quote_error.pending_approval": "Postitus on ootel",
|
||||
"status.quote_error.pending_approval_popout.body": "Mastodonis saad sa kontrollida seda, kes võib sind tsiteerida. See postitus on seni ootel, kuni pole algse autori kinnitust tsiteerimisele.",
|
||||
@@ -953,7 +965,7 @@
|
||||
"status.translate": "Tõlgi",
|
||||
"status.translated_from_with": "Tõlgitud {lang} keelest kasutades teenust {provider}",
|
||||
"status.uncached_media_warning": "Eelvaade pole saadaval",
|
||||
"status.unmute_conversation": "Ära vaigista vestlust",
|
||||
"status.unmute_conversation": "Lõpeta vestluse summutamine",
|
||||
"status.unpin": "Eemalda profiilile kinnitus",
|
||||
"subscribed_languages.lead": "Pärast muudatust näed koduvaates ja loetelude ajajoontel postitusi valitud keeltes. Ära vali midagi, kui tahad näha postitusi kõikides keeltes.",
|
||||
"subscribed_languages.save": "Salvesta muudatused",
|
||||
@@ -997,15 +1009,17 @@
|
||||
"video.expand": "Suurenda video",
|
||||
"video.fullscreen": "Täisekraan",
|
||||
"video.hide": "Peida video",
|
||||
"video.mute": "Vaigista",
|
||||
"video.mute": "Summuta",
|
||||
"video.pause": "Paus",
|
||||
"video.play": "Mängi",
|
||||
"video.skip_backward": "Keri tagasi",
|
||||
"video.skip_forward": "Keri edasi",
|
||||
"video.unmute": "Lõpeta vaigistamine",
|
||||
"video.unmute": "Lõpeta summutamine",
|
||||
"video.volume_down": "Heli vaiksemaks",
|
||||
"video.volume_up": "Heli valjemaks",
|
||||
"visibility_modal.button_title": "Muuda nähtavust",
|
||||
"visibility_modal.direct_quote_warning.text": "Kui sa need seadistused salvestad, siis lõimitud tsitaat muutub lingiks.",
|
||||
"visibility_modal.direct_quote_warning.title": "Tsitaate ei saa privaatse mainimise puhul lõimida",
|
||||
"visibility_modal.header": "Nähtavus ja kasutus",
|
||||
"visibility_modal.helper.direct_quoting": "Ainult mainituile mõeldud Mastodoni postitusi ei saa teiste poolt tsiteerida.",
|
||||
"visibility_modal.helper.privacy_editing": "Nähtavust ei saa peale postituse avaldamist muuta.",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzaileak argitaratzean",
|
||||
"account.domain_blocking": "Eragotzitako domeinua",
|
||||
"account.edit_profile": "Editatu profila",
|
||||
"account.edit_profile_short": "Editatu",
|
||||
"account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean",
|
||||
"account.endorse": "Nabarmendu profilean",
|
||||
"account.familiar_followers_many": "Jarraitzaileak: {name1}, {name2} eta beste {othersCount, plural, one {ezagun bat} other {# ezagun}}",
|
||||
@@ -40,6 +41,11 @@
|
||||
"account.featured_tags.last_status_never": "Bidalketarik ez",
|
||||
"account.follow": "Jarraitu",
|
||||
"account.follow_back": "Jarraitu bueltan",
|
||||
"account.follow_back_short": "Jarraitu bueltan",
|
||||
"account.follow_request": "Eskatu jarraitzeko",
|
||||
"account.follow_request_cancel": "Ezeztatu eskaera",
|
||||
"account.follow_request_cancel_short": "Ezeztatu",
|
||||
"account.follow_request_short": "Eskaera",
|
||||
"account.followers": "Jarraitzaileak",
|
||||
"account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} jarraitzaile} other {{counter} jarraitzaile}}",
|
||||
@@ -107,6 +113,11 @@
|
||||
"alt_text_modal.describe_for_people_with_visual_impairments": "Deskribatu hau ikusmen arazoak dituzten pertsonentzat…",
|
||||
"alt_text_modal.done": "Egina",
|
||||
"announcement.announcement": "Iragarpena",
|
||||
"annual_report.summary.archetype.booster": "Sustatzailea",
|
||||
"annual_report.summary.archetype.lurker": "Begiluzea",
|
||||
"annual_report.summary.archetype.oracle": "Orakulua",
|
||||
"annual_report.summary.archetype.pollster": "Bozketazalea",
|
||||
"annual_report.summary.archetype.replier": "Tolosa",
|
||||
"annual_report.summary.followers.followers": "jarraitzaileak",
|
||||
"annual_report.summary.followers.total": "{count} guztira",
|
||||
"annual_report.summary.here_it_is": "Hona hemen zure {year}. urtearen bilduma:",
|
||||
@@ -162,6 +173,8 @@
|
||||
"column.edit_list": "Editatu zerrenda",
|
||||
"column.favourites": "Gogokoak",
|
||||
"column.firehose": "Zuzeneko jarioak",
|
||||
"column.firehose_local": "Zerbitzari honen zuzeneko jarioa",
|
||||
"column.firehose_singular": "Zuzeneko jarioa",
|
||||
"column.follow_requests": "Jarraitzeko eskaerak",
|
||||
"column.home": "Hasiera",
|
||||
"column.list_members": "Kudeatu zerrrendako partaideak",
|
||||
@@ -181,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Lokala soilik",
|
||||
"community.column_settings.media_only": "Edukiak soilik",
|
||||
"community.column_settings.remote_only": "Urrunekoa soilik",
|
||||
"compose.error.blank_post": "Bidalketa ezin da hutsik egon.",
|
||||
"compose.language.change": "Aldatu hizkuntza",
|
||||
"compose.language.search": "Bilatu hizkuntzak...",
|
||||
"compose.published.body": "Argitalpena argitaratuta.",
|
||||
@@ -233,13 +247,30 @@
|
||||
"confirmations.missing_alt_text.secondary": "Bidali edonola ere",
|
||||
"confirmations.missing_alt_text.title": "Testu alternatiboa gehitu?",
|
||||
"confirmations.mute.confirm": "Mututu",
|
||||
"confirmations.private_quote_notify.cancel": "Ediziora bueltatu",
|
||||
"confirmations.private_quote_notify.confirm": "Argitaratu bidalketa",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Ez erakutsi mezu hau berriro",
|
||||
"confirmations.private_quote_notify.message": "Aipatzen ari zaren pertsonak eta aipatutako besteek jakinarazpena jasoko dute eta zure sarrera ikusi ahalko dute, zure jarraitzaileak ez badira ere.",
|
||||
"confirmations.private_quote_notify.title": "Partekatu jarraitzaileekin eta aipatutako erabiltzaileekin?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Ez gogorarazi berriro",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Ulertuta",
|
||||
"confirmations.quiet_post_quote_info.message": "Deiadar urriko bidalketa bat aipatzen duzunean, zure bidalketa joeretatik ezkutatuko da.",
|
||||
"confirmations.quiet_post_quote_info.title": "Deiadar urriko bidalketaren aipua",
|
||||
"confirmations.redraft.confirm": "Ezabatu eta berridatzi",
|
||||
"confirmations.redraft.message": "Ziur argitalpen hau ezabatu eta zirriborroa berriro egitea nahi duzula? Gogokoak eta bultzadak galduko dira, eta jatorrizko argitalpenaren erantzunak zurtz geratuko dira.",
|
||||
"confirmations.redraft.title": "Ezabatu eta berridatzi bidalketa?",
|
||||
"confirmations.remove_from_followers.confirm": "Jarraitzailea Kendu",
|
||||
"confirmations.remove_from_followers.message": "{name}-k zu jarraitzeari utziko dio. Seguru zaude jarraitu nahi duzula?",
|
||||
"confirmations.remove_from_followers.title": "Jarraitzailea kendu nahi duzu?",
|
||||
"confirmations.revoke_quote.confirm": "Ezabatu bidalketa",
|
||||
"confirmations.revoke_quote.message": "Ekintza hau ezin da desegin.",
|
||||
"confirmations.revoke_quote.title": "Ezabatu bidalketa?",
|
||||
"confirmations.unblock.confirm": "Desblokeatu",
|
||||
"confirmations.unblock.title": "Desblokeatu {name}?",
|
||||
"confirmations.unfollow.confirm": "Utzi jarraitzeari",
|
||||
"confirmations.unfollow.title": "{name} jarraitzeari utzi?",
|
||||
"confirmations.withdraw_request.confirm": "Baztertu eskaera",
|
||||
"confirmations.withdraw_request.title": "Baztertu {name} jarraitzeko eskaera?",
|
||||
"content_warning.hide": "Tuta ezkutatu",
|
||||
"content_warning.show": "Erakutsi hala ere",
|
||||
"content_warning.show_more": "Erakutsi gehiago",
|
||||
@@ -265,6 +296,7 @@
|
||||
"domain_block_modal.they_cant_follow": "Zerbitzari honetako inork ezin zaitu jarraitu.",
|
||||
"domain_block_modal.they_wont_know": "Ez dute jakingo blokeatuak izan direnik.",
|
||||
"domain_block_modal.title": "Domeinua blokeatu nahi duzu?",
|
||||
"domain_block_modal.you_will_lose_num_followers": "{followersCount, plural, one {Jarraitzaile {followersCountDisplay}} other {{followersCountDisplay} jarraitzaile}} eta {followingCount, plural, one {jarraitzen duzun pertsona {followingCountDisplay}} other {jarraitzen dituzun beste {followingCountDisplay} pertsona}} galduko dituzu.",
|
||||
"domain_block_modal.you_will_lose_relationships": "Instantzia honetatik jarraitzen dituzun jarraitzaile eta pertsona guztiak galduko dituzu.",
|
||||
"domain_block_modal.you_wont_see_posts": "Ez dituzu zerbitzari honetako erabiltzaileen argitalpenik edota jakinarazpenik ikusiko.",
|
||||
"domain_pill.activitypub_lets_connect": "Mastodon-en ez ezik, beste sare sozialen aplikazioetako jendearekin konektatzea eta harremanetan jartzea uzten dizu.",
|
||||
@@ -280,6 +312,7 @@
|
||||
"domain_pill.your_handle": "Zure helbidea:",
|
||||
"domain_pill.your_server": "Zure etxe digitala, non zure bidalketak dauden. Ez al zaizu gustatzen? Transferitu zerbitzariak edonoiz eta ekarri zure jarraitzaileak ere.",
|
||||
"domain_pill.your_username": "Zerbitzarian duzun identifikatzaile bakarra. Baliteke erabiltzaile-izen bera duten erabiltzaileak zerbitzari desberdinetan aurkitzea.",
|
||||
"dropdown.empty": "Aukeratu bat",
|
||||
"embed.instructions": "Txertatu bidalketa hau zure webgunean beheko kodea kopiatuz.",
|
||||
"embed.preview": "Hau da izango duen itxura:",
|
||||
"emoji_button.activity": "Jarduera",
|
||||
@@ -308,6 +341,7 @@
|
||||
"empty_column.bookmarked_statuses": "Oraindik ez dituzu bidalketa laster-markatutarik. Bat laster-markatzerakoan, hemen agertuko da.",
|
||||
"empty_column.community": "Denbora-lerro lokala hutsik dago. Idatzi zerbait publikoki pilota biraka jartzeko!",
|
||||
"empty_column.direct": "Ez duzu aipamen pribaturik oraindik. Baten bat bidali edo jasotzen duzunean, hemen agertuko da.",
|
||||
"empty_column.disabled_feed": "Zure zerbitzariko administratzaileek jario hau desgaitu dute.",
|
||||
"empty_column.domain_blocks": "Ez dago ezkutatutako domeinurik oraindik.",
|
||||
"empty_column.explore_statuses": "Ez dago joerarik une honetan. Begiratu beranduago!",
|
||||
"empty_column.favourited_statuses": "Ez duzu gogokorik oraindik. Gogoko bat duzunean, hemen agertuko da.",
|
||||
@@ -332,6 +366,7 @@
|
||||
"explore.trending_links": "Berriak",
|
||||
"explore.trending_statuses": "Tutak",
|
||||
"explore.trending_tags": "Traolak",
|
||||
"featured_carousel.header": "{count, plural, one {Finkatutako sarrera} other {Finkatutako sarrerak}}",
|
||||
"featured_carousel.next": "Hurrengoa",
|
||||
"featured_carousel.post": "Argitaratu",
|
||||
"featured_carousel.previous": "Aurrekoa",
|
||||
@@ -435,10 +470,12 @@
|
||||
"ignore_notifications_modal.private_mentions_title": "Eskatu gabeko aipamen pribatuen jakinarazpenei ez ikusiarena egin?",
|
||||
"info_button.label": "Laguntza",
|
||||
"info_button.what_is_alt_text": "<h1>Zer da Alt testua?</h1><p>Alt testuak irudiak deskribatzeko aukera ematen du, ikusmen-urritasunak, banda-zabalera txikiko konexioak edo testuinguru gehigarria nahi duten pertsonentzat.</p><p>Alt testu argi, zehatz eta objektiboen bidez, guztion irisgarritasuna eta ulermena hobetu ditzakezu.</p><ul><li>Hartu elementu garrantzitsuenak</li><li>Laburbildu irudietako testua</li><li>Erabili esaldien egitura erregularra</li><li>Baztertu informazio erredundantea.</li><li>Enfokatu joeretan eta funtsezko elementuetan irudi konplexuetan (diagrametan edo mapetan, adibidez)</li></ul>",
|
||||
"interaction_modal.action": "{name} erabiltzailearen sarrerarekin interaktuatzeko, saioa hasi behar duzu erabiltzen duzun Mastodon zerbitzarian.",
|
||||
"interaction_modal.go": "Joan",
|
||||
"interaction_modal.no_account_yet": "Ez al duzu konturik oraindik?",
|
||||
"interaction_modal.on_another_server": "Beste zerbitzari batean",
|
||||
"interaction_modal.on_this_server": "Zerbitzari honetan",
|
||||
"interaction_modal.title": "Hasi saioa jarraitzeko",
|
||||
"interaction_modal.username_prompt": "Adib. {example}",
|
||||
"intervals.full.days": "{number, plural, one {egun #} other {# egun}}",
|
||||
"intervals.full.hours": "{number, plural, one {ordu #} other {# ordu}}",
|
||||
@@ -459,6 +496,7 @@
|
||||
"keyboard_shortcuts.home": "hasierako denbora-lerroa irekitzeko",
|
||||
"keyboard_shortcuts.hotkey": "Laster-tekla",
|
||||
"keyboard_shortcuts.legend": "legenda hau bistaratzea",
|
||||
"keyboard_shortcuts.load_more": "Fokuratu \"Kargatu gehiago\" botoia",
|
||||
"keyboard_shortcuts.local": "denbora-lerro lokala irekitzeko",
|
||||
"keyboard_shortcuts.mention": "egilea aipatzea",
|
||||
"keyboard_shortcuts.muted": "mutututako erabiltzaileen zerrenda irekitzeko",
|
||||
@@ -467,6 +505,7 @@
|
||||
"keyboard_shortcuts.open_media": "Ireki edukia",
|
||||
"keyboard_shortcuts.pinned": "Ireki finkatutako bidalketen zerrenda",
|
||||
"keyboard_shortcuts.profile": "egilearen profila irekitzeko",
|
||||
"keyboard_shortcuts.quote": "Aipatu sarrera",
|
||||
"keyboard_shortcuts.reply": "Erantzun bidalketari",
|
||||
"keyboard_shortcuts.requests": "Jarraitzeko eskaeren zerrenda irekia",
|
||||
"keyboard_shortcuts.search": "bilaketan fokua jartzea",
|
||||
@@ -478,6 +517,8 @@
|
||||
"keyboard_shortcuts.translate": "bidalketa itzultzeko",
|
||||
"keyboard_shortcuts.unfocus": "testua konposatzeko area / bilaketatik fokua kentzea",
|
||||
"keyboard_shortcuts.up": "zerrendan gora mugitzea",
|
||||
"learn_more_link.got_it": "Ulertuta",
|
||||
"learn_more_link.learn_more": "Ikasi gehiago",
|
||||
"lightbox.close": "Itxi",
|
||||
"lightbox.next": "Hurrengoa",
|
||||
"lightbox.previous": "Aurrekoa",
|
||||
@@ -500,6 +541,7 @@
|
||||
"lists.exclusive": "Ezkutatu kideak Hasieran",
|
||||
"lists.exclusive_hint": "Norbait zerrenda honetan badago, ezkutatu zure Hasierako jariotik mezuak bi aldiz ez ikusteko.",
|
||||
"lists.find_users_to_add": "Bilatu erabiltzaileak gehitzeko",
|
||||
"lists.list_members_count": "{count, plural, one {kide #} other {# kide}}",
|
||||
"lists.list_name": "Zerrenda izena",
|
||||
"lists.new_list_name": "Zerrenda izen berria",
|
||||
"lists.no_lists_yet": "Ez duzu zerrendarik oraindik.",
|
||||
@@ -511,6 +553,7 @@
|
||||
"lists.replies_policy.none": "Bat ere ez",
|
||||
"lists.save": "Gorde",
|
||||
"lists.search": "Bilatu",
|
||||
"lists.show_replies_to": "Erakutsi zerrendako kideen erantzunak hauei:",
|
||||
"load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
|
||||
"loading_indicator.label": "Kargatzen…",
|
||||
"media_gallery.hide": "Ezkutatu",
|
||||
@@ -551,6 +594,10 @@
|
||||
"navigation_bar.privacy_and_reach": "Pribatutasuna eta irismena",
|
||||
"navigation_bar.search": "Bilatu",
|
||||
"navigation_bar.search_trends": "Bilatu / Joera",
|
||||
"navigation_panel.collapse_followed_tags": "Itxi jarraitzen dituzun traolen menua",
|
||||
"navigation_panel.collapse_lists": "Itxi zerrenden menua",
|
||||
"navigation_panel.expand_followed_tags": "Zabaldu jarraitzen dituzun traolen menua",
|
||||
"navigation_panel.expand_lists": "Zabaldu zerrenden menua",
|
||||
"not_signed_in_indicator.not_signed_in": "Baliabide honetara sarbidea izateko saioa hasi behar duzu.",
|
||||
"notification.admin.report": "{name} erabiltzaileak {target} salatu du",
|
||||
"notification.admin.report_account": "{name}-(e)k {target}-ren {count, plural, one {bidalketa bat} other {# bidalketa}} salatu zituen {category} delakoagatik",
|
||||
@@ -559,15 +606,20 @@
|
||||
"notification.admin.report_statuses_other": "{name} erabiltzaileak {target} salatu du",
|
||||
"notification.admin.sign_up": "{name} erabiltzailea erregistratu da",
|
||||
"notification.admin.sign_up.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiago} other {# erabiltzaile gehiago}} erregistratu dira",
|
||||
"notification.annual_report.message": "Zain duzu {year}(e)ko #Wrapstodon! Ikusi urteko zure unerik gogoangarrienak Mastodonen!",
|
||||
"notification.annual_report.view": "Ikusi #Wrapstodon",
|
||||
"notification.favourite": "{name}(e)k zure bidalketa gogoko du",
|
||||
"notification.favourite.name_and_others_with_link": "{name} eta <a>{count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}}</a> zure bidalketa gogoko dute",
|
||||
"notification.favourite_pm": "{name}-ek zure aipamen pribatua gogokoetan jarri du",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} erabiltzaileak eta beste <a>{count, plural, one {#ek} other {#(e)k}}</a> gogoko dute zure aipu pribatua",
|
||||
"notification.follow": "{name}(e)k jarraitzen dizu",
|
||||
"notification.follow.name_and_others": "{name} erabiltzaileak eta <a>{count, plural, one {# gehiagok} other {# gehiagok}}</a> jarraitu dizute",
|
||||
"notification.follow_request": "{name}(e)k zu jarraitzeko eskaera egin du",
|
||||
"notification.follow_request.name_and_others": "{name} eta {count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}} zu jarraitzeko eskaera egin dute",
|
||||
"notification.label.mention": "Aipamena",
|
||||
"notification.label.private_mention": "Aipamen pribatua",
|
||||
"notification.label.private_reply": "Erantzun pribatua",
|
||||
"notification.label.quote": "{name} erabiltzaileak zure bidalketa aipatu du",
|
||||
"notification.label.reply": "Erantzuna",
|
||||
"notification.mention": "Aipamena",
|
||||
"notification.mentioned_you": "{name}(e)k aipatu zaitu",
|
||||
@@ -582,18 +634,23 @@
|
||||
"notification.moderation_warning.action_suspend": "Kontua itxi da.",
|
||||
"notification.own_poll": "Zure inkesta amaitu da",
|
||||
"notification.poll": "Zuk erantzun duzun inkesta bat bukatu da",
|
||||
"notification.quoted_update": "{name} erabiltzaileak aipatu duzun post bat editatu du",
|
||||
"notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari",
|
||||
"notification.reblog.name_and_others_with_link": "{name} eta <a>{count, plural, one {erabiltzaile # gehiagok} other {# erabiltzaile gehiagok}}</a> bultzada eman diote zure bidalketari",
|
||||
"notification.relationships_severance_event": "{name} erabiltzailearekin galdutako konexioak",
|
||||
"notification.relationships_severance_event.account_suspension": "{from} zerbitzariko administratzaile batek {target} bertan behera utzi du, hau da, ezin izango dituzu jaso hango eguneratzerik edo hangoekin elkarreragin.",
|
||||
"notification.relationships_severance_event.domain_block": "{from} zerbitzariko administratzaile batek {target} blokeatu du, tartean zure {followersCount} jarraitzaile eta jarraitzen duzun {followingCount, plural, one {kontu #} other {# kontu}}.",
|
||||
"notification.relationships_severance_event.learn_more": "Informazio gehiago",
|
||||
"notification.relationships_severance_event.user_domain_block": "{target} blokeatu duzu, tartean zure {followersCount} jarraitzaile eta jarraitzen duzun {followingCount, plural, one {kontu #} other {# kontu}}.",
|
||||
"notification.status": "{name} erabiltzaileak bidalketa egin berri du",
|
||||
"notification.update": "{name} erabiltzaileak bidalketa bat editatu du",
|
||||
"notification_requests.accept": "Onartu",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {Onartu eskaera…} other {Onartu # eskaerak…}}",
|
||||
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Onartu eskaera} other {Onartu eskaerak}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "{count, plural, one {Jakinarazpen eskaera bat} other {# jakinarazpen eskaera}} onartzekotan zaude. Jarraitu nahi duzu?",
|
||||
"notification_requests.confirm_accept_multiple.title": "Onartu jakinarazpen-eskaerak?",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Baztertu eskaera} other {Baztertu eskaerak}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "{count, plural, one {Jakinarazpen eskaera bat} other {# jakinarazpen eskaera}} baztertzekotan zaude. Gerora ezingo {count, plural, one {duzu} other {dituzu}} berriz erraz atzitu. Jarraitu nahi duzu?",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "Baztertu jakinarazpen-eskaerak?",
|
||||
"notification_requests.dismiss": "Baztertu",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Baztertu eskaera…} other {Baztertu # eskaerak…}}",
|
||||
@@ -621,6 +678,7 @@
|
||||
"notifications.column_settings.mention": "Aipamenak:",
|
||||
"notifications.column_settings.poll": "Inkestaren emaitzak:",
|
||||
"notifications.column_settings.push": "Push jakinarazpenak",
|
||||
"notifications.column_settings.quote": "Aipuak:",
|
||||
"notifications.column_settings.reblog": "Bultzadak:",
|
||||
"notifications.column_settings.show": "Erakutsi zutabean",
|
||||
"notifications.column_settings.sound": "Jo soinua",
|
||||
@@ -691,14 +749,25 @@
|
||||
"poll_button.remove_poll": "Kendu inkesta",
|
||||
"privacy.change": "Aldatu bidalketaren pribatutasuna",
|
||||
"privacy.direct.long": "Argitalpen honetan aipatutako denak",
|
||||
"privacy.direct.short": "Aipu pribatua",
|
||||
"privacy.private.long": "Soilik jarraitzaileak",
|
||||
"privacy.private.short": "Jarraitzaileak",
|
||||
"privacy.public.long": "Mastodonen dagoen edo ez dagoen edonor",
|
||||
"privacy.public.short": "Publikoa",
|
||||
"privacy.quote.anyone": "{visibility}, edonork aipa dezake",
|
||||
"privacy.quote.disabled": "{visibility}, aipuak desgaituta",
|
||||
"privacy.quote.limited": "{visibility}, aipuak mugatuta",
|
||||
"privacy.unlisted.additional": "Aukera honek publiko modua bezala funtzionatzen du, baina argitalpena ez da agertuko zuzeneko jarioetan edo traoletan, \"Arakatu\" atalean edo Mastodonen bilaketan, nahiz eta kontua zabaltzeko onartu duzun.",
|
||||
"privacy.unlisted.long": "Ezkutatuta Mastodon bilaketen emaitzetatik, joeretatik, eta denbora-lerro publikoetatik",
|
||||
"privacy.unlisted.short": "Deiadar urrikoa",
|
||||
"privacy_policy.last_updated": "Azkenengo eguneraketa {date}",
|
||||
"privacy_policy.title": "Pribatutasun politika",
|
||||
"quote_error.edit": "Aipuak ezin dira gehitu bidalketa bat editatzean.",
|
||||
"quote_error.poll": "Inkestak ezin dira aipatu.",
|
||||
"quote_error.private_mentions": "Aipuak ez dira onartzen aipamen pribatuetan.",
|
||||
"quote_error.quote": "Bidalketa bakoitzeko aipu bakarra onartzen da.",
|
||||
"quote_error.unauthorized": "Ez duzu baimenik bidalketa hau aipatzeko.",
|
||||
"quote_error.upload": "Aipuak ez dira onartzen multimedia eranskinekin.",
|
||||
"recommended": "Gomendatua",
|
||||
"refresh": "Berritu",
|
||||
"regeneration_indicator.please_stand_by": "Itxaron, mesedez.",
|
||||
@@ -714,6 +783,9 @@
|
||||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"relative_time.today": "gaur",
|
||||
"remove_quote_hint.button_label": "Ulertuta",
|
||||
"remove_quote_hint.message": "{icon} aukeren menutik egin dezakezu.",
|
||||
"remove_quote_hint.title": "Aipatu dizuten bidalketa kendu nahi duzu?",
|
||||
"reply_indicator.attachments": "{count, plural, one {# eranskin} other {# eranskin}}",
|
||||
"reply_indicator.cancel": "Utzi",
|
||||
"reply_indicator.poll": "Inkesta",
|
||||
@@ -788,8 +860,10 @@
|
||||
"search_results.all": "Guztiak",
|
||||
"search_results.hashtags": "Traolak",
|
||||
"search_results.no_results": "Emaitzarik ez.",
|
||||
"search_results.no_search_yet": "Saiatu bilatzen bidalketak, profilak edo traolak.",
|
||||
"search_results.see_all": "Ikusi guztiak",
|
||||
"search_results.statuses": "Bidalketak",
|
||||
"search_results.title": "Bilatu \"{q}\"",
|
||||
"server_banner.about_active_users": "Azken 30 egunetan zerbitzari hau erabili duen jendea (hilabeteko erabiltzaile aktiboak)",
|
||||
"server_banner.active_users": "erabiltzaile aktibo",
|
||||
"server_banner.administered_by": "Administratzailea(k):",
|
||||
@@ -803,13 +877,23 @@
|
||||
"status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea",
|
||||
"status.admin_domain": "{domain}-(r)en moderazio-interfazea ireki",
|
||||
"status.admin_status": "Ireki bidalketa hau moderazio interfazean",
|
||||
"status.all_disabled": "Bultzadak eta aipuak desgaituta daude",
|
||||
"status.block": "Blokeatu @{name}",
|
||||
"status.bookmark": "Laster-marka",
|
||||
"status.cancel_reblog_private": "Kendu bultzada",
|
||||
"status.cannot_quote": "Ez duzu bidalketa hau aipatzeko baimenik",
|
||||
"status.cannot_reblog": "Bidalketa honi ezin zaio bultzada eman",
|
||||
"status.contains_quote": "Aipua darama",
|
||||
"status.context.loading": "Erantzun gehiago kargatzen",
|
||||
"status.context.loading_error": "Ezin erantzun berririk kargatu",
|
||||
"status.context.loading_success": "Erantzun berriak kargatuta",
|
||||
"status.context.more_replies_found": "Erantzun gehiago aurkitu dira",
|
||||
"status.context.retry": "Saiatu berriz",
|
||||
"status.context.show": "Erakutsi",
|
||||
"status.continued_thread": "Harian jarraitu zuen",
|
||||
"status.copy": "Kopiatu bidalketaren esteka",
|
||||
"status.delete": "Ezabatu",
|
||||
"status.delete.success": "Bidalketa ezabatuta",
|
||||
"status.detailed_status": "Elkarrizketaren ikuspegi xehetsua",
|
||||
"status.direct": "Aipatu pribatuki @{name}",
|
||||
"status.direct_indicator": "Aipamen pribatua",
|
||||
@@ -832,18 +916,46 @@
|
||||
"status.mute_conversation": "Mututu elkarrizketa",
|
||||
"status.open": "Hedatu bidalketa hau",
|
||||
"status.pin": "Finkatu profilean",
|
||||
"status.quote": "Aipua",
|
||||
"status.quote.cancel": "Utzi aipua",
|
||||
"status.quote_error.blocked_account_hint.title": "Bidalketa hau ezkutatuta dago @{name} blokeatu duzulako.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Bidalketa hau ezkutatuta dago {domain} blokeatu duzulako.",
|
||||
"status.quote_error.filtered": "Ezkutatuta zure iragazki baten ondorioz",
|
||||
"status.quote_error.limited_account_hint.action": "Erakutsi hala ere",
|
||||
"status.quote_error.limited_account_hint.title": "{domain} zerbitzariaren moderatzaileek kontu hau ezkutatu dute.",
|
||||
"status.quote_error.muted_account_hint.title": "Bidalketa hau ezkutatuta dago @{name} mututu duzulako.",
|
||||
"status.quote_error.not_available": "Bidalketa ez dago eskuragarri",
|
||||
"status.quote_error.pending_approval": "Bidalketa zain dago",
|
||||
"status.quote_error.pending_approval_popout.body": "Mastodonen, norbaitek aipa zaitzakeen kontrola dezakezu. Bidalketa hau argitaratzeke dago jatorrizko egilearen oniritzia jaso bitartean.",
|
||||
"status.quote_error.revoked": "Egileak bidalketa kendu du",
|
||||
"status.quote_followers_only": "Bidalketa hau jarraitzaileek soilik aipatu dezakete",
|
||||
"status.quote_manual_review": "Egileak eskuz berrikusiko du",
|
||||
"status.quote_noun": "Aipua",
|
||||
"status.quote_policy_change": "Aldatu nork aipa zaitzakeen",
|
||||
"status.quote_post_author": "@{name} erabiltzailearen bidalketaren aipua",
|
||||
"status.quote_private": "Bidalketa pribatuak ezin dira aipatu",
|
||||
"status.quotes": "{count, plural, one {aipu} other {aipu}}",
|
||||
"status.quotes.empty": "Momentuz inork ez du bidalketa hau aipatu. Norbaitek eginez gero, hemen agertuko da.",
|
||||
"status.quotes.local_other_disclaimer": "Egileak errefusatutako aipuak ez dira erakutsiko.",
|
||||
"status.quotes.remote_other_disclaimer": "{domain} zerbitzariko aipuak baino ez daude bermatuta hemen. Egileak errefusatutako aipuak ez dira erakutsiko.",
|
||||
"status.read_more": "Irakurri gehiago",
|
||||
"status.reblog": "Bultzada",
|
||||
"status.reblog_or_quote": "Bultzatu edo aipatu",
|
||||
"status.reblog_private": "Partekatu berriz zure jarraitzaileekin",
|
||||
"status.reblogged_by": "{name}(r)en bultzada",
|
||||
"status.reblogs": "{count, plural, one {bultzada} other {bultzada}}",
|
||||
"status.reblogs.empty": "Inork ez dio bultzada eman bidalketa honi oraindik. Inork egiten badu, hemen agertuko da.",
|
||||
"status.redraft": "Ezabatu eta berridatzi",
|
||||
"status.remove_bookmark": "Kendu laster-marka",
|
||||
"status.remove_favourite": "Kendu gogokoetatik",
|
||||
"status.remove_quote": "Kendu",
|
||||
"status.replied_in_thread": "Harian erantzun zuen",
|
||||
"status.replied_to": "{name} erabiltzaileari erantzuna",
|
||||
"status.reply": "Erantzun",
|
||||
"status.replyAll": "Erantzun harian",
|
||||
"status.report": "Salatu @{name}",
|
||||
"status.request_quote": "Eskatu aipatzeko",
|
||||
"status.revoke_quote": "Ezabatu nire bidalketa @{name}-(r)en bidalketatik",
|
||||
"status.sensitive_warning": "Kontuz: Eduki hunkigarria",
|
||||
"status.share": "Partekatu",
|
||||
"status.show_less_all": "Erakutsi denetarik gutxiago",
|
||||
@@ -863,7 +975,9 @@
|
||||
"tabs_bar.notifications": "Jakinarazpenak",
|
||||
"tabs_bar.publish": "Bidalketa berria",
|
||||
"tabs_bar.search": "Bilatu",
|
||||
"terms_of_service.effective_as_of": "Indarrean {date}tik aurrera",
|
||||
"terms_of_service.title": "Zerbitzuaren baldintzak",
|
||||
"terms_of_service.upcoming_changes_on": "{date} datarako datozen aldaketak",
|
||||
"time_remaining.days": "{number, plural, one {egun #} other {# egun}} amaitzeko",
|
||||
"time_remaining.hours": "{number, plural, one {ordu #} other {# ordu}} amaitzeko",
|
||||
"time_remaining.minutes": "{number, plural, one {minutu #} other {# minutu}} amaitzeko",
|
||||
@@ -879,6 +993,12 @@
|
||||
"upload_button.label": "Gehitu multimedia (JPEG, PNG, GIF, WebM, MP4, MOV)",
|
||||
"upload_error.limit": "Fitxategi igoera muga gaindituta.",
|
||||
"upload_error.poll": "Ez da inkestetan fitxategiak igotzea onartzen.",
|
||||
"upload_error.quote": "Artxiboak igotzea ez da onartzen aipuekin.",
|
||||
"upload_form.drag_and_drop.instructions": "Multimedia eranskin bat aukeratzeko, sakatu espazioa edo enter. Arrastatu bitartean, erabili gezi-teklak multimedia eranskina edozein norabidetan mugitzeko. Sakatu berriz espazioa edon enter multimedia eranskina bere kokapen berrian jartzeko, edo sakatu ESC uzteko.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Arrastatzea bertan behera utzi da. {item} multimedia eranskina ez da mugitu.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "{item} multimedia eranskina ez da mugitu.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "{item} multimedia eranskina mugitu da.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "{item} multimedia eranskina hautatu da.",
|
||||
"upload_form.edit": "Editatu",
|
||||
"upload_progress.label": "Igotzen...",
|
||||
"upload_progress.processing": "Prozesatzen…",
|
||||
@@ -889,10 +1009,28 @@
|
||||
"video.expand": "Hedatu bideoa",
|
||||
"video.fullscreen": "Pantaila osoa",
|
||||
"video.hide": "Ezkutatu bideoa",
|
||||
"video.mute": "Mututu",
|
||||
"video.pause": "Pausatu",
|
||||
"video.play": "Jo",
|
||||
"video.skip_backward": "Saltatu atzerantz",
|
||||
"video.skip_forward": "Jauzi aurrerantz",
|
||||
"video.unmute": "Soinua ezarri",
|
||||
"video.volume_down": "Bolumena jaitsi",
|
||||
"video.volume_up": "Bolumena Igo"
|
||||
"video.volume_up": "Bolumena Igo",
|
||||
"visibility_modal.button_title": "Ezarri ikusgarritasuna",
|
||||
"visibility_modal.direct_quote_warning.text": "Uneko ezarpenak gordez gero, txertatutako aipua lotura bilakatuko da.",
|
||||
"visibility_modal.direct_quote_warning.title": "Aipuak ezin dira sartu aipamen prbatuetan",
|
||||
"visibility_modal.header": "Ikusgarritasuna eta elkarreraginak",
|
||||
"visibility_modal.helper.direct_quoting": "Mastodonen argitaratutako aipamen pribatuak ezin dituzte beste erabiltzaileek aipatu.",
|
||||
"visibility_modal.helper.privacy_editing": "Ikusgarritasuna ezin da aldatu bidalketa argitaratu ondoren.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Bidalketa pribatuen auto-aipuak ezin dira publiko egin.",
|
||||
"visibility_modal.helper.private_quoting": "Jarraitzaileentzat soilik sortutako bidalketak Mastodonen ezin dituzte beste batzuek aipatu.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Jendeak aipatzen zaituenean, bere bidalketa ere joeren denbora-lerro publikoetatik ezkutatuko da.",
|
||||
"visibility_modal.instructions": "Kontrolatu nork izan dezakeen elkarreragina bidalketa honekin. Ezarpenak etorkizuneko bidalketa guztiei ere aplika diezazkiekezu <link>Hobespenak > Bidalketarako lehentsitakoak</link> atalera joanda.",
|
||||
"visibility_modal.privacy_label": "Ikusgarritasuna",
|
||||
"visibility_modal.quote_followers": "Jarraitzaileentzat soilik",
|
||||
"visibility_modal.quote_label": "Nork aipa dezake",
|
||||
"visibility_modal.quote_nobody": "Nik bakarrik",
|
||||
"visibility_modal.quote_public": "Edonork",
|
||||
"visibility_modal.save": "Gorde"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"about.blocks": "کارسازهای نظارت شده",
|
||||
"about.blocks": "کارسازهای نظارت شده",
|
||||
"about.contact": "تماس:",
|
||||
"about.default_locale": "پیشفرض",
|
||||
"about.default_locale": "پیشگزیده",
|
||||
"about.disclaimer": "ماستودون نرمافزار آزاد و نشان تجاری یک شرکت غیر انتفاعی با مسئولیت محدود آلمانی است.",
|
||||
"about.domain_blocks.no_reason_available": "دلیلی موجود نیست",
|
||||
"about.domain_blocks.preamble": "ماستودون عموماً میگذارد محتوا را از از هر کارساز دیگری در دنیای شبکههای اجتماعی غیرمتمرکز دیده و با آنان برهمکنش داشته باشید. اینها استثناهایی هستند که روی این کارساز خاص وضع شدهاند.",
|
||||
@@ -28,18 +28,24 @@
|
||||
"account.disable_notifications": "آگاه کردن من هنگام فرستههای @{name} را متوقّف کن",
|
||||
"account.domain_blocking": "دامنهٔ مسدود کرده",
|
||||
"account.edit_profile": "ویرایش نمایه",
|
||||
"account.edit_profile_short": "ویرایش",
|
||||
"account.enable_notifications": "هنگام فرستههای @{name} مرا آگاه کن",
|
||||
"account.endorse": "معرّفی در نمایه",
|
||||
"account.familiar_followers_many": "پیگرفته از سوی {name1}، {name2} و {othersCount, plural,one {یکی دیگر از پیگرفتههایتان} other {# نفر دیگر از پیگرفتههایتان}}",
|
||||
"account.familiar_followers_one": "پیگرفته از سوی {name1}",
|
||||
"account.familiar_followers_two": "پیگرفته از سوی {name1} و {name2}",
|
||||
"account.featured": " پیشنهادی",
|
||||
"account.featured": "پیشنهادی",
|
||||
"account.featured.accounts": "نمایهها",
|
||||
"account.featured.hashtags": "برچسبها",
|
||||
"account.featured_tags.last_status_at": "آخرین فرسته در {date}",
|
||||
"account.featured_tags.last_status_never": "بدون فرسته",
|
||||
"account.follow": "پیگرفتن",
|
||||
"account.follow_back": "پیگیری متقابل",
|
||||
"account.follow_back_short": "پیگیری متقابل",
|
||||
"account.follow_request": "درخواست پیگیری",
|
||||
"account.follow_request_cancel": "لغو درخواست",
|
||||
"account.follow_request_cancel_short": "لغو",
|
||||
"account.follow_request_short": "درخواست",
|
||||
"account.followers": "پیگیرندگان",
|
||||
"account.followers.empty": "هنوز کسی پیگیر این کاربر نیست.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} پیگیرنده} other {{counter} پیگیرنده}}",
|
||||
@@ -167,6 +173,8 @@
|
||||
"column.edit_list": "ویرایش سیاهه",
|
||||
"column.favourites": "برگزیدهها",
|
||||
"column.firehose": "خوراکهای زنده",
|
||||
"column.firehose_local": "خوراک زندهٔ این کارساز",
|
||||
"column.firehose_singular": "خوراک زنده",
|
||||
"column.follow_requests": "درخواستهای پیگیری",
|
||||
"column.home": "خانه",
|
||||
"column.list_members": "مدیریت اعضای سیاهه",
|
||||
@@ -186,6 +194,7 @@
|
||||
"community.column_settings.local_only": "فقط محلی",
|
||||
"community.column_settings.media_only": "فقط رسانه",
|
||||
"community.column_settings.remote_only": "تنها دوردست",
|
||||
"compose.error.blank_post": "فرسته نمیتواند خالی باشد.",
|
||||
"compose.language.change": "تغییر زبان",
|
||||
"compose.language.search": "جستوجوی زبانها...",
|
||||
"compose.published.body": "فرسته منتشر شد.",
|
||||
@@ -238,6 +247,15 @@
|
||||
"confirmations.missing_alt_text.secondary": "به هر حال پست کن",
|
||||
"confirmations.missing_alt_text.title": "متن جایگزین اضافه شود؟",
|
||||
"confirmations.mute.confirm": "خموش",
|
||||
"confirmations.private_quote_notify.cancel": "بازگشت به ویرایش کردن",
|
||||
"confirmations.private_quote_notify.confirm": "انتشار فرسته",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "دیگر این پیام نشان داده نشود",
|
||||
"confirmations.private_quote_notify.message": "فردی که نقلش میکنید و کسانی که اشاره شدهاند آگاه خواهند شد و میتوانند فرستهتان را ببینند؛ حتا اگر پیتان نگیرند.",
|
||||
"confirmations.private_quote_notify.title": "همرسانی با پیگیرندگان و کاربران اشاره شده؟",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "دیگر یادآوری نشود",
|
||||
"confirmations.quiet_post_quote_info.got_it": "گرفتم",
|
||||
"confirmations.quiet_post_quote_info.message": "هنگام نقل کردن فرستهٔ عمومی ساکت، فرستهتان از خطهای زمانی داغ پنهان خواهد بود.",
|
||||
"confirmations.quiet_post_quote_info.title": "نقل کردن فرستههای عمومی ساکت",
|
||||
"confirmations.redraft.confirm": "حذف و بازنویسی",
|
||||
"confirmations.redraft.message": "مطمئنید که میخواهید این فرسته را حذف کنید و از نو بنویسید؟ با این کار تقویتها و پسندهایش از دست رفته و پاسخها به آن بیمرجع میشود.",
|
||||
"confirmations.redraft.title": "حذف و پیشنویسی دوبارهٔ فرسته؟",
|
||||
@@ -247,7 +265,12 @@
|
||||
"confirmations.revoke_quote.confirm": "حذف فرسته",
|
||||
"confirmations.revoke_quote.message": "این اقدام قابل بازگشت نیست.",
|
||||
"confirmations.revoke_quote.title": "آیا فرسته را حذف کنم؟",
|
||||
"confirmations.unblock.confirm": "رفع انسداد",
|
||||
"confirmations.unblock.title": "رفع انسداد {name}؟",
|
||||
"confirmations.unfollow.confirm": "پینگرفتن",
|
||||
"confirmations.unfollow.title": "ناپیگیری {name}؟",
|
||||
"confirmations.withdraw_request.confirm": "انصراف از درخواست",
|
||||
"confirmations.withdraw_request.title": "انصراف از درخواست پیگیری {name}؟",
|
||||
"content_warning.hide": "نهفتن فرسته",
|
||||
"content_warning.show": "در هر صورت نشان داده شود",
|
||||
"content_warning.show_more": "نمایش بیشتر",
|
||||
@@ -318,6 +341,7 @@
|
||||
"empty_column.bookmarked_statuses": "هنوز هیچ فرستهٔ نشانهگذاری شدهای ندارید. هنگامی که فرستهای را نشانهگذاری کنید، اینجا نشان داده خواهد شد.",
|
||||
"empty_column.community": "خط زمانی محلی خالیست. چیزی نوشته تا چرخش بچرخد!",
|
||||
"empty_column.direct": "هنوز هیچ اشاره خصوصیای ندارید. هنگامی که چنین پیامی بگیرید یا بفرستید اینجا نشان داده خواهد شد.",
|
||||
"empty_column.disabled_feed": "این خوراک به دست مدیران کارسازتان از کار انداخته شده.",
|
||||
"empty_column.domain_blocks": "هنوز هیچ دامنهای مسدود نشده است.",
|
||||
"empty_column.explore_statuses": "الآن چیزی پرطرفدار نیست. بعداً دوباره بررسی کنید!",
|
||||
"empty_column.favourited_statuses": "شما هنوز هیچ فرستهای را نپسندیدهاید. هنگامی که فرستهای را بپسندید، اینجا نشان داده خواهد شد.",
|
||||
@@ -446,10 +470,12 @@
|
||||
"ignore_notifications_modal.private_mentions_title": "چشمپوشی از نامبریهای خصوصی ناخواسته؟",
|
||||
"info_button.label": "راهنما",
|
||||
"info_button.what_is_alt_text": "<h1>متن جایگزین چیست؟</h1> <p>متن جایگزین توضیحات تصویری را برای افراد دارای اختلالات بینایی، اتصالات با پهنای باند کم یا کسانی که به دنبال زمینه اضافی هستند ارائه می دهد.</p> <p>با نوشتن متن جایگزین واضح، مختصر و عینی می توانید دسترسی و درک را برای همه بهبود بخشید.</p> <ul> <li>عناصر مهم را ضبط کنید</li> <li>متن را در تصاویر خلاصه کنید</li> <li>از ساختار جمله منظم استفاده کنید</li> <li>از اطلاعات اضافی خودداری کنید</li> <li>روی روندها و یافته های کلیدی در تصاویر پیچیده (مانند نمودارها یا نقشه ها) تمرکز کنید.</li> </ul>",
|
||||
"interaction_modal.action": "برای تعامل با فرستهٔ {name} باید به حسابتان روی هر کارساز ماستودونی که استفاده میکنید وارد شوید.",
|
||||
"interaction_modal.go": "برو",
|
||||
"interaction_modal.no_account_yet": "هنوز حساب کاربری ندارید؟",
|
||||
"interaction_modal.on_another_server": "روی کارسازی دیگر",
|
||||
"interaction_modal.on_this_server": "روی این کارساز",
|
||||
"interaction_modal.title": "ورود برای ادامه",
|
||||
"interaction_modal.username_prompt": "به عنوان مثال {example}",
|
||||
"intervals.full.days": "{number, plural, one {# روز} other {# روز}}",
|
||||
"intervals.full.hours": "{number, plural, one {# ساعت} other {# ساعت}}",
|
||||
@@ -470,6 +496,7 @@
|
||||
"keyboard_shortcuts.home": "گشودن خط زمانی خانگی",
|
||||
"keyboard_shortcuts.hotkey": "میانبر",
|
||||
"keyboard_shortcuts.legend": "نمایش این نشانه",
|
||||
"keyboard_shortcuts.load_more": "تمرکز روی دکمهٔ «بار کردن بیشتر»",
|
||||
"keyboard_shortcuts.local": "گشودن خط زمانی محلی",
|
||||
"keyboard_shortcuts.mention": "اشاره به نویسنده",
|
||||
"keyboard_shortcuts.muted": "گشودن فهرست کاربران خموش",
|
||||
@@ -478,6 +505,7 @@
|
||||
"keyboard_shortcuts.open_media": "گشودن رسانه",
|
||||
"keyboard_shortcuts.pinned": "گشودن سیاههٔ فرستههای سنجاق شده",
|
||||
"keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
|
||||
"keyboard_shortcuts.quote": "نقل فرسته",
|
||||
"keyboard_shortcuts.reply": "پاسخ به فرسته",
|
||||
"keyboard_shortcuts.requests": "گشودن سیاههٔ درخواستهای پیگیری",
|
||||
"keyboard_shortcuts.search": "تمرکز روی نوار جستوجو",
|
||||
@@ -527,7 +555,7 @@
|
||||
"lists.search": "جستوجو",
|
||||
"lists.show_replies_to": "شامل پاسخ از اعضای لیست به",
|
||||
"load_pending": "{count, plural, one {# مورد جدید} other {# مورد جدید}}",
|
||||
"loading_indicator.label": "در حال بارگذاری…",
|
||||
"loading_indicator.label": "بار کردن…",
|
||||
"media_gallery.hide": "نهفتن",
|
||||
"moved_to_account_banner.text": "حسابتان {disabledAccount} اکنون از کار افتاده؛ چرا که به {movedToAccount} منتقل شدید.",
|
||||
"mute_modal.hide_from_notifications": "نهفتن از آگاهیها",
|
||||
@@ -582,8 +610,8 @@
|
||||
"notification.annual_report.view": "دیدن #Wrapstodon",
|
||||
"notification.favourite": "{name} فرستهتان را برگزید",
|
||||
"notification.favourite.name_and_others_with_link": "{name} و <a>{count, plural, one {# نفر دیگر} other {# نفر دیگر}}</a> فرستهتان را برگزیدند",
|
||||
"notification.favourite_pm": "{name} ذکر خصوصی شما را مورد علاقه قرار داد",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} و <a>{count, plural, one {دیگری} other {دیگران}}</a> ذکر خصوصی شما را مورد علاقه قرار دادند",
|
||||
"notification.favourite_pm": "{name} اشارهٔ خصوصیتان را برگزید",
|
||||
"notification.favourite_pm.name_and_others_with_link": "{name} و <a>{count, plural, one {# نفر دیگر} other {# نفر دیگر}}</a> اشارهٔ خصوصیتان را برگزیدند",
|
||||
"notification.follow": "{name} پیگیرتان شد",
|
||||
"notification.follow.name_and_others": "{name} و <a>{count, plural, other {#}} نفر دیگر</a> پیتان گرفتند",
|
||||
"notification.follow_request": "{name} درخواست پیگیریتان را داد",
|
||||
@@ -606,6 +634,7 @@
|
||||
"notification.moderation_warning.action_suspend": "حسابتان معلّق شده.",
|
||||
"notification.own_poll": "نظرسنجیتان پایان یافت",
|
||||
"notification.poll": "نظرسنجیای که در آن رأی دادید به پایان رسید",
|
||||
"notification.quoted_update": "{name} فرستهای که نقل کردید را ویراست",
|
||||
"notification.reblog": "{name} فرستهتان را تقویت کرد",
|
||||
"notification.reblog.name_and_others_with_link": "{name} و <a>{count, plural, one {# نفر دیگر} other {# نفر دیگر}}</a> فرستهتان را تقویت کردند",
|
||||
"notification.relationships_severance_event": "قطع ارتباط با {name}",
|
||||
@@ -618,10 +647,10 @@
|
||||
"notification_requests.accept": "پذیرش",
|
||||
"notification_requests.accept_multiple": "{count, plural, one {پذیرش درخواست…} other {پذیرش درخواستها…}}",
|
||||
"notification_requests.confirm_accept_multiple.button": "پذیرش {count, plural,one {درخواست} other {درخواستها}}",
|
||||
"notification_requests.confirm_accept_multiple.message": "در حال پذیرش {count, plural,one {یک}other {#}} درخواست آگاهی هستید. مطمئنید که میخواهید ادامه دهید؟",
|
||||
"notification_requests.confirm_accept_multiple.message": "دارید {count, plural,one {یک}other {#}} درخواست آگاهی را میپذیرید. مطمئنید که میخواهید ادامه دهید؟",
|
||||
"notification_requests.confirm_accept_multiple.title": "پذیرش درخواستهای آگاهی؟",
|
||||
"notification_requests.confirm_dismiss_multiple.button": "رد {count, plural,one {درخواست} other {درخواستها}}",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "شما در شرف رد کردن {count, plural, one {یک درخواست آگاهی} other {درخواست آگاهی}} هستید. دیگر نمی توانید به راحتی به {count, plural, one {آن} other {آنها}} دسترسی پیدا کنید. آیا مطمئن هستید که می خواهید ادامه دهید؟",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "دارید {count, plural, one {یک درخواست آگاهی} other {# درخواست آگاهی}} را رد میکنید که دیگر نمی توانید به راحتی به {count, plural, one {آن} other {آنها}} دسترسی پیدا کنید. مطمئنید که میخواهید ادامه دهید؟",
|
||||
"notification_requests.confirm_dismiss_multiple.title": "رد کردن درخواستهای آگاهی؟",
|
||||
"notification_requests.dismiss": "دورانداختن",
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {دورانداختن درخواست…} other {دورانداختن درخواستها…}}",
|
||||
@@ -720,19 +749,29 @@
|
||||
"poll_button.remove_poll": "برداشتن نظرسنجی",
|
||||
"privacy.change": "تغییر محرمانگی فرسته",
|
||||
"privacy.direct.long": "هرکسی که در فرسته نام برده شده",
|
||||
"privacy.direct.short": "ذکر خصوصی",
|
||||
"privacy.direct.short": "اشارهٔ خصوصی",
|
||||
"privacy.private.long": "تنها پیگیرندگانتان",
|
||||
"privacy.private.short": "پیگیرندگان",
|
||||
"privacy.public.long": "هرکسی در و بیرون از ماستودون",
|
||||
"privacy.public.short": "عمومی",
|
||||
"privacy.quote.anyone": "{visibility}، هرکسی میتواند نقل کند",
|
||||
"privacy.quote.disabled": "{visibility}، نقلها از کار افتاده",
|
||||
"privacy.quote.limited": "{visibility}، نقلها محدود شده",
|
||||
"privacy.unlisted.additional": "درست مثل عمومی رفتار میکند؛ جز این که فرسته در برچسبها یا خوراکهای زنده، کشف یا جستوجوی ماستودون ظاهر نخواهد شد. حتا اگر کلیّت نمایهتان اجازه داده باشد.",
|
||||
"privacy.unlisted.long": "نهفته از نتیجههای جستوجوی ماستودون و خطهای زمانی داغ و عمومی",
|
||||
"privacy.unlisted.short": "عمومی ساکت",
|
||||
"privacy_policy.last_updated": "آخرین بهروز رسانی در {date}",
|
||||
"privacy_policy.title": "سیاست محرمانگی",
|
||||
"quote_error.edit": "هنگام ویراستن فرسته نمیتوان نقلی افزود.",
|
||||
"quote_error.poll": "نقل نظرسنجیها مجاز نیست.",
|
||||
"quote_error.private_mentions": "نقل اشارههای مستقیم مجاز نیست.",
|
||||
"quote_error.quote": "در هر زمان تنها یک نقل مجاز است.",
|
||||
"quote_error.unauthorized": "مجاز به نقل این فرسته نیستید.",
|
||||
"quote_error.upload": "نقل پیوستهای رسانهای مجاز نیست.",
|
||||
"recommended": "پیشنهادشده",
|
||||
"refresh": "نوسازی",
|
||||
"regeneration_indicator.please_stand_by": "لطفا منتظر باشید.",
|
||||
"regeneration_indicator.preparing_your_home_feed": "در حال آماده کردن خوراک خانگی شما…",
|
||||
"regeneration_indicator.preparing_your_home_feed": "آماده کردن خوراک خانگیتان…",
|
||||
"relative_time.days": "{number} روز",
|
||||
"relative_time.full.days": "{number, plural, one {# روز} other {# روز}} پیش",
|
||||
"relative_time.full.hours": "{number, plural, one {# ساعت} other {# ساعت}} پیش",
|
||||
@@ -744,6 +783,9 @@
|
||||
"relative_time.minutes": "{number} دقیقه",
|
||||
"relative_time.seconds": "{number} ثانیه",
|
||||
"relative_time.today": "امروز",
|
||||
"remove_quote_hint.button_label": "گرفتم",
|
||||
"remove_quote_hint.message": "میتوانید این کار را از {icon} فهرست گزینهها انجام دهید.",
|
||||
"remove_quote_hint.title": "میخواهید فرستهٔ نقل شدهتان را بردارید؟",
|
||||
"reply_indicator.attachments": "{count, plural, one {# پیوست} other {# پیوست}}",
|
||||
"reply_indicator.cancel": "لغو",
|
||||
"reply_indicator.poll": "نظرسنجی",
|
||||
@@ -780,9 +822,9 @@
|
||||
"report.statuses.subtitle": "همهٔ موارد انجام شده را برگزینید",
|
||||
"report.statuses.title": "آیا فرستهای وجود دارد که از این گزارش پشتیبانی کند؟",
|
||||
"report.submit": "فرستادن",
|
||||
"report.target": "در حال گزارش {target}",
|
||||
"report.target": "گزارش کردن {target}",
|
||||
"report.thanks.take_action": "در اینجا گزینههایی برای کنترل آنچه در ماستودون میبینید، وجود دارد:",
|
||||
"report.thanks.take_action_actionable": "در حالی که ما این مورد را بررسی میکنیم، میتوانید علیه @{name} اقدام کنید:",
|
||||
"report.thanks.take_action_actionable": "تا بررسیش میکنیم میتوانید علیه @{name} اقدام کنید:",
|
||||
"report.thanks.title": "نمیخواهید این را ببینید؟",
|
||||
"report.thanks.title_actionable": "ممنون بابت گزارش، ما آن را بررسی خواهیم کرد.",
|
||||
"report.unfollow": "پینگرفتن @{name}",
|
||||
@@ -835,13 +877,23 @@
|
||||
"status.admin_account": "گشودن واسط مدیریت برای @{name}",
|
||||
"status.admin_domain": "گشودن واسط مدیریت برای {domain}",
|
||||
"status.admin_status": "گشودن این فرسته در واسط مدیریت",
|
||||
"status.all_disabled": "تقویتها و نقلها از کار افتادهاند",
|
||||
"status.block": "انسداد @{name}",
|
||||
"status.bookmark": "نشانک",
|
||||
"status.cancel_reblog_private": "ناتقویت",
|
||||
"status.cannot_quote": "مجاز به نقل این فرسته نیستید",
|
||||
"status.cannot_reblog": "این فرسته قابل تقویت نیست",
|
||||
"status.contains_quote": "دارای نقل",
|
||||
"status.context.loading": "بار کردن پاسخهای بیشتر",
|
||||
"status.context.loading_error": "نتوانست پاسخهای بیشتری بار کند",
|
||||
"status.context.loading_success": "پاسخهای جدید بار شدند",
|
||||
"status.context.more_replies_found": "پاسخهای بیشتری پیدا شد",
|
||||
"status.context.retry": "تلاش دوباره",
|
||||
"status.context.show": "نمایش",
|
||||
"status.continued_thread": "رشتهٔ دنباله دار",
|
||||
"status.copy": "رونوشت از پیوند فرسته",
|
||||
"status.delete": "حذف",
|
||||
"status.delete.success": "فرسته حذف شد",
|
||||
"status.detailed_status": "نمایش کامل گفتگو",
|
||||
"status.direct": "اشارهٔ خصوصی به @{name}",
|
||||
"status.direct_indicator": "اشارهٔ خصوصی",
|
||||
@@ -866,24 +918,43 @@
|
||||
"status.pin": "سنجاق به نمایه",
|
||||
"status.quote": "نقلقول",
|
||||
"status.quote.cancel": "لغو نقل",
|
||||
"status.quote_error.blocked_account_hint.title": "این فرسته نهفته است چرا که @{name} را مسدود کردهاید.",
|
||||
"status.quote_error.blocked_domain_hint.title": "این فرسته نهفته است چرا که {domain} را مسدود کردهاید.",
|
||||
"status.quote_error.filtered": "نهفته بنا بر یکی از پالایههایتان",
|
||||
"status.quote_error.limited_account_hint.action": "نمایش به هر روی",
|
||||
"status.quote_error.limited_account_hint.title": "این حساب به دست ناظمهای {domain} پنهان شده.",
|
||||
"status.quote_error.muted_account_hint.title": "این فرسته نهفته است چرا که @{name} را خموش کردهاید.",
|
||||
"status.quote_error.not_available": "فرسته در دسترس نیست",
|
||||
"status.quote_error.pending_approval": "فرسته منتظر",
|
||||
"status.quote_error.pending_approval_popout.body": "روی ماستودون میتوان افراد مجاز به نقل را واپایید. این فرسته تا زمان گرفتن تأییده از نگارندهٔ اصلی معلّق است.",
|
||||
"status.quote_error.revoked": "فرسته به دست نگارنده برداشته شد",
|
||||
"status.quote_followers_only": "تنها پیگیران میتوانند این فرسته را نقل کنند",
|
||||
"status.quote_manual_review": "نگارنده به صورت دستی بررسی خواهد کرد",
|
||||
"status.quote_noun": "نقل",
|
||||
"status.quote_policy_change": "تغییر کسانی که میتوانند نقل کنند",
|
||||
"status.quote_post_author": "فرستهای از @{name} نقل شد",
|
||||
"status.quote_private": "فرستههای خصوصی نمیتوانند نقل شوند",
|
||||
"status.quotes": "{count, plural, one {نقل} other {نقل}}",
|
||||
"status.quotes.empty": "هنوز کسی این فرسته را نقل نکرده. وقتی کسی چنین کند اینجا نشان داده خواهد شد.",
|
||||
"status.quotes.local_other_disclaimer": "نقلهایی که به دست نگارنده رد شده باشند نشان داده نخواهند شد.",
|
||||
"status.quotes.remote_other_disclaimer": "تنها نقلها از {domain} تضمین نمایش در اینجا را دارند. نقلهای رد شده به دست نگاره نشان داده نخواهند شد.",
|
||||
"status.read_more": "بیشتر بخوانید",
|
||||
"status.reblog": "تقویت",
|
||||
"status.reblog_or_quote": "نقل یا تقویت",
|
||||
"status.reblog_private": "همرسانی دوباره با پیگیرانتان",
|
||||
"status.reblogged_by": "{name} تقویت کرد",
|
||||
"status.reblogs": "{count, plural, one {تقویت} other {تقویت}}",
|
||||
"status.reblogs.empty": "هنوز هیچ کسی این فرسته را تقویت نکرده است. وقتی کسی چنین کاری کند، اینجا نمایش داده خواهد شد.",
|
||||
"status.redraft": "حذف و بازنویسی",
|
||||
"status.remove_bookmark": "برداشتن نشانک",
|
||||
"status.remove_favourite": "حذف از موارد دلخواه",
|
||||
"status.remove_quote": "برداشتن",
|
||||
"status.replied_in_thread": "در رشته پاسخ داده",
|
||||
"status.replied_to": "به {name} پاسخ داد",
|
||||
"status.reply": "پاسخ",
|
||||
"status.replyAll": "پاسخ به رشته",
|
||||
"status.report": "گزارش @{name}",
|
||||
"status.request_quote": "درخواست نقل",
|
||||
"status.revoke_quote": "حذف فرستهام از فرسته @{name}",
|
||||
"status.sensitive_warning": "محتوای حساس",
|
||||
"status.share": "همرسانی",
|
||||
@@ -922,14 +993,15 @@
|
||||
"upload_button.label": "افزودن تصاویر، ویدیو یا یک پروندهٔ صوتی",
|
||||
"upload_error.limit": "از حد مجاز بارگذاری پرونده فراتر رفتید.",
|
||||
"upload_error.poll": "بارگذاری پرونده در نظرسنجیها مجاز نیست.",
|
||||
"upload_error.quote": "بارگذاری پرونده در نقلها محاز نیست.",
|
||||
"upload_form.drag_and_drop.instructions": "برای دریافت پیوست رسانه، space را فشار دهید یا وارد کنید. در حین کشیدن، از کلیدهای جهت دار برای حرکت دادن پیوست رسانه در هر جهت معین استفاده کنید. برای رها کردن ضمیمه رسانه در موقعیت جدید خود، مجدداً space یا enter را فشار دهید، یا برای لغو، escape را فشار دهید.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "کشیدن لغو شد. پیوست رسانه {item} حذف شد.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "پیوست رسانه {item} حذف شد.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "پیوست رسانه {item} منتقل شد.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "پیوست رسانه {item} برداشته شد.",
|
||||
"upload_form.edit": "ویرایش",
|
||||
"upload_progress.label": "در حال بارگذاری...",
|
||||
"upload_progress.processing": "در حال پردازش…",
|
||||
"upload_progress.label": "بار گذاشتن...",
|
||||
"upload_progress.processing": "پردازش کردن…",
|
||||
"username.taken": "این نام کاربری گرفته شده. نام دیگری امتحان کنید",
|
||||
"video.close": "بستن ویدیو",
|
||||
"video.download": "بارگیری پرونده",
|
||||
@@ -946,9 +1018,19 @@
|
||||
"video.volume_down": "کاهش حجم صدا",
|
||||
"video.volume_up": "افزایش حجم صدا",
|
||||
"visibility_modal.button_title": "تنظیم نمایانی",
|
||||
"visibility_modal.direct_quote_warning.text": "اگر تنظبمات کنونی را ذخیره کنید نقل تعبیه شده تبدیل به پیوند خواهد شد.",
|
||||
"visibility_modal.direct_quote_warning.title": "نقلها نمیتوانند در اشارههای خصوصی تعبیه شوند",
|
||||
"visibility_modal.header": "نمایانی و برهمکنش",
|
||||
"visibility_modal.helper.direct_quoting": "اشارههای خصوصی روی ماستودون نیمتوانند به دست دیگران نقل شوند.",
|
||||
"visibility_modal.helper.privacy_editing": "پس از انتشار فرسته نمیتوان نمایانی را تغییر داد.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "خودنقلی فرستههای خصوصی نمیتواند عمومی شود.",
|
||||
"visibility_modal.helper.private_quoting": "فرستهّای فقط پیگیران روی ماستودون نمیتوانند به دست دیگران نقل شوند.",
|
||||
"visibility_modal.helper.unlisted_quoting": "هنگامی که افراد نقلتان میکنند فرستهشان هم از خطزمانیهای داغ پنهان خواهد بود.",
|
||||
"visibility_modal.instructions": "واپایش کسانی که میتوانند با این فرسته تعامل داشته باشند. میتواند با رفتن به <link>ترجیحات > پیشگزیدهها فرستادن</link> تنظیمات را به همهٔ فرستههای آینده نیز اعمال کنید.",
|
||||
"visibility_modal.privacy_label": "نمایانی",
|
||||
"visibility_modal.quote_followers": "فقط پیگیرندگان",
|
||||
"visibility_modal.quote_label": "چهکسی میتواند نقل کند",
|
||||
"visibility_modal.quote_nobody": "فقط من",
|
||||
"visibility_modal.quote_public": "هرکسی",
|
||||
"visibility_modal.save": "ذخیره"
|
||||
}
|
||||
|
||||
@@ -247,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Julkaise silti",
|
||||
"confirmations.missing_alt_text.title": "Lisätäänkö vaihtoehtoinen teksti?",
|
||||
"confirmations.mute.confirm": "Mykistä",
|
||||
"confirmations.private_quote_notify.cancel": "Takaisin muokkaukseen",
|
||||
"confirmations.private_quote_notify.confirm": "Julkaise",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Älä näytä tätä viestiä uudelleen",
|
||||
"confirmations.private_quote_notify.message": "Lainaamasi käyttäjä ja muut mainitut saavat ilmoituksen ja voivat tarkastella julkaisuasi, vaikka he eivät seuraisi sinua.",
|
||||
"confirmations.private_quote_notify.title": "Jaetaanko seuraajien ja mainittujen käyttäjien kanssa?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Älä muistuta minua uudelleen",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Selvä",
|
||||
"confirmations.quiet_post_quote_info.message": "Kun lainaat vaivihkaa julkisia julkaisuja, oma julkaisusi piilotetaan suosittujen julkaisujen aikajanoilta.",
|
||||
@@ -759,6 +764,7 @@
|
||||
"privacy_policy.title": "Tietosuojakäytäntö",
|
||||
"quote_error.edit": "Lainauksia ei voi lisätä julkaisua muokattaessa.",
|
||||
"quote_error.poll": "Äänestysten lainaaminen ei ole sallittua.",
|
||||
"quote_error.private_mentions": "Lainaaminen ei ole sallittua yksityismaininnoissa.",
|
||||
"quote_error.quote": "Vain yksi lainaus kerrallaan on sallittu.",
|
||||
"quote_error.unauthorized": "Sinulla ei ole valtuuksia lainata tätä julkaisua.",
|
||||
"quote_error.upload": "Medialiitteiden lainaaminen ei ole sallittua.",
|
||||
@@ -1012,6 +1018,8 @@
|
||||
"video.volume_down": "Vähennä äänenvoimakkuutta",
|
||||
"video.volume_up": "Lisää äänenvoimakkuutta",
|
||||
"visibility_modal.button_title": "Aseta näkyvyys",
|
||||
"visibility_modal.direct_quote_warning.text": "Jos tallennat nykyiset asetukset, upotettu lainaus muunnetaan linkiksi.",
|
||||
"visibility_modal.direct_quote_warning.title": "Lainauksia ei voi upottaa yksityismainintoihin",
|
||||
"visibility_modal.header": "Näkyvyys ja vuorovaikutus",
|
||||
"visibility_modal.helper.direct_quoting": "Muut eivät voi lainata Mastodonissa kirjoitettuja yksityismainintoja.",
|
||||
"visibility_modal.helper.privacy_editing": "Näkyvyyttä ei voi muuttaa julkaisun jälkeen.",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Einans lokalt",
|
||||
"community.column_settings.media_only": "Einans miðlar",
|
||||
"community.column_settings.remote_only": "Einans útifrá",
|
||||
"compose.error.blank_post": "Postar kunnu ikki vera blankir.",
|
||||
"compose.language.change": "Skift mál",
|
||||
"compose.language.search": "Leita eftir málum...",
|
||||
"compose.published.body": "Postur útgivin.",
|
||||
@@ -246,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Posta allíkavæl",
|
||||
"confirmations.missing_alt_text.title": "Legg alternativan tekst afturat?",
|
||||
"confirmations.mute.confirm": "Doyv",
|
||||
"confirmations.private_quote_notify.cancel": "Aftur til rættingar",
|
||||
"confirmations.private_quote_notify.confirm": "Útgev post",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Ikki vísa hesi boðini aftur",
|
||||
"confirmations.private_quote_notify.message": "Persónurin, sum tú siterar, og onnur, sum eru nevnd, verða kunnaði og kunnu síggja postin hjá tær, sjálvt um tey ikki fylgja tær.",
|
||||
"confirmations.private_quote_notify.title": "Deil við fylgjarum og nevndum brúkarum?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Ikki minna meg á tað aftur",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Eg skilji",
|
||||
"confirmations.quiet_post_quote_info.message": "Tá tú siterar ein stillan almennan post, verður posturin hjá tær fjaldur frá tíðarlinjum, ið vísa vælumtóktar postar.",
|
||||
@@ -758,6 +764,7 @@
|
||||
"privacy_policy.title": "Privatlívspolitikkur",
|
||||
"quote_error.edit": "Sitatir kunnu ikki leggjast afturat tá tú rættar ein post.",
|
||||
"quote_error.poll": "Tað er ikki loyvt at sitera spurnarkanningar.",
|
||||
"quote_error.private_mentions": "Sitering er ikki loyvd við beinleiðis umrøðum.",
|
||||
"quote_error.quote": "Bara ein sitering er loyvd í senn.",
|
||||
"quote_error.unauthorized": "Tú hevur ikki rættindi at sitera hendan postin.",
|
||||
"quote_error.upload": "Sitering er ikki loyvd um miðlar eru viðheftir.",
|
||||
@@ -1011,6 +1018,8 @@
|
||||
"video.volume_down": "Minka ljóðstyrki",
|
||||
"video.volume_up": "Øk um ljóðstyrki",
|
||||
"visibility_modal.button_title": "Set sýni",
|
||||
"visibility_modal.direct_quote_warning.text": "Um tú goymir verandi stillingar, verður innskotna sitatið umskapað til eitt leinki.",
|
||||
"visibility_modal.direct_quote_warning.title": "Sitatir kunnu ikki innskjótast í privatar umrøður",
|
||||
"visibility_modal.header": "Sýni og samvirkni",
|
||||
"visibility_modal.helper.direct_quoting": "Privatar umrøður, sum eru skrivaðar á Mastodon, kunnu ikki siterast av øðrum.",
|
||||
"visibility_modal.helper.privacy_editing": "Sýni kann ikki broytast eftir, at ein postur er útgivin.",
|
||||
|
||||
@@ -21,29 +21,35 @@
|
||||
"account.block_domain": "Bloquer le domaine {domain}",
|
||||
"account.block_short": "Bloquer",
|
||||
"account.blocked": "Bloqué·e",
|
||||
"account.blocking": "Bloquer",
|
||||
"account.blocking": "Bloqué",
|
||||
"account.cancel_follow_request": "Retirer cette demande d'abonnement",
|
||||
"account.copy": "Copier le lien vers le profil",
|
||||
"account.direct": "Mention privée @{name}",
|
||||
"account.disable_notifications": "Ne plus me notifier quand @{name} publie",
|
||||
"account.domain_blocking": "Bloquer domaine",
|
||||
"account.domain_blocking": "Domaine bloqué",
|
||||
"account.edit_profile": "Modifier le profil",
|
||||
"account.edit_profile_short": "Modifier",
|
||||
"account.enable_notifications": "Me notifier quand @{name} publie",
|
||||
"account.endorse": "Inclure sur profil",
|
||||
"account.familiar_followers_many": "Suivi par {name1},{name2}, et {othersCount, plural,one {une personne connue} other {# autres personnel connues}}",
|
||||
"account.familiar_followers_many": "Suivi par {name1}, {name2}, et {othersCount, plural, one {une autre personne que vous suivez} other {# autres personnes que vous suivez}}",
|
||||
"account.familiar_followers_one": "Suivi par {name1}",
|
||||
"account.familiar_followers_two": "Suivi par {name1} et {name2}",
|
||||
"account.featured": "En vedette",
|
||||
"account.featured.accounts": "Profiles",
|
||||
"account.featured.accounts": "Profils",
|
||||
"account.featured.hashtags": "Hashtags",
|
||||
"account.featured_tags.last_status_at": "Dernière publication {date}",
|
||||
"account.featured_tags.last_status_never": "Aucune publication",
|
||||
"account.follow": "Suivre",
|
||||
"account.follow_back": "Suivre en retour",
|
||||
"account.follow_back_short": "Suivre en retour",
|
||||
"account.follow_request": "Demande d’abonnement",
|
||||
"account.follow_request_cancel": "Annuler la demande",
|
||||
"account.follow_request_cancel_short": "Annuler",
|
||||
"account.follow_request_short": "Requête",
|
||||
"account.followers": "abonné·e·s",
|
||||
"account.followers.empty": "Personne ne suit ce compte pour l'instant.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
|
||||
"account.followers_you_know_counter": "Vous connaissez {counter}",
|
||||
"account.followers_you_know_counter": "{count, plural, one {{counter} suivi}, other {{counter} suivis}}",
|
||||
"account.following": "Abonné·e",
|
||||
"account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}",
|
||||
"account.follows.empty": "Ce compte ne suit personne présentement.",
|
||||
@@ -68,10 +74,10 @@
|
||||
"account.open_original_page": "Ouvrir la page d'origine",
|
||||
"account.posts": "Publications",
|
||||
"account.posts_with_replies": "Publications et réponses",
|
||||
"account.remove_from_followers": "Retirer {name} des suiveurs",
|
||||
"account.remove_from_followers": "Retirer {name} des abonnés",
|
||||
"account.report": "Signaler @{name}",
|
||||
"account.requested_follow": "{name} a demandé à vous suivre",
|
||||
"account.requests_to_follow_you": "Demande a vous suivre",
|
||||
"account.requests_to_follow_you": "Demande à vous suivre",
|
||||
"account.share": "Partager le profil de @{name}",
|
||||
"account.show_reblogs": "Afficher les boosts de @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
@@ -100,7 +106,7 @@
|
||||
"alert.unexpected.title": "Oups!",
|
||||
"alt_text_badge.title": "Texte alternatif",
|
||||
"alt_text_modal.add_alt_text": "Ajouter un texte alternatif",
|
||||
"alt_text_modal.add_text_from_image": "Ajouter le texte provenant de l'image",
|
||||
"alt_text_modal.add_text_from_image": "Extraire le texte de l'image",
|
||||
"alt_text_modal.cancel": "Annuler",
|
||||
"alt_text_modal.change_thumbnail": "Changer la vignette",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Décrire pour les personnes ayant des difficultés d’audition…",
|
||||
@@ -167,6 +173,8 @@
|
||||
"column.edit_list": "Modifier la liste",
|
||||
"column.favourites": "Favoris",
|
||||
"column.firehose": "Flux en direct",
|
||||
"column.firehose_local": "Flux en direct pour ce serveur",
|
||||
"column.firehose_singular": "Flux en direct",
|
||||
"column.follow_requests": "Demande d'abonnement",
|
||||
"column.home": "Accueil",
|
||||
"column.list_members": "Gérer les membres de la liste",
|
||||
@@ -186,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Local seulement",
|
||||
"community.column_settings.media_only": "Média seulement",
|
||||
"community.column_settings.remote_only": "À distance seulement",
|
||||
"compose.error.blank_post": "Le message ne peut être laissé vide.",
|
||||
"compose.language.change": "Changer de langue",
|
||||
"compose.language.search": "Rechercher des langues…",
|
||||
"compose.published.body": "Publiée.",
|
||||
@@ -238,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Publier quand-même",
|
||||
"confirmations.missing_alt_text.title": "Ajouter un texte alternatif?",
|
||||
"confirmations.mute.confirm": "Masquer",
|
||||
"confirmations.private_quote_notify.cancel": "Retour à l'édition",
|
||||
"confirmations.private_quote_notify.confirm": "Publier",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Ne plus afficher ce message",
|
||||
"confirmations.private_quote_notify.message": "La personne citée et celles mentionnées seront notifiées et pourront voir le message, même si elles ne vous suivent pas.",
|
||||
"confirmations.private_quote_notify.title": "Partager avec les personnes abonnées et mentionnées ?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Ne plus me rappeler",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Compris",
|
||||
"confirmations.quiet_post_quote_info.message": "Lorsque vous citez un message public silencieux, votre message sera caché des fils tendances.",
|
||||
@@ -327,6 +341,7 @@
|
||||
"empty_column.bookmarked_statuses": "Vous n'avez pas de publications parmi vos signets. Lorsque vous en ajouterez une, elle apparaîtra ici.",
|
||||
"empty_column.community": "Le fil local est vide. Écrivez donc quelque chose pour le remplir!",
|
||||
"empty_column.direct": "Vous n'avez pas encore de mentions privées. Quand vous en envoyez ou en recevez, elles apparaîtront ici.",
|
||||
"empty_column.disabled_feed": "Ce flux a été désactivé par les administrateur·rice·s de votre serveur.",
|
||||
"empty_column.domain_blocks": "Il n’y a aucun domaine bloqué pour le moment.",
|
||||
"empty_column.explore_statuses": "Rien n'est en tendance présentement. Revenez plus tard!",
|
||||
"empty_column.favourited_statuses": "Vous n’avez pas encore de publications favorites. Lorsque vous en ajouterez une, elle apparaîtra ici.",
|
||||
@@ -747,7 +762,9 @@
|
||||
"privacy.unlisted.short": "Public discret",
|
||||
"privacy_policy.last_updated": "Dernière mise à jour {date}",
|
||||
"privacy_policy.title": "Politique de confidentialité",
|
||||
"quote_error.edit": "Les citations ne peuvent pas être ajoutés lors de l'édition d'un message.",
|
||||
"quote_error.poll": "Les citations ne sont pas autorisées avec les sondages.",
|
||||
"quote_error.private_mentions": "La citation n'est pas autorisée avec les mentions privées.",
|
||||
"quote_error.quote": "Une seule citation à la fois est autorisée.",
|
||||
"quote_error.unauthorized": "Vous n'êtes pas autorisé⋅e à citer cette publication.",
|
||||
"quote_error.upload": "La citation n'est pas autorisée avec un média joint.",
|
||||
@@ -869,6 +886,7 @@
|
||||
"status.contains_quote": "Contient la citation",
|
||||
"status.context.loading": "Chargement de réponses supplémentaires",
|
||||
"status.context.loading_error": "Impossible de charger les nouvelles réponses",
|
||||
"status.context.loading_success": "Nouvelles réponses ont été chargées",
|
||||
"status.context.more_replies_found": "Plus de réponses trouvées",
|
||||
"status.context.retry": "Réessayer",
|
||||
"status.context.show": "Montrer",
|
||||
@@ -900,9 +918,12 @@
|
||||
"status.pin": "Épingler sur profil",
|
||||
"status.quote": "Citer",
|
||||
"status.quote.cancel": "Annuler la citation",
|
||||
"status.quote_error.blocked_account_hint.title": "Ce message est masqué car vous avez bloqué @{name}.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Ce message est masqué car vous avez bloqué {domain}.",
|
||||
"status.quote_error.filtered": "Caché en raison de l'un de vos filtres",
|
||||
"status.quote_error.limited_account_hint.action": "Afficher quand même",
|
||||
"status.quote_error.limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.",
|
||||
"status.quote_error.muted_account_hint.title": "Ce message est masqué car vous avez Mis en sourdine @{name}.",
|
||||
"status.quote_error.not_available": "Publication non disponible",
|
||||
"status.quote_error.pending_approval": "Publication en attente",
|
||||
"status.quote_error.pending_approval_popout.body": "Sur Mastodon, vous pouvez contrôler si quelqu'un peut vous citer. Ce message est en attente pendant que nous recevons l'approbation de l'auteur original.",
|
||||
@@ -997,6 +1018,8 @@
|
||||
"video.volume_down": "Baisser le volume",
|
||||
"video.volume_up": "Augmenter le volume",
|
||||
"visibility_modal.button_title": "Définir la visibilité",
|
||||
"visibility_modal.direct_quote_warning.text": "Si vous enregistrez les paramètres actuels, la citation sera convertie en lien.",
|
||||
"visibility_modal.direct_quote_warning.title": "Les citations ne peuvent pas être intégrées dans les mentions privées",
|
||||
"visibility_modal.header": "Visibilité et interactions",
|
||||
"visibility_modal.helper.direct_quoting": "Les mentions privées rédigées sur Mastodon ne peuvent pas être citées par d'autres personnes.",
|
||||
"visibility_modal.helper.privacy_editing": "La visibilité ne peut pas être modifiée après la publication d'un message.",
|
||||
|
||||
@@ -21,29 +21,35 @@
|
||||
"account.block_domain": "Bloquer le domaine {domain}",
|
||||
"account.block_short": "Bloquer",
|
||||
"account.blocked": "Bloqué·e",
|
||||
"account.blocking": "Bloquer",
|
||||
"account.blocking": "Bloqué",
|
||||
"account.cancel_follow_request": "Annuler l'abonnement",
|
||||
"account.copy": "Copier le lien vers le profil",
|
||||
"account.direct": "Mention privée @{name}",
|
||||
"account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose",
|
||||
"account.domain_blocking": "Bloquer domaine",
|
||||
"account.domain_blocking": "Domaine bloqué",
|
||||
"account.edit_profile": "Modifier le profil",
|
||||
"account.edit_profile_short": "Modifier",
|
||||
"account.enable_notifications": "Me notifier quand @{name} publie quelque chose",
|
||||
"account.endorse": "Recommander sur votre profil",
|
||||
"account.familiar_followers_many": "Suivi par {name1},{name2}, et {othersCount, plural,one {une personne connue} other {# autres personnel connues}}",
|
||||
"account.familiar_followers_many": "Suivi par {name1}, {name2}, et {othersCount, plural, one {une autre personne que vous suivez} other {# autres personnes que vous suivez}}",
|
||||
"account.familiar_followers_one": "Suivi par {name1}",
|
||||
"account.familiar_followers_two": "Suivi par {name1} et {name2}",
|
||||
"account.featured": "En vedette",
|
||||
"account.featured.accounts": "Profiles",
|
||||
"account.featured.accounts": "Profils",
|
||||
"account.featured.hashtags": "Hashtags",
|
||||
"account.featured_tags.last_status_at": "Dernier message le {date}",
|
||||
"account.featured_tags.last_status_never": "Aucun message",
|
||||
"account.follow": "Suivre",
|
||||
"account.follow_back": "Suivre en retour",
|
||||
"account.follow_back_short": "Suivre en retour",
|
||||
"account.follow_request": "Demande d’abonnement",
|
||||
"account.follow_request_cancel": "Annuler la demande",
|
||||
"account.follow_request_cancel_short": "Annuler",
|
||||
"account.follow_request_short": "Requête",
|
||||
"account.followers": "Abonné·e·s",
|
||||
"account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
|
||||
"account.followers_you_know_counter": "Vous connaissez {counter}",
|
||||
"account.followers_you_know_counter": "{count, plural, one {{counter} suivi}, other {{counter} suivis}}",
|
||||
"account.following": "Abonnements",
|
||||
"account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}",
|
||||
"account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour l’instant.",
|
||||
@@ -68,10 +74,10 @@
|
||||
"account.open_original_page": "Ouvrir la page d'origine",
|
||||
"account.posts": "Messages",
|
||||
"account.posts_with_replies": "Messages et réponses",
|
||||
"account.remove_from_followers": "Retirer {name} des suiveurs",
|
||||
"account.remove_from_followers": "Retirer {name} des abonnés",
|
||||
"account.report": "Signaler @{name}",
|
||||
"account.requested_follow": "{name} a demandé à vous suivre",
|
||||
"account.requests_to_follow_you": "Demande a vous suivre",
|
||||
"account.requests_to_follow_you": "Demande à vous suivre",
|
||||
"account.share": "Partager le profil de @{name}",
|
||||
"account.show_reblogs": "Afficher les partages de @{name}",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
@@ -100,7 +106,7 @@
|
||||
"alert.unexpected.title": "Oups !",
|
||||
"alt_text_badge.title": "Texte alternatif",
|
||||
"alt_text_modal.add_alt_text": "Ajouter un texte alternatif",
|
||||
"alt_text_modal.add_text_from_image": "Ajouter le texte provenant de l'image",
|
||||
"alt_text_modal.add_text_from_image": "Extraire le texte de l'image",
|
||||
"alt_text_modal.cancel": "Annuler",
|
||||
"alt_text_modal.change_thumbnail": "Changer la vignette",
|
||||
"alt_text_modal.describe_for_people_with_hearing_impairments": "Décrire pour les personnes ayant des difficultés d’audition…",
|
||||
@@ -167,6 +173,8 @@
|
||||
"column.edit_list": "Modifier la liste",
|
||||
"column.favourites": "Favoris",
|
||||
"column.firehose": "Flux en direct",
|
||||
"column.firehose_local": "Flux en direct pour ce serveur",
|
||||
"column.firehose_singular": "Flux en direct",
|
||||
"column.follow_requests": "Demandes d'abonnement",
|
||||
"column.home": "Accueil",
|
||||
"column.list_members": "Gérer les membres de la liste",
|
||||
@@ -186,6 +194,7 @@
|
||||
"community.column_settings.local_only": "Local seulement",
|
||||
"community.column_settings.media_only": "Média uniquement",
|
||||
"community.column_settings.remote_only": "Distant seulement",
|
||||
"compose.error.blank_post": "Le message ne peut être laissé vide.",
|
||||
"compose.language.change": "Changer de langue",
|
||||
"compose.language.search": "Rechercher des langues...",
|
||||
"compose.published.body": "Message Publié.",
|
||||
@@ -238,6 +247,11 @@
|
||||
"confirmations.missing_alt_text.secondary": "Publier quand-même",
|
||||
"confirmations.missing_alt_text.title": "Ajouter un texte alternatif?",
|
||||
"confirmations.mute.confirm": "Masquer",
|
||||
"confirmations.private_quote_notify.cancel": "Retour à l'édition",
|
||||
"confirmations.private_quote_notify.confirm": "Publier",
|
||||
"confirmations.private_quote_notify.do_not_show_again": "Ne plus afficher ce message",
|
||||
"confirmations.private_quote_notify.message": "La personne citée et celles mentionnées seront notifiées et pourront voir le message, même si elles ne vous suivent pas.",
|
||||
"confirmations.private_quote_notify.title": "Partager avec les personnes abonnées et mentionnées ?",
|
||||
"confirmations.quiet_post_quote_info.dismiss": "Ne plus me rappeler",
|
||||
"confirmations.quiet_post_quote_info.got_it": "Compris",
|
||||
"confirmations.quiet_post_quote_info.message": "Lorsque vous citez un message public silencieux, votre message sera caché des fils tendances.",
|
||||
@@ -327,6 +341,7 @@
|
||||
"empty_column.bookmarked_statuses": "Vous n'avez pas de message en marque-page. Lorsque vous en ajouterez un, il apparaîtra ici.",
|
||||
"empty_column.community": "Le fil public local est vide. Écrivez donc quelque chose pour le remplir !",
|
||||
"empty_column.direct": "Vous n'avez pas encore de mentions privées. Quand vous en enverrez ou recevrez, elles apparaîtront ici.",
|
||||
"empty_column.disabled_feed": "Ce flux a été désactivé par les administrateur·rice·s de votre serveur.",
|
||||
"empty_column.domain_blocks": "Il n’y a aucun domaine bloqué pour le moment.",
|
||||
"empty_column.explore_statuses": "Rien n'est en tendance pour le moment. Revenez plus tard !",
|
||||
"empty_column.favourited_statuses": "Vous n’avez pas encore de message en favori. Lorsque vous en ajouterez un, il apparaîtra ici.",
|
||||
@@ -747,7 +762,9 @@
|
||||
"privacy.unlisted.short": "Public discret",
|
||||
"privacy_policy.last_updated": "Dernière mise à jour {date}",
|
||||
"privacy_policy.title": "Politique de confidentialité",
|
||||
"quote_error.edit": "Les citations ne peuvent pas être ajoutés lors de l'édition d'un message.",
|
||||
"quote_error.poll": "Les citations ne sont pas autorisées avec les sondages.",
|
||||
"quote_error.private_mentions": "La citation n'est pas autorisée avec les mentions privées.",
|
||||
"quote_error.quote": "Une seule citation à la fois est autorisée.",
|
||||
"quote_error.unauthorized": "Vous n'êtes pas autorisé⋅e à citer cette publication.",
|
||||
"quote_error.upload": "La citation n'est pas autorisée avec un média joint.",
|
||||
@@ -869,6 +886,7 @@
|
||||
"status.contains_quote": "Contient la citation",
|
||||
"status.context.loading": "Chargement de réponses supplémentaires",
|
||||
"status.context.loading_error": "Impossible de charger les nouvelles réponses",
|
||||
"status.context.loading_success": "Nouvelles réponses ont été chargées",
|
||||
"status.context.more_replies_found": "Plus de réponses trouvées",
|
||||
"status.context.retry": "Réessayer",
|
||||
"status.context.show": "Montrer",
|
||||
@@ -900,9 +918,12 @@
|
||||
"status.pin": "Épingler sur le profil",
|
||||
"status.quote": "Citer",
|
||||
"status.quote.cancel": "Annuler la citation",
|
||||
"status.quote_error.blocked_account_hint.title": "Ce message est masqué car vous avez bloqué @{name}.",
|
||||
"status.quote_error.blocked_domain_hint.title": "Ce message est masqué car vous avez bloqué {domain}.",
|
||||
"status.quote_error.filtered": "Caché en raison de l'un de vos filtres",
|
||||
"status.quote_error.limited_account_hint.action": "Afficher quand même",
|
||||
"status.quote_error.limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.",
|
||||
"status.quote_error.muted_account_hint.title": "Ce message est masqué car vous avez Mis en sourdine @{name}.",
|
||||
"status.quote_error.not_available": "Publication non disponible",
|
||||
"status.quote_error.pending_approval": "Publication en attente",
|
||||
"status.quote_error.pending_approval_popout.body": "Sur Mastodon, vous pouvez contrôler si quelqu'un peut vous citer. Ce message est en attente pendant que nous recevons l'approbation de l'auteur original.",
|
||||
@@ -997,6 +1018,8 @@
|
||||
"video.volume_down": "Baisser le volume",
|
||||
"video.volume_up": "Augmenter le volume",
|
||||
"visibility_modal.button_title": "Définir la visibilité",
|
||||
"visibility_modal.direct_quote_warning.text": "Si vous enregistrez les paramètres actuels, la citation sera convertie en lien.",
|
||||
"visibility_modal.direct_quote_warning.title": "Les citations ne peuvent pas être intégrées dans les mentions privées",
|
||||
"visibility_modal.header": "Visibilité et interactions",
|
||||
"visibility_modal.helper.direct_quoting": "Les mentions privées rédigées sur Mastodon ne peuvent pas être citées par d'autres personnes.",
|
||||
"visibility_modal.helper.privacy_editing": "La visibilité ne peut pas être modifiée après la publication d'un message.",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user