Merge commit '079d681ac6f279f3a7a6be5c3734549f3be8f912' into glitch-soc/merge-upstream

Conflicts:
- `app/javascript/material-icons/400-24px/code.svg`:
  Icon newly-introduced upstream.
  Used upstream's more recent version.
- `app/javascript/material-icons/400-24px/mail.svg`:
  Icon newly-introduced upstream.
  Used upstream's more recent version.
- `app/javascript/material-icons/400-24px/manufacturing.svg`:
  Icon newly-introduced upstream.
  Used upstream's more recent version.
- `app/views/admin/settings/shared/_links.html.haml`:
  Glitch-soc had extra menu items, ported the changes for them too.
- `config/navigation.rb`:
  Glitch-soc had extra menu items, ported the changes for them too.
This commit is contained in:
Claire committed 2024-08-08 19:30:16 +02:00
commit dd2a991e24
83 files changed
+476 -277

No files matched your search

@@ -0,0 +1,185 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import { supportsPassiveEvents } from 'detect-passive-events';
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
import type { IconProp } from './icon';
import { Icon } from './icon';
const listenerOptions = supportsPassiveEvents
? { passive: true, capture: true }
: true;
interface SelectItem {
value: string;
icon?: string;
iconComponent?: IconProp;
text: string;
meta: string;
extra?: string;
}
interface Props {
value: string;
classNamePrefix: string;
style?: React.CSSProperties;
items: SelectItem[];
onChange: (value: string) => void;
onClose: () => void;
}
export const DropdownSelector: React.FC<Props> = ({
style,
items,
value,
classNamePrefix = 'privacy-dropdown',
onClose,
onChange,
}) => {
const nodeRef = useRef<HTMLUListElement>(null);
const focusedItemRef = useRef<HTMLLIElement>(null);
const [currentValue, setCurrentValue] = useState(value);
const handleDocumentClick = useCallback(
(e: MouseEvent | TouchEvent) => {
if (
nodeRef.current &&
e.target instanceof Node &&
!nodeRef.current.contains(e.target)
) {
onClose();
e.stopPropagation();
}
},
[nodeRef, onClose],
);
const handleClick = useCallback(
(
e: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
) => {
const value = e.currentTarget.getAttribute('data-index');
e.preventDefault();
onClose();
if (value) onChange(value);
},
[onClose, onChange],
);
const handleKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLLIElement>) => {
const value = e.currentTarget.getAttribute('data-index');
const index = items.findIndex((item) => item.value === value);
let element: Element | null | undefined = null;
switch (e.key) {
case 'Escape':
onClose();
break;
case ' ':
case 'Enter':
handleClick(e);
break;
case 'ArrowDown':
element =
nodeRef.current?.children[index + 1] ??
nodeRef.current?.firstElementChild;
break;
case 'ArrowUp':
element =
nodeRef.current?.children[index - 1] ??
nodeRef.current?.lastElementChild;
break;
case 'Tab':
if (e.shiftKey) {
element =
nodeRef.current?.children[index + 1] ??
nodeRef.current?.firstElementChild;
} else {
element =
nodeRef.current?.children[index - 1] ??
nodeRef.current?.lastElementChild;
}
break;
case 'Home':
element = nodeRef.current?.firstElementChild;
break;
case 'End':
element = nodeRef.current?.lastElementChild;
break;
}
if (element && element instanceof HTMLElement) {
const selectedValue = element.getAttribute('data-index');
element.focus();
if (selectedValue) setCurrentValue(selectedValue);
e.preventDefault();
e.stopPropagation();
}
},
[nodeRef, items, onClose, handleClick, setCurrentValue],
);
useEffect(() => {
document.addEventListener('click', handleDocumentClick, { capture: true });
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
focusedItemRef.current?.focus({ preventScroll: true });
return () => {
document.removeEventListener('click', handleDocumentClick, {
capture: true,
});
document.removeEventListener(
'touchend',
handleDocumentClick,
listenerOptions,
);
};
}, [handleDocumentClick]);
return (
<ul style={style} role='listbox' ref={nodeRef}>
{items.map((item) => (
<li
role='option'
tabIndex={0}
key={item.value}
data-index={item.value}
onKeyDown={handleKeyDown}
onClick={handleClick}
className={classNames(`${classNamePrefix}__option`, {
active: item.value === currentValue,
})}
aria-selected={item.value === currentValue}
ref={item.value === currentValue ? focusedItemRef : null}
>
{item.icon && item.iconComponent && (
<div className={`${classNamePrefix}__option__icon`}>
<Icon id={item.icon} icon={item.iconComponent} />
</div>
)}
<div className={`${classNamePrefix}__option__content`}>
<strong>{item.text}</strong>
{item.meta}
</div>
{item.extra && (
<div
className={`${classNamePrefix}__option__additional`}
title={item.extra}
>
<Icon id='info-circle' icon={InfoIcon} />
</div>
)}
</li>
))}
</ul>
);
};
@@ -11,10 +11,9 @@ import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?re
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
import { DropdownSelector } from 'mastodon/components/dropdown_selector';
import { Icon } from 'mastodon/components/icon';
import { PrivacyDropdownMenu } from './privacy_dropdown_menu';
const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
public_long: { id: 'privacy.public.long', defaultMessage: 'Anyone on and off Mastodon' },
@@ -143,7 +142,7 @@ class PrivacyDropdown extends PureComponent {
{({ props, placement }) => (
<div {...props}>
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
<PrivacyDropdownMenu
<DropdownSelector
items={this.options}
value={value}
onClose={this.handleClose}
@@ -1,128 +0,0 @@
import PropTypes from 'prop-types';
import { useCallback, useEffect, useRef, useState } from 'react';
import classNames from 'classnames';
import { supportsPassiveEvents } from 'detect-passive-events';
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
import { Icon } from 'mastodon/components/icon';
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
export const PrivacyDropdownMenu = ({ style, items, value, onClose, onChange }) => {
const nodeRef = useRef(null);
const focusedItemRef = useRef(null);
const [currentValue, setCurrentValue] = useState(value);
const handleDocumentClick = useCallback((e) => {
if (nodeRef.current && !nodeRef.current.contains(e.target)) {
onClose();
e.stopPropagation();
}
}, [nodeRef, onClose]);
const handleClick = useCallback((e) => {
const value = e.currentTarget.getAttribute('data-index');
e.preventDefault();
onClose();
onChange(value);
}, [onClose, onChange]);
const handleKeyDown = useCallback((e) => {
const value = e.currentTarget.getAttribute('data-index');
const index = items.findIndex(item => (item.value === value));
let element = null;
switch (e.key) {
case 'Escape':
onClose();
break;
case ' ':
case 'Enter':
handleClick(e);
break;
case 'ArrowDown':
element = nodeRef.current.childNodes[index + 1] || nodeRef.current.firstChild;
break;
case 'ArrowUp':
element = nodeRef.current.childNodes[index - 1] || nodeRef.current.lastChild;
break;
case 'Tab':
if (e.shiftKey) {
element = nodeRef.current.childNodes[index + 1] || nodeRef.current.firstChild;
} else {
element = nodeRef.current.childNodes[index - 1] || nodeRef.current.lastChild;
}
break;
case 'Home':
element = nodeRef.current.firstChild;
break;
case 'End':
element = nodeRef.current.lastChild;
break;
}
if (element) {
element.focus();
setCurrentValue(element.getAttribute('data-index'));
e.preventDefault();
e.stopPropagation();
}
}, [nodeRef, items, onClose, handleClick, setCurrentValue]);
useEffect(() => {
document.addEventListener('click', handleDocumentClick, { capture: true });
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
focusedItemRef.current?.focus({ preventScroll: true });
return () => {
document.removeEventListener('click', handleDocumentClick, { capture: true });
document.removeEventListener('touchend', handleDocumentClick, listenerOptions);
};
}, [handleDocumentClick]);
return (
<ul style={{ ...style }} role='listbox' ref={nodeRef}>
{items.map(item => (
<li
role='option'
tabIndex={0}
key={item.value}
data-index={item.value}
onKeyDown={handleKeyDown}
onClick={handleClick}
className={classNames('privacy-dropdown__option', { active: item.value === currentValue })}
aria-selected={item.value === currentValue}
ref={item.value === currentValue ? focusedItemRef : null}
>
<div className='privacy-dropdown__option__icon'>
<Icon id={item.icon} icon={item.iconComponent} />
</div>
<div className='privacy-dropdown__option__content'>
<strong>{item.text}</strong>
{item.meta}
</div>
{item.extra && (
<div className='privacy-dropdown__option__additional' title={item.extra}>
<Icon id='info-circle' icon={InfoIcon} />
</div>
)}
</li>
))}
</ul>
);
};
PrivacyDropdownMenu.propTypes = {
style: PropTypes.object,
items: PropTypes.array.isRequired,
value: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};
@@ -7,6 +7,9 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { CheckboxWithLabel } from './checkbox_with_label';
// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = () => {};
export const PolicyControls: React.FC = () => {
const dispatch = useAppDispatch();
@@ -135,6 +138,21 @@ export const PolicyControls: React.FC = () => {
/>
</span>
</CheckboxWithLabel>
<CheckboxWithLabel checked disabled onChange={noop}>
<strong>
<FormattedMessage
id='notifications.policy.filter_limited_accounts_title'
defaultMessage='Moderated accounts'
/>
</strong>
<span className='hint'>
<FormattedMessage
id='notifications.policy.filter_limited_accounts_hint'
defaultMessage='Limited by server moderators'
/>
</span>
</CheckboxWithLabel>
</div>
</section>
);
@@ -1,7 +1,7 @@
import PropTypes from 'prop-types';
import { useRef, useCallback, useEffect } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
@@ -90,6 +90,23 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
const columnTitle = intl.formatMessage(messages.title, { name: account?.get('display_name') || account?.get('username') });
let explainer = null;
if (account?.limited) {
const isLocal = account.acct.indexOf('@') === -1;
explainer = (
<div className='dismissable-banner'>
<div className='dismissable-banner__message'>
{isLocal ? (
<FormattedMessage id='notification_requests.explainer_for_limited_account' defaultMessage='Notifications from this account have been filtered because the account has been limited by a moderator.' />
) : (
<FormattedMessage id='notification_requests.explainer_for_limited_remote_account' defaultMessage='Notifications from this account have been filtered because the account or its server has been limited by a moderator.' />
)}
</div>
</div>
);
}
return (
<Column bindToDocument={!multiColumn} ref={columnRef} label={columnTitle}>
<ColumnHeader
@@ -109,6 +126,7 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
<SensitiveMediaContextProvider hideMediaByDefault>
<ScrollableList
prepend={explainer}
scrollKey={`notification_requests/${id}`}
trackScroll={!multiColumn}
bindToDocument={!multiColumn}
+2
View File
@@ -545,6 +545,8 @@
"notifications.permission_denied": "Computernotifikationer er utilgængelige grundet tidligere afvist browsertilladelsesanmodning",
"notifications.permission_denied_alert": "Computernotifikationer kan ikke aktiveres, da browsertilladelse tidligere blev nægtet",
"notifications.permission_required": "Computernotifikationer er utilgængelige, da den krævede tilladelse ikke er tildelt.",
"notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer",
"notifications.policy.filter_limited_accounts_title": "Modererede konti",
"notifications.policy.filter_new_accounts.hint": "Oprettet indenfor {days, plural, one {den seneste dag} other {de seneste # dage}}",
"notifications.policy.filter_new_accounts_title": "Ny konti",
"notifications.policy.filter_not_followers_hint": "Inklusiv personer, som har fulgt dig {days, plural, one {mindre end én dag} other {færre end # dage}}",
+12
View File
@@ -11,6 +11,7 @@
"about.not_available": "Αυτές οι πληροφορίες δεν έχουν είναι διαθέσιμες σε αυτόν τον διακομιστή.",
"about.powered_by": "Αποκεντρωμένα μέσα κοινωνικής δικτύωσης που βασίζονται στο {mastodon}",
"about.rules": "Κανόνες διακομιστή",
"account.account_note_header": "Προσωπική σημείωση",
"account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
"account.badges.bot": "Αυτοματοποιημένος",
"account.badges.group": "Ομάδα",
@@ -170,21 +171,28 @@
"confirmations.block.confirm": "Αποκλεισμός",
"confirmations.delete.confirm": "Διαγραφή",
"confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή τη δημοσίευση;",
"confirmations.delete.title": "Διαγραφή ανάρτησης;",
"confirmations.delete_list.confirm": "Διαγραφή",
"confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
"confirmations.delete_list.title": "Διαγραφή λίστας;",
"confirmations.discard_edit_media.confirm": "Απόρριψη",
"confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;",
"confirmations.edit.confirm": "Επεξεργασία",
"confirmations.edit.message": "Αν το επεξεργαστείς τώρα θα αντικατασταθεί το μήνυμα που συνθέτεις. Είσαι σίγουρος ότι θέλεις να συνεχίσεις;",
"confirmations.edit.title": "Αντικατάσταση ανάρτησης;",
"confirmations.logout.confirm": "Αποσύνδεση",
"confirmations.logout.message": "Σίγουρα θέλεις να αποσυνδεθείς;",
"confirmations.logout.title": "Αποσύνδεση;",
"confirmations.mute.confirm": "Αποσιώπηση",
"confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
"confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.",
"confirmations.redraft.title": "Διαγραφή & επανασύνταξη;",
"confirmations.reply.confirm": "Απάντησε",
"confirmations.reply.message": "Απαντώντας τώρα θα αντικαταστήσεις το κείμενο που ήδη γράφεις. Σίγουρα θέλεις να συνεχίσεις;",
"confirmations.reply.title": "Αντικατάσταση ανάρτησης;",
"confirmations.unfollow.confirm": "Άρση ακολούθησης",
"confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
"confirmations.unfollow.title": "Άρση ακολούθησης;",
"conversation.delete": "Διαγραφή συζήτησης",
"conversation.mark_as_read": "Σήμανση ως αναγνωσμένο",
"conversation.open": "Προβολή συνομιλίας",
@@ -292,6 +300,7 @@
"filter_modal.select_filter.subtitle": "Χρησιμοποιήστε μια υπάρχουσα κατηγορία ή δημιουργήστε μια νέα",
"filter_modal.select_filter.title": "Φιλτράρισμα αυτής της ανάρτησης",
"filter_modal.title.status": "Φιλτράρισμα μιας ανάρτησης",
"filtered_notifications_banner.pending_requests": "Από {count, plural, =0 {κανένα} one {ένα άτομο} other {# άτομα}} που μπορεί να ξέρεις",
"filtered_notifications_banner.title": "Φιλτραρισμένες ειδοποιήσεις",
"firehose.all": "Όλα",
"firehose.local": "Αυτός ο διακομιστής",
@@ -496,10 +505,13 @@
"notification.update": "ο/η {name} επεξεργάστηκε μια ανάρτηση",
"notification_requests.accept": "Αποδοχή",
"notification_requests.dismiss": "Απόρριψη",
"notification_requests.maximize": "Μεγιστοποίηση",
"notification_requests.minimize_banner": "Ελαχιστοποίηση μπάνερ φιλτραρισμένων ειδοποιήσεων",
"notification_requests.notifications_from": "Ειδοποιήσεις από {name}",
"notification_requests.title": "Φιλτραρισμένες ειδοποιήσεις",
"notifications.clear": "Καθαρισμός ειδοποιήσεων",
"notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις μόνιμα όλες τις ειδοποιήσεις σου;",
"notifications.clear_title": "Εκκαθάριση ειδοποιήσεων;",
"notifications.column_settings.admin.report": "Νέες αναφορές:",
"notifications.column_settings.admin.sign_up": "Νέες εγγραφές:",
"notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
+4
View File
@@ -505,6 +505,8 @@
"notification.update": "{name} edited a post",
"notification_requests.accept": "Accept",
"notification_requests.dismiss": "Dismiss",
"notification_requests.explainer_for_limited_account": "Notifications from this account have been filtered because the account has been limited by a moderator.",
"notification_requests.explainer_for_limited_remote_account": "Notifications from this account have been filtered because the account or its server has been limited by a moderator.",
"notification_requests.maximize": "Maximize",
"notification_requests.minimize_banner": "Minimize filtered notifications banner",
"notification_requests.notifications_from": "Notifications from {name}",
@@ -545,6 +547,8 @@
"notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
"notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
"notifications.permission_required": "Desktop notifications are unavailable because the required permission has not been granted.",
"notifications.policy.filter_limited_accounts_hint": "Limited by server moderators",
"notifications.policy.filter_limited_accounts_title": "Moderated accounts",
"notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}",
"notifications.policy.filter_new_accounts_title": "New accounts",
"notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}",
@@ -545,6 +545,8 @@
"notifications.permission_denied": "Las notificaciones de escritorio no están disponibles, debido a una solicitud de permiso del navegador web previamente denegada",
"notifications.permission_denied_alert": "No se pueden habilitar las notificaciones de escritorio, ya que el permiso del navegador fue denegado antes",
"notifications.permission_required": "Las notificaciones de escritorio no están disponibles porque no se concedió el permiso requerido.",
"notifications.policy.filter_limited_accounts_hint": "Limitada por los moderadores del servidor",
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
"notifications.policy.filter_new_accounts.hint": "Creada hace {days, plural, one {un día} other {# días}}",
"notifications.policy.filter_new_accounts_title": "Nuevas cuentas",
"notifications.policy.filter_not_followers_hint": "Incluyendo cuentas que te han estado siguiendo menos de {days, plural, one {un día} other {# días}}",
+2
View File
@@ -545,6 +545,8 @@
"notifications.permission_denied": "Työpöytäilmoitukset eivät ole käytettävissä, koska selaimen käyttöoikeuspyyntö on aiemmin evätty",
"notifications.permission_denied_alert": "Työpöytäilmoituksia ei voi ottaa käyttöön, koska selaimen käyttöoikeus on aiemmin evätty",
"notifications.permission_required": "Työpöytäilmoitukset eivät ole käytettävissä, koska siihen tarvittavaa käyttöoikeutta ei ole myönnetty.",
"notifications.policy.filter_limited_accounts_hint": "Palvelimen moderaattorien rajoittamat",
"notifications.policy.filter_limited_accounts_title": "Moderoidut tilit",
"notifications.policy.filter_new_accounts.hint": "Luotu {days, plural, one {viime päivän} other {viimeisen # päivän}} aikana",
"notifications.policy.filter_new_accounts_title": "Uudet tilit",
"notifications.policy.filter_not_followers_hint": "Mukaan lukien alle {days, plural, one {päivän} other {# päivää}} sinua seuranneet",
+11 -2
View File
@@ -75,8 +75,9 @@
"block_modal.show_less": "Ssken-d drus",
"block_modal.show_more": "Ssken-d ugar",
"block_modal.they_cant_mention": "Ur zmiren ad k·m-id-bedren, ur zmiren ad k·m-ḍefren.",
"block_modal.they_cant_see_posts": "Ur zmiren ad walin tisufaɣ-nwen, ur tettwalim tid-nsen.",
"block_modal.they_cant_see_posts": "Ur yezmir ad wali tisuffaɣ-ik·im, ur tettwaliḍ tidak-is.",
"block_modal.title": "Sewḥel aseqdac ?",
"block_modal.you_wont_see_mentions": "Ur tezmireḍ ara ad twaliḍ tisuffaɣ anda d-yettwabdar.",
"boost_modal.combo": "Tzemreḍ ad tsiteḍ ɣef {combo} akken ad tzegleḍ aya tikelt i d-iteddun",
"bundle_column_error.copy_stacktrace": "Nɣel tuccḍa n uneqqis",
"bundle_column_error.error.title": "Uh, ala !",
@@ -176,7 +177,9 @@
"dismissable_banner.explore_tags": "D wiyi i d ihacṭagen i d-yettawin tamyigawt deg web anmetti ass-a. Ihacṭagen i sseqdacen ugar n medden, εlayit d imezwura.",
"domain_block_modal.block": "Sewḥel aqeddac",
"domain_block_modal.they_cant_follow": "Yiwen ur yezmir ad k·m-id-yeḍfer seg uqeddac-a.",
"domain_block_modal.they_wont_know": "Ur-d yettawi ara s lexbaṛ belli yettuseḥbes.",
"domain_block_modal.title": "Sewḥel taɣult?",
"domain_block_modal.you_wont_see_posts": "Ur tettuɣaleḍ ara ttwaliḍ tisuffaɣ neɣ ulɣuten n iseqdacen n uqeddac-a.",
"domain_pill.activitypub_like_language": "ActivityPub am tutlayt yettmeslay Mastodon d izeḍwan inmettiyen nniḍen.",
"domain_pill.server": "Aqeddac",
"domain_pill.username": "Isem n useqdac",
@@ -348,9 +351,15 @@
"load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}",
"loading_indicator.label": "Yessalay-d …",
"media_gallery.toggle_visible": "{number, plural, one {Ffer tugna} other {Ffer tugniwin}}",
"mute_modal.hide_from_notifications": "Ffer-it deg ulɣuten",
"mute_modal.hide_options": "Ffer tinefrunin",
"mute_modal.indefinite": "Alamma ssnesreɣ asgugem fell-as",
"mute_modal.show_options": "Sken-d tinefrunin",
"mute_modal.title": "Sgugem aseqdac?",
"mute_modal.they_can_mention_and_follow": "Yezmer ad k·m-id-yebder, ad k·m-yeḍfer, maca ur t-tettwaliḍt ara.",
"mute_modal.they_wont_know": "Ur yezmir ara ad iẓer dakken tesgugmeṭ.",
"mute_modal.title": "Asgugem n useqdac?",
"mute_modal.you_wont_see_mentions": "Ur tezmireḍ ara ad twaliḍ tisuffaɣ anda d-yettwabdar.",
"mute_modal.you_wont_see_posts": "Yezmer ad yettwali tisuffaɣ-ik·im, maca ur tettwaliḍ ara tidak-is.",
"navigation_bar.about": "Ɣef",
"navigation_bar.advanced_interface": "Ldi deg ugrudem n web leqqayen",
"navigation_bar.blocks": "Iseqdacen yettusḥebsen",
+2
View File
@@ -545,6 +545,8 @@
"notifications.permission_denied": "권한이 거부되었기 때문에 데스크탑 알림을 활성화할 수 없음",
"notifications.permission_denied_alert": "이전에 브라우저 권한이 거부되었기 때문에, 데스크탑 알림이 활성화 될 수 없습니다.",
"notifications.permission_required": "필요한 권한이 승인되지 않아 데스크탑 알림을 사용할 수 없습니다.",
"notifications.policy.filter_limited_accounts_hint": "서버 중재자에 의해 제한됨",
"notifications.policy.filter_limited_accounts_title": "중재된 계정",
"notifications.policy.filter_new_accounts.hint": "{days, plural, one {하루} other {#일}} 안에 만들어진",
"notifications.policy.filter_new_accounts_title": "새 계정",
"notifications.policy.filter_not_followers_hint": "나를 팔로우 한 지 {days, plural, other {# 일}}이 되지 않은 사람들을 포함",
+1 -1
View File
@@ -610,7 +610,7 @@
"poll_button.remove_poll": "移除投票",
"privacy.change": "设置嘟文的可见范围",
"privacy.direct.long": "帖子中提到的每个人",
"privacy.direct.short": "具体的人",
"privacy.direct.short": "特定的人",
"privacy.private.long": "仅限您的关注者",
"privacy.private.short": "关注者",
"privacy.public.long": "所有 Mastodon 内外的人",
@@ -474,8 +474,11 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
state.lastReadId,
action.payload.markers.notifications.last_read_id,
) < 0
)
) {
state.lastReadId = action.payload.markers.notifications.last_read_id;
state.readMarkerId =
action.payload.markers.notifications.last_read_id;
}
})
.addCase(mountNotifications, (state) => {
state.mounted += 1;