Merge remote-tracking branch 'parent/main' into upstream-20231120

This commit is contained in:
KMY 2023-11-20 12:50:02 +09:00
commit abec232dd7
85 changed files with 1314 additions and 458 deletions

View file

@ -229,7 +229,7 @@ jobs:
path: tmp/screenshots/ path: tmp/screenshots/
test-search: test-search:
name: Testing search name: Elastic Search integration testing
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
@ -329,7 +329,7 @@ jobs:
- name: Load database schema - name: Load database schema
run: './bin/rails db:create db:schema:load db:seed' run: './bin/rails db:create db:schema:load db:seed'
- run: bundle exec rake spec:search - run: bin/rspec --tag search
- name: Archive logs - name: Archive logs
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3

View file

@ -38,6 +38,7 @@ RUN apt-get update && \
corepack enable corepack enable
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/ COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY streaming/package.json /opt/mastodon/streaming/
COPY .yarn /opt/mastodon/.yarn COPY .yarn /opt/mastodon/.yarn
RUN bundle install -j"$(nproc)" RUN bundle install -j"$(nproc)"

View file

@ -109,6 +109,9 @@ group :test do
# RSpec progress bar formatter # RSpec progress bar formatter
gem 'fuubar', '~> 2.5' gem 'fuubar', '~> 2.5'
# RSpec helpers for email specs
gem 'email_spec'
# Extra RSpec extenion methods and helpers for sidekiq # Extra RSpec extenion methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 4.0' gem 'rspec-sidekiq', '~> 4.0'

View file

@ -260,6 +260,10 @@ GEM
elasticsearch-transport (7.13.3) elasticsearch-transport (7.13.3)
faraday (~> 1) faraday (~> 1)
multi_json multi_json
email_spec (2.2.2)
htmlentities (~> 4.3.3)
launchy (~> 2.1)
mail (~> 2.7)
encryptor (3.0.0) encryptor (3.0.0)
erubi (1.12.0) erubi (1.12.0)
et-orbi (1.2.7) et-orbi (1.2.7)
@ -855,6 +859,7 @@ DEPENDENCIES
doorkeeper (~> 5.6) doorkeeper (~> 5.6)
dotenv-rails (~> 2.8) dotenv-rails (~> 2.8)
ed25519 (~> 1.3) ed25519 (~> 1.3)
email_spec
fabrication (~> 2.30) fabrication (~> 2.30)
faker (~> 3.2) faker (~> 3.2)
fast_blank (~> 1.0) fast_blank (~> 1.0)

View file

@ -32,6 +32,9 @@ DB_USER=postgres DB_PASS=password foreman start
RAILS_ENV=test bundle exec rspec spec RAILS_ENV=test bundle exec rspec spec
# ElasticSearch連携テストを行う # ElasticSearch連携テストを行う
RAILS_ENV=test ES_ENABLED=true bundle exec rspec --tag search
RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search
``` ```

View file

@ -5,8 +5,8 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { useDispatch } from 'react-redux';
import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg'; import { ReactComponent as AddPhotoAlternateIcon } from '@material-symbols/svg-600/outlined/add_photo_alternate.svg';
import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg'; import { ReactComponent as EditIcon } from '@material-symbols/svg-600/outlined/edit.svg';
@ -33,7 +33,6 @@ export const Profile = () => {
const [avatar, setAvatar] = useState(null); const [avatar, setAvatar] = useState(null);
const [header, setHeader] = useState(null); const [header, setHeader] = useState(null);
const [discoverable, setDiscoverable] = useState(account.get('discoverable')); const [discoverable, setDiscoverable] = useState(account.get('discoverable'));
const [indexable, setIndexable] = useState(account.get('indexable'));
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const [errors, setErrors] = useState(); const [errors, setErrors] = useState();
const avatarFileRef = createRef(); const avatarFileRef = createRef();
@ -54,10 +53,6 @@ export const Profile = () => {
setDiscoverable(e.target.checked); setDiscoverable(e.target.checked);
}, [setDiscoverable]); }, [setDiscoverable]);
const handleIndexableChange = useCallback(e => {
setIndexable(e.target.checked);
}, [setIndexable]);
const handleAvatarChange = useCallback(e => { const handleAvatarChange = useCallback(e => {
setAvatar(e.target?.files?.[0]); setAvatar(e.target?.files?.[0]);
}, [setAvatar]); }, [setAvatar]);
@ -78,12 +73,12 @@ export const Profile = () => {
avatar, avatar,
header, header,
discoverable, discoverable,
indexable, indexable: discoverable,
})).then(() => history.push('/start/follows')).catch(err => { })).then(() => history.push('/start/follows')).catch(err => {
setIsSaving(false); setIsSaving(false);
setErrors(err.response.data.details); setErrors(err.response.data.details);
}); });
}, [dispatch, displayName, note, avatar, header, discoverable, indexable, history]); }, [dispatch, displayName, note, avatar, header, discoverable, history]);
return ( return (
<> <>
@ -141,17 +136,20 @@ export const Profile = () => {
<textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} /> <textarea id='note' value={note} onChange={handleNoteChange} maxLength={500} />
</div> </div>
</div> </div>
<label className='app-form__toggle'>
<div className='app-form__toggle__label'>
<strong><FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Make my profile discoverable' /></strong> <span className='recommended'><FormattedMessage id='recommended' defaultMessage='Recommended' /></span>
<span className='hint'><FormattedMessage id='onboarding.profile.discoverable_hint' defaultMessage='When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.' /></span>
</div> </div>
<label className='report-dialog-modal__toggle'> <div className='app-form__toggle__toggle'>
<div>
<Toggle checked={discoverable} onChange={handleDiscoverableChange} /> <Toggle checked={discoverable} onChange={handleDiscoverableChange} />
<FormattedMessage id='onboarding.profile.discoverable' defaultMessage='Feature profile and posts in discovery algorithms' /> </div>
</label> </div>
<label className='report-dialog-modal__toggle'>
<Toggle checked={indexable} onChange={handleIndexableChange} />
<FormattedMessage id='onboarding.profile.indexable' defaultMessage='Include public posts in search results' />
</label> </label>
</div>
<div className='onboarding__footer'> <div className='onboarding__footer'>
<Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button> <Button block onClick={handleSubmit} disabled={isSaving}>{isSaving ? <LoadingIndicator /> : <FormattedMessage id='onboarding.profile.save_and_continue' defaultMessage='Save and continue' />}</Button>

View file

@ -21,6 +21,7 @@
"account.blocked": "Заблакіраваны", "account.blocked": "Заблакіраваны",
"account.browse_more_on_origin_server": "Глядзіце больш у арыгінальным профілі", "account.browse_more_on_origin_server": "Глядзіце больш у арыгінальным профілі",
"account.cancel_follow_request": "Скасаваць запыт на падпіску", "account.cancel_follow_request": "Скасаваць запыт на падпіску",
"account.copy": "Скапіраваць спасылку на профіль",
"account.direct": "Згадаць асабіста @{name}", "account.direct": "Згадаць асабіста @{name}",
"account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}", "account.disable_notifications": "Не паведамляць мне пра публікацыі @{name}",
"account.domain_blocked": "Дамен заблакаваны", "account.domain_blocked": "Дамен заблакаваны",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Адзначыць прачытаным", "conversation.mark_as_read": "Адзначыць прачытаным",
"conversation.open": "Прагледзець размову", "conversation.open": "Прагледзець размову",
"conversation.with": "З {names}", "conversation.with": "З {names}",
"copy_icon_button.copied": "Скапіявана ў буфер абмену",
"copypaste.copied": "Скапіравана", "copypaste.copied": "Скапіравана",
"copypaste.copy_to_clipboard": "Капіраваць у буфер абмену", "copypaste.copy_to_clipboard": "Капіраваць у буфер абмену",
"directory.federated": "З вядомага федэсвету", "directory.federated": "З вядомага федэсвету",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokovaný", "account.blocked": "Blokovaný",
"account.browse_more_on_origin_server": "Více na původním profilu", "account.browse_more_on_origin_server": "Více na původním profilu",
"account.cancel_follow_request": "Zrušit žádost o sledování", "account.cancel_follow_request": "Zrušit žádost o sledování",
"account.copy": "Kopírovat odkaz na profil",
"account.direct": "Soukromě zmínit @{name}", "account.direct": "Soukromě zmínit @{name}",
"account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek", "account.disable_notifications": "Přestat mě upozorňovat, když @{name} zveřejní příspěvek",
"account.domain_blocked": "Doména blokována", "account.domain_blocked": "Doména blokována",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Označit jako přečtené", "conversation.mark_as_read": "Označit jako přečtené",
"conversation.open": "Zobrazit konverzaci", "conversation.open": "Zobrazit konverzaci",
"conversation.with": "S {names}", "conversation.with": "S {names}",
"copy_icon_button.copied": "Zkopírováno do schránky",
"copypaste.copied": "Zkopírováno", "copypaste.copied": "Zkopírováno",
"copypaste.copy_to_clipboard": "Zkopírovat do schránky", "copypaste.copy_to_clipboard": "Zkopírovat do schránky",
"directory.federated": "Ze známého fedivesmíru", "directory.federated": "Ze známého fedivesmíru",

View file

@ -191,6 +191,7 @@
"conversation.mark_as_read": "Nodi fel wedi'i ddarllen", "conversation.mark_as_read": "Nodi fel wedi'i ddarllen",
"conversation.open": "Gweld sgwrs", "conversation.open": "Gweld sgwrs",
"conversation.with": "Gyda {names}", "conversation.with": "Gyda {names}",
"copy_icon_button.copied": "Copïwyd i'r clipfwrdd",
"copypaste.copied": "Wedi ei gopïo", "copypaste.copied": "Wedi ei gopïo",
"copypaste.copy_to_clipboard": "Copïo i'r clipfwrdd", "copypaste.copy_to_clipboard": "Copïo i'r clipfwrdd",
"directory.federated": "O'r ffedysawd cyfan", "directory.federated": "O'r ffedysawd cyfan",
@ -390,6 +391,7 @@
"lists.search": "Chwilio ymysg pobl rydych yn eu dilyn", "lists.search": "Chwilio ymysg pobl rydych yn eu dilyn",
"lists.subheading": "Eich rhestrau", "lists.subheading": "Eich rhestrau",
"load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}", "load_pending": "{count, plural, one {# eitem newydd} other {# eitem newydd}}",
"loading_indicator.label": "Yn llwytho…",
"media_gallery.toggle_visible": "{number, plural, one {Cuddio delwedd} other {Cuddio delwedd}}", "media_gallery.toggle_visible": "{number, plural, one {Cuddio delwedd} other {Cuddio delwedd}}",
"moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.", "moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.",
"mute_modal.duration": "Hyd", "mute_modal.duration": "Hyd",
@ -478,6 +480,11 @@
"onboarding.follows.empty": "Yn anffodus, nid oes modd dangos unrhyw ganlyniadau ar hyn o bryd. Gallwch geisio defnyddio chwilio neu bori'r dudalen archwilio i ddod o hyd i bobl i'w dilyn, neu ceisio eto yn nes ymlaen.", "onboarding.follows.empty": "Yn anffodus, nid oes modd dangos unrhyw ganlyniadau ar hyn o bryd. Gallwch geisio defnyddio chwilio neu bori'r dudalen archwilio i ddod o hyd i bobl i'w dilyn, neu ceisio eto yn nes ymlaen.",
"onboarding.follows.lead": "Rydych chi'n curadu eich ffrwd gartref eich hun. Po fwyaf o bobl y byddwch chi'n eu dilyn, y mwyaf egnïol a diddorol fydd hi. Gall y proffiliau hyn fod yn fan cychwyn da - gallwch chi bob amser eu dad-ddilyn yn nes ymlaen:", "onboarding.follows.lead": "Rydych chi'n curadu eich ffrwd gartref eich hun. Po fwyaf o bobl y byddwch chi'n eu dilyn, y mwyaf egnïol a diddorol fydd hi. Gall y proffiliau hyn fod yn fan cychwyn da - gallwch chi bob amser eu dad-ddilyn yn nes ymlaen:",
"onboarding.follows.title": "Yn boblogaidd ar Mastodon", "onboarding.follows.title": "Yn boblogaidd ar Mastodon",
"onboarding.profile.display_name_hint": "Eich enw llawn neu'ch enw hwyl…",
"onboarding.profile.note": "Bywgraffiad",
"onboarding.profile.note_hint": "Gallwch @grybwyll pobl eraill neu #hashnodau…",
"onboarding.profile.save_and_continue": "Cadw a pharhau",
"onboarding.profile.title": "Gosodiad proffil",
"onboarding.share.lead": "Cofiwch ddweud wrth bobl sut y gallan nhw ddod o hyd i chi ar Mastodon!", "onboarding.share.lead": "Cofiwch ddweud wrth bobl sut y gallan nhw ddod o hyd i chi ar Mastodon!",
"onboarding.share.message": "Fi yw {username} ar #Mastodon! Dewch i'm dilyn i yn {url}", "onboarding.share.message": "Fi yw {username} ar #Mastodon! Dewch i'm dilyn i yn {url}",
"onboarding.share.next_steps": "Camau nesaf posib:", "onboarding.share.next_steps": "Camau nesaf posib:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokeret", "account.blocked": "Blokeret",
"account.browse_more_on_origin_server": "Se mere på den oprindelige profil", "account.browse_more_on_origin_server": "Se mere på den oprindelige profil",
"account.cancel_follow_request": "Annullér anmodning om at følge", "account.cancel_follow_request": "Annullér anmodning om at følge",
"account.copy": "Kopiér link til profil",
"account.direct": "Privat omtale @{name}", "account.direct": "Privat omtale @{name}",
"account.disable_notifications": "Advisér mig ikke længere, når @{name} poster", "account.disable_notifications": "Advisér mig ikke længere, når @{name} poster",
"account.domain_blocked": "Domæne blokeret", "account.domain_blocked": "Domæne blokeret",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Markér som læst", "conversation.mark_as_read": "Markér som læst",
"conversation.open": "Vis samtale", "conversation.open": "Vis samtale",
"conversation.with": "Med {names}", "conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopieret til udklipsholderen",
"copypaste.copied": "Kopieret", "copypaste.copied": "Kopieret",
"copypaste.copy_to_clipboard": "Kopiér til udklipsholder", "copypaste.copy_to_clipboard": "Kopiér til udklipsholder",
"directory.federated": "Fra kendt fedivers", "directory.federated": "Fra kendt fedivers",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blockiert", "account.blocked": "Blockiert",
"account.browse_more_on_origin_server": "Mehr auf dem Originalprofil durchsuchen", "account.browse_more_on_origin_server": "Mehr auf dem Originalprofil durchsuchen",
"account.cancel_follow_request": "Folgeanfrage zurückziehen", "account.cancel_follow_request": "Folgeanfrage zurückziehen",
"account.copy": "Link zum Profil kopieren",
"account.direct": "@{name} privat erwähnen", "account.direct": "@{name} privat erwähnen",
"account.disable_notifications": "Höre auf mich zu benachrichtigen wenn @{name} etwas postet", "account.disable_notifications": "Höre auf mich zu benachrichtigen wenn @{name} etwas postet",
"account.domain_blocked": "Domain versteckt", "account.domain_blocked": "Domain versteckt",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Als gelesen markieren", "conversation.mark_as_read": "Als gelesen markieren",
"conversation.open": "Unterhaltung anzeigen", "conversation.open": "Unterhaltung anzeigen",
"conversation.with": "Mit {names}", "conversation.with": "Mit {names}",
"copy_icon_button.copied": "In die Zwischenablage kopiert",
"copypaste.copied": "Kopiert", "copypaste.copied": "Kopiert",
"copypaste.copy_to_clipboard": "In die Zwischenablage kopieren", "copypaste.copy_to_clipboard": "In die Zwischenablage kopieren",
"directory.federated": "Aus bekanntem Fediverse", "directory.federated": "Aus bekanntem Fediverse",

View file

@ -506,10 +506,10 @@
"onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.", "onboarding.follows.empty": "Unfortunately, no results can be shown right now. You can try using search or browsing the explore page to find people to follow, or try again later.",
"onboarding.follows.lead": "Your home feed is the primary way to experience Mastodon. The more people you follow, the more active and interesting it will be. To get you started, here are some suggestions:", "onboarding.follows.lead": "Your home feed is the primary way to experience Mastodon. The more people you follow, the more active and interesting it will be. To get you started, here are some suggestions:",
"onboarding.follows.title": "Personalize your home feed", "onboarding.follows.title": "Personalize your home feed",
"onboarding.profile.discoverable": "Feature profile and posts in discovery algorithms", "onboarding.profile.discoverable": "Make my profile discoverable",
"onboarding.profile.discoverable_hint": "When you opt in to discoverability on Mastodon, your posts may appear in search results and trending, and your profile may be suggested to people with similar interests to you.",
"onboarding.profile.display_name": "Display name", "onboarding.profile.display_name": "Display name",
"onboarding.profile.display_name_hint": "Your full name or your fun name…", "onboarding.profile.display_name_hint": "Your full name or your fun name…",
"onboarding.profile.indexable": "Include public posts in search results",
"onboarding.profile.lead": "You can always complete this later in the settings, where even more customization options are available.", "onboarding.profile.lead": "You can always complete this later in the settings, where even more customization options are available.",
"onboarding.profile.note": "Bio", "onboarding.profile.note": "Bio",
"onboarding.profile.note_hint": "You can @mention other people or #hashtags…", "onboarding.profile.note_hint": "You can @mention other people or #hashtags…",
@ -570,6 +570,7 @@
"privacy_policy.title": "Privacy Policy", "privacy_policy.title": "Privacy Policy",
"reaction_deck.add": "Add", "reaction_deck.add": "Add",
"reaction_deck.remove": "Remove", "reaction_deck.remove": "Remove",
"recommended": "Recommended",
"refresh": "Refresh", "refresh": "Refresh",
"regeneration_indicator.label": "Loading…", "regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!", "regeneration_indicator.sublabel": "Your home feed is being prepared!",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado", "account.blocked": "Bloqueado",
"account.browse_more_on_origin_server": "Explorar más en el perfil original", "account.browse_more_on_origin_server": "Explorar más en el perfil original",
"account.cancel_follow_request": "Dejar de seguir", "account.cancel_follow_request": "Dejar de seguir",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada a @{name}", "account.direct": "Mención privada a @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} envíe mensajes", "account.disable_notifications": "Dejar de notificarme cuando @{name} envíe mensajes",
"account.domain_blocked": "Dominio bloqueado", "account.domain_blocked": "Dominio bloqueado",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como leída", "conversation.mark_as_read": "Marcar como leída",
"conversation.open": "Ver conversación", "conversation.open": "Ver conversación",
"conversation.with": "Con {names}", "conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiado en el portapapeles",
"copypaste.copied": "Copiado", "copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar al portapapeles", "copypaste.copy_to_clipboard": "Copiar al portapapeles",
"directory.federated": "Desde fediverso conocido", "directory.federated": "Desde fediverso conocido",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado", "account.blocked": "Bloqueado",
"account.browse_more_on_origin_server": "Ver más en el perfil original", "account.browse_more_on_origin_server": "Ver más en el perfil original",
"account.cancel_follow_request": "Retirar solicitud de seguimiento", "account.cancel_follow_request": "Retirar solicitud de seguimiento",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada @{name}", "account.direct": "Mención privada @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo", "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
"account.domain_blocked": "Dominio oculto", "account.domain_blocked": "Dominio oculto",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como leído", "conversation.mark_as_read": "Marcar como leído",
"conversation.open": "Ver conversación", "conversation.open": "Ver conversación",
"conversation.with": "Con {names}", "conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiado al portapapeles",
"copypaste.copied": "Copiado", "copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar al portapapeles", "copypaste.copy_to_clipboard": "Copiar al portapapeles",
"directory.federated": "Desde el fediverso conocido", "directory.federated": "Desde el fediverso conocido",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado", "account.blocked": "Bloqueado",
"account.browse_more_on_origin_server": "Ver más en el perfil original", "account.browse_more_on_origin_server": "Ver más en el perfil original",
"account.cancel_follow_request": "Retirar solicitud de seguimiento", "account.cancel_follow_request": "Retirar solicitud de seguimiento",
"account.copy": "Copiar enlace al perfil",
"account.direct": "Mención privada a @{name}", "account.direct": "Mención privada a @{name}",
"account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo", "account.disable_notifications": "Dejar de notificarme cuando @{name} publique algo",
"account.domain_blocked": "Dominio bloqueado", "account.domain_blocked": "Dominio bloqueado",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como leído", "conversation.mark_as_read": "Marcar como leído",
"conversation.open": "Ver conversación", "conversation.open": "Ver conversación",
"conversation.with": "Con {names}", "conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiado al portapapeles",
"copypaste.copied": "Copiado", "copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar al portapapeles", "copypaste.copy_to_clipboard": "Copiar al portapapeles",
"directory.federated": "Desde el fediverso conocido", "directory.federated": "Desde el fediverso conocido",
@ -479,7 +481,17 @@
"onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar personas a las que seguir, o inténtalo de nuevo más tarde.", "onboarding.follows.empty": "Desafortunadamente, no se pueden mostrar resultados en este momento. Puedes intentar usar la búsqueda o navegar por la página de exploración para encontrar personas a las que seguir, o inténtalo de nuevo más tarde.",
"onboarding.follows.lead": "Tu línea de inicio es la forma principal de experimentar Mastodon. Cuanta más personas sigas, más activa e interesante será. Para empezar, aquí hay algunas sugerencias:", "onboarding.follows.lead": "Tu línea de inicio es la forma principal de experimentar Mastodon. Cuanta más personas sigas, más activa e interesante será. Para empezar, aquí hay algunas sugerencias:",
"onboarding.follows.title": "Personaliza tu línea de inicio", "onboarding.follows.title": "Personaliza tu línea de inicio",
"onboarding.profile.discoverable": "Destacar perfil y publicaciones en algoritmos de descubrimiento",
"onboarding.profile.display_name": "Nombre para mostrar", "onboarding.profile.display_name": "Nombre para mostrar",
"onboarding.profile.display_name_hint": "Tu nombre completo o tu apodo…",
"onboarding.profile.indexable": "Incluir publicaciones públicas en los resultados de búsqueda",
"onboarding.profile.lead": "Siempre puedes completar esto más tarde en los ajustes, donde hay aún más opciones de personalización disponibles.",
"onboarding.profile.note": "Biografía",
"onboarding.profile.note_hint": "Puedes @mencionar a otras personas o #etiquetas…",
"onboarding.profile.save_and_continue": "Guardar y continuar",
"onboarding.profile.title": "Configuración del perfil",
"onboarding.profile.upload_avatar": "Subir foto de perfil",
"onboarding.profile.upload_header": "Subir encabezado de perfil",
"onboarding.share.lead": "¡Cuéntale a otras personas cómo te pueden encontrar en Mastodon!", "onboarding.share.lead": "¡Cuéntale a otras personas cómo te pueden encontrar en Mastodon!",
"onboarding.share.message": "¡Soy {username} en #Mastodon! Ven a seguirme en {url}", "onboarding.share.message": "¡Soy {username} en #Mastodon! Ven a seguirme en {url}",
"onboarding.share.next_steps": "Posibles siguientes pasos:", "onboarding.share.next_steps": "Posibles siguientes pasos:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokeatuta", "account.blocked": "Blokeatuta",
"account.browse_more_on_origin_server": "Arakatu gehiago jatorrizko profilean", "account.browse_more_on_origin_server": "Arakatu gehiago jatorrizko profilean",
"account.cancel_follow_request": "Baztertu jarraitzeko eskaera", "account.cancel_follow_request": "Baztertu jarraitzeko eskaera",
"account.copy": "Kopiatu profilerako esteka",
"account.direct": "Aipatu pribatuki @{name}", "account.direct": "Aipatu pribatuki @{name}",
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzailearen bidalketetan", "account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzailearen bidalketetan",
"account.domain_blocked": "Ezkutatutako domeinua", "account.domain_blocked": "Ezkutatutako domeinua",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Markatu irakurrita bezala", "conversation.mark_as_read": "Markatu irakurrita bezala",
"conversation.open": "Ikusi elkarrizketa", "conversation.open": "Ikusi elkarrizketa",
"conversation.with": "Hauekin: {names}", "conversation.with": "Hauekin: {names}",
"copy_icon_button.copied": "Arbelera kopiatu da",
"copypaste.copied": "Kopiatuta", "copypaste.copied": "Kopiatuta",
"copypaste.copy_to_clipboard": "Kopiatu arbelera", "copypaste.copy_to_clipboard": "Kopiatu arbelera",
"directory.federated": "Fedibertso ezagunekoak", "directory.federated": "Fedibertso ezagunekoak",

View file

@ -21,6 +21,7 @@
"account.blocked": "Estetty", "account.blocked": "Estetty",
"account.browse_more_on_origin_server": "Selaile lisää alkuperäisellä palvelimella", "account.browse_more_on_origin_server": "Selaile lisää alkuperäisellä palvelimella",
"account.cancel_follow_request": "Peruuta seurantapyyntö", "account.cancel_follow_request": "Peruuta seurantapyyntö",
"account.copy": "Kopioi profiilin linkki",
"account.direct": "Mainitse @{name} yksityisesti", "account.direct": "Mainitse @{name} yksityisesti",
"account.disable_notifications": "Lopeta ilmoittamasta minulle, kun @{name} julkaisee", "account.disable_notifications": "Lopeta ilmoittamasta minulle, kun @{name} julkaisee",
"account.domain_blocked": "Verkkotunnus estetty", "account.domain_blocked": "Verkkotunnus estetty",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Merkitse luetuksi", "conversation.mark_as_read": "Merkitse luetuksi",
"conversation.open": "Näytä keskustelu", "conversation.open": "Näytä keskustelu",
"conversation.with": "{names} kanssa", "conversation.with": "{names} kanssa",
"copy_icon_button.copied": "Kopioitu leikepöydälle",
"copypaste.copied": "Kopioitu", "copypaste.copied": "Kopioitu",
"copypaste.copy_to_clipboard": "Kopioi leikepöydälle", "copypaste.copy_to_clipboard": "Kopioi leikepöydälle",
"directory.federated": "Koko tunnettu fediversumi", "directory.federated": "Koko tunnettu fediversumi",

View file

@ -390,6 +390,7 @@
"lists.search": "Rechercher parmi les gens que vous suivez", "lists.search": "Rechercher parmi les gens que vous suivez",
"lists.subheading": "Vos listes", "lists.subheading": "Vos listes",
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}", "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
"loading_indicator.label": "Chargement…",
"media_gallery.toggle_visible": "{number, plural, one {Cacher limage} other {Cacher les images}}", "media_gallery.toggle_visible": "{number, plural, one {Cacher limage} other {Cacher les images}}",
"moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous avez déménagé sur {movedToAccount}.", "moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous avez déménagé sur {movedToAccount}.",
"mute_modal.duration": "Durée", "mute_modal.duration": "Durée",
@ -478,6 +479,9 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.", "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon", "onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages",
"onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!", "onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}", "onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles:", "onboarding.share.next_steps": "Étapes suivantes possibles:",

View file

@ -107,7 +107,7 @@
"closed_registrations_modal.preamble": "Mastodon est décentralisé : peu importe où vous créez votre compte, vous serez en mesure de suivre et d'interagir avec quiconque sur ce serveur. Vous pouvez même l'héberger !", "closed_registrations_modal.preamble": "Mastodon est décentralisé : peu importe où vous créez votre compte, vous serez en mesure de suivre et d'interagir avec quiconque sur ce serveur. Vous pouvez même l'héberger !",
"closed_registrations_modal.title": "Inscription sur Mastodon", "closed_registrations_modal.title": "Inscription sur Mastodon",
"column.about": "À propos", "column.about": "À propos",
"column.blocks": "Comptes bloqués", "column.blocks": "Utilisateurs bloqués",
"column.bookmarks": "Marque-pages", "column.bookmarks": "Marque-pages",
"column.community": "Fil public local", "column.community": "Fil public local",
"column.direct": "Mentions privées", "column.direct": "Mentions privées",
@ -390,6 +390,7 @@
"lists.search": "Rechercher parmi les gens que vous suivez", "lists.search": "Rechercher parmi les gens que vous suivez",
"lists.subheading": "Vos listes", "lists.subheading": "Vos listes",
"load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}", "load_pending": "{count, plural, one {# nouvel élément} other {# nouveaux éléments}}",
"loading_indicator.label": "Chargement…",
"media_gallery.toggle_visible": "{number, plural, one {Cacher limage} other {Cacher les images}}", "media_gallery.toggle_visible": "{number, plural, one {Cacher limage} other {Cacher les images}}",
"moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous l'avez déplacé à {movedToAccount}.", "moved_to_account_banner.text": "Votre compte {disabledAccount} est actuellement désactivé parce que vous l'avez déplacé à {movedToAccount}.",
"mute_modal.duration": "Durée", "mute_modal.duration": "Durée",
@ -478,6 +479,9 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.", "onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Personnaliser votre flux principal", "onboarding.follows.title": "Personnaliser votre flux principal",
"onboarding.profile.discoverable": "Autoriser des algorithmes de découverte à mettre en avant votre profil et vos messages",
"onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!", "onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}", "onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles :", "onboarding.share.next_steps": "Étapes suivantes possibles :",

View file

@ -390,6 +390,7 @@
"lists.search": "Procurar entre as persoas que segues", "lists.search": "Procurar entre as persoas que segues",
"lists.subheading": "As túas listaxes", "lists.subheading": "As túas listaxes",
"load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}", "load_pending": "{count, plural, one {# novo elemento} other {# novos elementos}}",
"loading_indicator.label": "Estase a cargar…",
"media_gallery.toggle_visible": "Agochar {number, plural, one {imaxe} other {imaxes}}", "media_gallery.toggle_visible": "Agochar {number, plural, one {imaxe} other {imaxes}}",
"moved_to_account_banner.text": "A túa conta {disabledAccount} está actualmente desactivada porque movéchela a {movedToAccount}.", "moved_to_account_banner.text": "A túa conta {disabledAccount} está actualmente desactivada porque movéchela a {movedToAccount}.",
"mute_modal.duration": "Duración", "mute_modal.duration": "Duración",
@ -478,6 +479,17 @@
"onboarding.follows.empty": "Desgraciadamente agora mesmo non hai nada que mostrar. Podes intentalo coa busca ou na páxina descubrir para atopar persoas ás que seguir, ou intentalo máis tarde.", "onboarding.follows.empty": "Desgraciadamente agora mesmo non hai nada que mostrar. Podes intentalo coa busca ou na páxina descubrir para atopar persoas ás que seguir, ou intentalo máis tarde.",
"onboarding.follows.lead": "Podes facer que a túa cronoloxía de inicio sexa como ti a queres. Canta máis xente sigas máis interesante será. Estes perfís poderían axudarche a comezar —sempre poderás deixar de seguilos despois!", "onboarding.follows.lead": "Podes facer que a túa cronoloxía de inicio sexa como ti a queres. Canta máis xente sigas máis interesante será. Estes perfís poderían axudarche a comezar —sempre poderás deixar de seguilos despois!",
"onboarding.follows.title": "Popular en Mastodon", "onboarding.follows.title": "Popular en Mastodon",
"onboarding.profile.discoverable": "Perfil destacado e publicacións nos algoritmos de descubrimento",
"onboarding.profile.display_name": "Nome público",
"onboarding.profile.display_name_hint": "O teu nome completo ou un nome divertido…",
"onboarding.profile.indexable": "Incluír publicacións públicas nos resultados das buscas",
"onboarding.profile.lead": "Sempre poderás incluír esta información mais tarde nos axustes, onde terás máis opcións dispoñibles.",
"onboarding.profile.note": "Acerca de ti",
"onboarding.profile.note_hint": "Podes @mencionar a outras persoas ou usar #cancelos…",
"onboarding.profile.save_and_continue": "Gardar e continuar",
"onboarding.profile.title": "Configuración do perfil",
"onboarding.profile.upload_avatar": "Subir imaxe do perfil",
"onboarding.profile.upload_header": "Subir cabeceira para o perfil",
"onboarding.share.lead": "Fai que as persoas saiban como atoparte en Mastodon!", "onboarding.share.lead": "Fai que as persoas saiban como atoparte en Mastodon!",
"onboarding.share.message": "Son {username} en #Mastodon! Ségueme en {url}", "onboarding.share.message": "Son {username} en #Mastodon! Ségueme en {url}",
"onboarding.share.next_steps": "Seguintes pasos:", "onboarding.share.next_steps": "Seguintes pasos:",

View file

@ -21,6 +21,7 @@
"account.blocked": "לחסום", "account.blocked": "לחסום",
"account.browse_more_on_origin_server": "ראה יותר בפרופיל המקורי", "account.browse_more_on_origin_server": "ראה יותר בפרופיל המקורי",
"account.cancel_follow_request": "משיכת בקשת מעקב", "account.cancel_follow_request": "משיכת בקשת מעקב",
"account.copy": "להעתיק קישור לפרופיל",
"account.direct": "הודעה פרטית אל @{name}", "account.direct": "הודעה פרטית אל @{name}",
"account.disable_notifications": "הפסק לשלוח לי התראות כש@{name} מפרסמים", "account.disable_notifications": "הפסק לשלוח לי התראות כש@{name} מפרסמים",
"account.domain_blocked": "הדומיין חסום", "account.domain_blocked": "הדומיין חסום",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "סמן כנקרא", "conversation.mark_as_read": "סמן כנקרא",
"conversation.open": "צפו בשיחה", "conversation.open": "צפו בשיחה",
"conversation.with": "עם {names}", "conversation.with": "עם {names}",
"copy_icon_button.copied": "הועתק ללוח",
"copypaste.copied": "הועתק", "copypaste.copied": "הועתק",
"copypaste.copy_to_clipboard": "העתקה ללוח הגזירים", "copypaste.copy_to_clipboard": "העתקה ללוח הגזירים",
"directory.federated": "מהפדרציה הידועה", "directory.federated": "מהפדרציה הידועה",

View file

@ -21,6 +21,7 @@
"account.blocked": "Letiltva", "account.blocked": "Letiltva",
"account.browse_more_on_origin_server": "További böngészés az eredeti profilon", "account.browse_more_on_origin_server": "További böngészés az eredeti profilon",
"account.cancel_follow_request": "Követési kérés visszavonása", "account.cancel_follow_request": "Követési kérés visszavonása",
"account.copy": "Hivatkozás másolása a profilba",
"account.direct": "@{name} személyes említése", "account.direct": "@{name} személyes említése",
"account.disable_notifications": "Ne figyelmeztessen, ha @{name} bejegyzést tesz közzé", "account.disable_notifications": "Ne figyelmeztessen, ha @{name} bejegyzést tesz közzé",
"account.domain_blocked": "Letiltott domain", "account.domain_blocked": "Letiltott domain",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Megjelölés olvasottként", "conversation.mark_as_read": "Megjelölés olvasottként",
"conversation.open": "Beszélgetés megtekintése", "conversation.open": "Beszélgetés megtekintése",
"conversation.with": "Velük: {names}", "conversation.with": "Velük: {names}",
"copy_icon_button.copied": "A szöveg a vágólapra másolva",
"copypaste.copied": "Másolva", "copypaste.copied": "Másolva",
"copypaste.copy_to_clipboard": "Másolás vágólapra", "copypaste.copy_to_clipboard": "Másolás vágólapra",
"directory.federated": "Az ismert fediverzumból", "directory.federated": "Az ismert fediverzumból",
@ -481,13 +483,13 @@
"onboarding.follows.title": "Népszerű a Mastodonon", "onboarding.follows.title": "Népszerű a Mastodonon",
"onboarding.profile.discoverable": "Profil és bejegyzések szerepeltetése a felfedezési algoritmusokban", "onboarding.profile.discoverable": "Profil és bejegyzések szerepeltetése a felfedezési algoritmusokban",
"onboarding.profile.display_name": "Megjelenített név", "onboarding.profile.display_name": "Megjelenített név",
"onboarding.profile.display_name_hint": "Teljes név vagy becenév…", "onboarding.profile.display_name_hint": "Teljes neved vagy vicces neved…",
"onboarding.profile.indexable": "Nyilvános bejegyzések is a keresési eredményekben", "onboarding.profile.indexable": "Nyilvános bejegyzések szerepeltetése a keresési eredményekben",
"onboarding.profile.lead": "Ezt később bármikor elvégezhető a beállításoknál, ahol még több testreszabási lehetőség áll rendelkezésre.", "onboarding.profile.lead": "Ezt később bármikor befejezheted a beállításokban, ahol még több testreszabási lehetőség áll rendelkezésre.",
"onboarding.profile.note": "Biográfia", "onboarding.profile.note": "Bemutatkozás",
"onboarding.profile.note_hint": "@említhetünk másokat vagy #hashtag elemeket…", "onboarding.profile.note_hint": "Megemlíthetsz @másokat vagy #hashtag-eket…",
"onboarding.profile.save_and_continue": "Mentés és folytatás", "onboarding.profile.save_and_continue": "Mentés és folytatás",
"onboarding.profile.title": "Profil beüzemelés", "onboarding.profile.title": "Profilbeállítás",
"onboarding.profile.upload_avatar": "Profilkép feltöltése", "onboarding.profile.upload_avatar": "Profilkép feltöltése",
"onboarding.profile.upload_header": "Profil fejléc feltöltése", "onboarding.profile.upload_header": "Profil fejléc feltöltése",
"onboarding.share.lead": "Tudassuk az emberekkel, hogyan találhatnak meg a Mastodonon!", "onboarding.share.lead": "Tudassuk az emberekkel, hogyan találhatnak meg a Mastodonon!",

View file

@ -21,6 +21,7 @@
"account.blocked": "Útilokaður", "account.blocked": "Útilokaður",
"account.browse_more_on_origin_server": "Skoða nánari upplýsingar á notandasniðinu", "account.browse_more_on_origin_server": "Skoða nánari upplýsingar á notandasniðinu",
"account.cancel_follow_request": "Taka fylgjendabeiðni til baka", "account.cancel_follow_request": "Taka fylgjendabeiðni til baka",
"account.copy": "Afrita tengil í notandasnið",
"account.direct": "Einkaspjall við @{name}", "account.direct": "Einkaspjall við @{name}",
"account.disable_notifications": "Hætta að láta mig vita þegar @{name} sendir inn", "account.disable_notifications": "Hætta að láta mig vita þegar @{name} sendir inn",
"account.domain_blocked": "Lén útilokað", "account.domain_blocked": "Lén útilokað",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Merkja sem lesið", "conversation.mark_as_read": "Merkja sem lesið",
"conversation.open": "Skoða samtal", "conversation.open": "Skoða samtal",
"conversation.with": "Við {names}", "conversation.with": "Við {names}",
"copy_icon_button.copied": "Afritað á klippispjald",
"copypaste.copied": "Afritað", "copypaste.copied": "Afritað",
"copypaste.copy_to_clipboard": "Afrita á klippispjald", "copypaste.copy_to_clipboard": "Afrita á klippispjald",
"directory.federated": "Frá samtengdum vefþjónum", "directory.federated": "Frá samtengdum vefþjónum",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloccato", "account.blocked": "Bloccato",
"account.browse_more_on_origin_server": "Sfoglia di più sul profilo originale", "account.browse_more_on_origin_server": "Sfoglia di più sul profilo originale",
"account.cancel_follow_request": "Annulla la richiesta di seguire", "account.cancel_follow_request": "Annulla la richiesta di seguire",
"account.copy": "Copia link del profilo",
"account.direct": "Menziona privatamente @{name}", "account.direct": "Menziona privatamente @{name}",
"account.disable_notifications": "Smetti di avvisarmi quando @{name} pubblica un post", "account.disable_notifications": "Smetti di avvisarmi quando @{name} pubblica un post",
"account.domain_blocked": "Dominio bloccato", "account.domain_blocked": "Dominio bloccato",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Segna come letto", "conversation.mark_as_read": "Segna come letto",
"conversation.open": "Visualizza conversazione", "conversation.open": "Visualizza conversazione",
"conversation.with": "Con {names}", "conversation.with": "Con {names}",
"copy_icon_button.copied": "Copiato negli appunti",
"copypaste.copied": "Copiato", "copypaste.copied": "Copiato",
"copypaste.copy_to_clipboard": "Copia negli Appunti", "copypaste.copy_to_clipboard": "Copia negli Appunti",
"directory.federated": "Da un fediverse noto", "directory.federated": "Da un fediverse noto",

View file

@ -21,6 +21,7 @@
"account.blocked": "차단함", "account.blocked": "차단함",
"account.browse_more_on_origin_server": "원본 프로필에서 더 탐색하기", "account.browse_more_on_origin_server": "원본 프로필에서 더 탐색하기",
"account.cancel_follow_request": "팔로우 취소", "account.cancel_follow_request": "팔로우 취소",
"account.copy": "프로필 링크 복사",
"account.direct": "@{name} 님에게 개인적으로 멘션", "account.direct": "@{name} 님에게 개인적으로 멘션",
"account.disable_notifications": "@{name} 의 게시물 알림 끄기", "account.disable_notifications": "@{name} 의 게시물 알림 끄기",
"account.domain_blocked": "도메인 차단함", "account.domain_blocked": "도메인 차단함",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "읽은 상태로 표시", "conversation.mark_as_read": "읽은 상태로 표시",
"conversation.open": "대화 보기", "conversation.open": "대화 보기",
"conversation.with": "{names} 님과", "conversation.with": "{names} 님과",
"copy_icon_button.copied": "클립보드에 복사함",
"copypaste.copied": "복사됨", "copypaste.copied": "복사됨",
"copypaste.copy_to_clipboard": "클립보드에 복사", "copypaste.copy_to_clipboard": "클립보드에 복사",
"directory.federated": "알려진 연합우주로부터", "directory.federated": "알려진 연합우주로부터",

View file

@ -21,6 +21,7 @@
"account.blocked": "Užblokuota", "account.blocked": "Užblokuota",
"account.browse_more_on_origin_server": "Naršyti daugiau originaliame profilyje", "account.browse_more_on_origin_server": "Naršyti daugiau originaliame profilyje",
"account.cancel_follow_request": "Atšaukti sekimą", "account.cancel_follow_request": "Atšaukti sekimą",
"account.copy": "Kopijuoti nuorodą į profilį",
"account.direct": "Privačiai paminėti @{name}", "account.direct": "Privačiai paminėti @{name}",
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia", "account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
"account.domain_blocked": "Užblokuotas domenas", "account.domain_blocked": "Užblokuotas domenas",
@ -91,11 +92,28 @@
"bundle_column_error.routing.body": "Prašyto puslapio nepavyko rasti. Ar esi tikras (-a), kad adreso juostoje nurodytas URL adresas yra teisingas?", "bundle_column_error.routing.body": "Prašyto puslapio nepavyko rasti. Ar esi tikras (-a), kad adreso juostoje nurodytas URL adresas yra teisingas?",
"bundle_column_error.routing.title": "404", "bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Uždaryti", "bundle_modal_error.close": "Uždaryti",
"bundle_modal_error.retry": "Bandyti dar kartą",
"closed_registrations.other_server_instructions": "Kadangi Mastodon yra decentralizuotas, gali susikurti paskyrą kitame serveryje ir vis tiek bendrauti su šiuo serveriu.",
"closed_registrations_modal.description": "Sukurti paskyrą {domain} šiuo metu neįmanoma, tačiau nepamiršk, kad norint naudotis Mastodon nebūtina turėti paskyrą {domain}.",
"closed_registrations_modal.find_another_server": "Rasti kitą serverį", "closed_registrations_modal.find_another_server": "Rasti kitą serverį",
"column.domain_blocks": "Hidden domains", "closed_registrations_modal.preamble": "Mastodon yra decentralizuotas, todėl nesvarbu, kur susikursi paskyrą, galėsi sekti ir bendrauti su bet kuriuo šiame serveryje esančiu asmeniu. Jį gali net savarankiškai talpinti!",
"closed_registrations_modal.title": "Užsiregistravimas į Mastodon",
"column.about": "Apie",
"column.blocks": "Užblokuoti naudotojai",
"column.bookmarks": "Žymės",
"column.community": "Vietinė laiko skalė",
"column.direct": "Privatūs paminėjimai",
"column.directory": "Naršyti profilius",
"column.domain_blocks": "Užblokuoti domenai",
"column.favourites": "Mėgstamiausi",
"column.firehose": "Tiesioginiai padavimai",
"column.follow_requests": "Sekti prašymus",
"column.home": "Pradžia",
"column.lists": "Sąrašai", "column.lists": "Sąrašai",
"column.mutes": "Užtildyti vartotojai", "column.mutes": "Užtildyti naudotojai",
"column.pins": "Pinned toot", "column.notifications": "Pranešimai",
"column.pins": "Prisegti įrašai",
"column.public": "Federacinė laiko skalė",
"column_back_button.label": "Atgal", "column_back_button.label": "Atgal",
"column_header.hide_settings": "Slėpti nustatymus", "column_header.hide_settings": "Slėpti nustatymus",
"column_header.pin": "Prisegti", "column_header.pin": "Prisegti",
@ -106,6 +124,7 @@
"compose.language.change": "Keisti kalbą", "compose.language.change": "Keisti kalbą",
"compose.language.search": "Ieškoti kalbų...", "compose.language.search": "Ieškoti kalbų...",
"compose.published.body": "Įrašas paskelbtas.", "compose.published.body": "Įrašas paskelbtas.",
"compose_form.direct_message_warning_learn_more": "Sužinoti daugiau",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.placeholder": "Kas tavo mintyse?", "compose_form.placeholder": "Kas tavo mintyse?",
@ -137,6 +156,7 @@
"confirmations.reply.confirm": "Atsakyti", "confirmations.reply.confirm": "Atsakyti",
"confirmations.reply.message": "Atsakydamas (-a) dabar perrašysi šiuo metu rašomą žinutę. Ar tikrai nori tęsti?", "confirmations.reply.message": "Atsakydamas (-a) dabar perrašysi šiuo metu rašomą žinutę. Ar tikrai nori tęsti?",
"confirmations.unfollow.confirm": "Nebesekti", "confirmations.unfollow.confirm": "Nebesekti",
"copy_icon_button.copied": "Nukopijuota į iškarpinę",
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
"embed.instructions": "Embed this status on your website by copying the code below.", "embed.instructions": "Embed this status on your website by copying the code below.",

View file

@ -21,6 +21,7 @@
"account.blocked": "Geblokkeerd", "account.blocked": "Geblokkeerd",
"account.browse_more_on_origin_server": "Zie meer op het originele profiel", "account.browse_more_on_origin_server": "Zie meer op het originele profiel",
"account.cancel_follow_request": "Ontvolgen", "account.cancel_follow_request": "Ontvolgen",
"account.copy": "Link naar profiel kopiëren",
"account.direct": "@{name} een privébericht sturen", "account.direct": "@{name} een privébericht sturen",
"account.disable_notifications": "Geen melding meer geven wanneer @{name} een bericht plaatst", "account.disable_notifications": "Geen melding meer geven wanneer @{name} een bericht plaatst",
"account.domain_blocked": "Domein geblokkeerd", "account.domain_blocked": "Domein geblokkeerd",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Als gelezen markeren", "conversation.mark_as_read": "Als gelezen markeren",
"conversation.open": "Gesprek tonen", "conversation.open": "Gesprek tonen",
"conversation.with": "Met {names}", "conversation.with": "Met {names}",
"copy_icon_button.copied": "Gekopieerd naar klembord",
"copypaste.copied": "Gekopieerd", "copypaste.copied": "Gekopieerd",
"copypaste.copy_to_clipboard": "Naar klembord kopiëren", "copypaste.copy_to_clipboard": "Naar klembord kopiëren",
"directory.federated": "Fediverse (wat bekend is)", "directory.federated": "Fediverse (wat bekend is)",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokkert", "account.blocked": "Blokkert",
"account.browse_more_on_origin_server": "Sjå gjennom meir på den opphavlege profilen", "account.browse_more_on_origin_server": "Sjå gjennom meir på den opphavlege profilen",
"account.cancel_follow_request": "Trekk attende fylgeførespurnad", "account.cancel_follow_request": "Trekk attende fylgeførespurnad",
"account.copy": "Kopier lenka til profilen",
"account.direct": "Nevn @{name} privat", "account.direct": "Nevn @{name} privat",
"account.disable_notifications": "Slutt å varsle meg når @{name} skriv innlegg", "account.disable_notifications": "Slutt å varsle meg når @{name} skriv innlegg",
"account.domain_blocked": "Domenet er sperra", "account.domain_blocked": "Domenet er sperra",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marker som lesen", "conversation.mark_as_read": "Marker som lesen",
"conversation.open": "Sjå samtale", "conversation.open": "Sjå samtale",
"conversation.with": "Med {names}", "conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopiert til utklyppstavla",
"copypaste.copied": "Kopiert", "copypaste.copied": "Kopiert",
"copypaste.copy_to_clipboard": "Kopier til utklyppstavla", "copypaste.copy_to_clipboard": "Kopier til utklyppstavla",
"directory.federated": "Frå den kjende allheimen", "directory.federated": "Frå den kjende allheimen",
@ -390,7 +392,7 @@
"lists.search": "Søk blant folk du fylgjer", "lists.search": "Søk blant folk du fylgjer",
"lists.subheading": "Listene dine", "lists.subheading": "Listene dine",
"load_pending": "{count, plural, one {# nytt element} other {# nye element}}", "load_pending": "{count, plural, one {# nytt element} other {# nye element}}",
"loading_indicator.label": "Laster…", "loading_indicator.label": "Lastar…",
"media_gallery.toggle_visible": "{number, plural, one {Skjul bilete} other {Skjul bilete}}", "media_gallery.toggle_visible": "{number, plural, one {Skjul bilete} other {Skjul bilete}}",
"moved_to_account_banner.text": "Kontoen din, {disabledAccount} er for tida deaktivert fordi du har flytta til {movedToAccount}.", "moved_to_account_banner.text": "Kontoen din, {disabledAccount} er for tida deaktivert fordi du har flytta til {movedToAccount}.",
"mute_modal.duration": "Varigheit", "mute_modal.duration": "Varigheit",
@ -479,17 +481,17 @@
"onboarding.follows.empty": "Me kan ikkje visa deg nokon resultat no. Du kan prøva å søkja eller bla gjennom utforsk-sida for å finna folk å fylgja, eller du kan prøva att seinare.", "onboarding.follows.empty": "Me kan ikkje visa deg nokon resultat no. Du kan prøva å søkja eller bla gjennom utforsk-sida for å finna folk å fylgja, eller du kan prøva att seinare.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon", "onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Fremhevede profiler og innlegg i oppdagelsealgoritmer", "onboarding.profile.discoverable": "Ta med profilen og innlegga i oppdagingsalgoritmar",
"onboarding.profile.display_name": "Visningsnavn", "onboarding.profile.display_name": "Synleg namn",
"onboarding.profile.display_name_hint": "Ditt fulle navn eller ditt morsomme navn…", "onboarding.profile.display_name_hint": "Det fulle namnet eller kallenamnet ditt…",
"onboarding.profile.indexable": "Inkluder offentlige innlegg i søkeresultatene", "onboarding.profile.indexable": "Ta med offentlege innlegg i søkjeresultat",
"onboarding.profile.lead": "Du kan alltid fullføre dette senere i innstillingene, der enda flere tilpasningsalternativer er tilgjengelige.", "onboarding.profile.lead": "Du kan alltid fullføra dette seinare i innstillingane, og der er det endå fleire tilpassingsalternativ.",
"onboarding.profile.note": "Om meg", "onboarding.profile.note": "Om meg",
"onboarding.profile.note_hint": "Du kan @nevne andre eller #emneknagger…", "onboarding.profile.note_hint": "Du kan @nemna folk eller #emneknaggar…",
"onboarding.profile.save_and_continue": "Lagre og fortsett", "onboarding.profile.save_and_continue": "Lagre og hald fram",
"onboarding.profile.title": "Konfigurering av profil", "onboarding.profile.title": "Profiloppsett",
"onboarding.profile.upload_avatar": "Last opp profilbilde", "onboarding.profile.upload_avatar": "Last opp profilbilete",
"onboarding.profile.upload_header": "Last opp profiltoppbilde", "onboarding.profile.upload_header": "Last opp profiltoppbilete",
"onboarding.share.lead": "La folk vita korleis dei kan finna deg på Mastodon!", "onboarding.share.lead": "La folk vita korleis dei kan finna deg på Mastodon!",
"onboarding.share.message": "Eg er {username} på #Mastodon! Du kan fylgja meg på {url}", "onboarding.share.message": "Eg er {username} på #Mastodon! Du kan fylgja meg på {url}",
"onboarding.share.next_steps": "Dette kan du gjera no:", "onboarding.share.next_steps": "Dette kan du gjera no:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokkert", "account.blocked": "Blokkert",
"account.browse_more_on_origin_server": "Bla mer på den opprinnelige profilen", "account.browse_more_on_origin_server": "Bla mer på den opprinnelige profilen",
"account.cancel_follow_request": "Avbryt følgeforespørselen", "account.cancel_follow_request": "Avbryt følgeforespørselen",
"account.copy": "Kopier lenke til profil",
"account.direct": "Nevn @{name} privat", "account.direct": "Nevn @{name} privat",
"account.disable_notifications": "Slutt å varsle meg når @{name} legger ut innlegg", "account.disable_notifications": "Slutt å varsle meg når @{name} legger ut innlegg",
"account.domain_blocked": "Domene blokkert", "account.domain_blocked": "Domene blokkert",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marker som lest", "conversation.mark_as_read": "Marker som lest",
"conversation.open": "Vis samtale", "conversation.open": "Vis samtale",
"conversation.with": "Med {names}", "conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopiert til utklippstavlen",
"copypaste.copied": "Kopiert", "copypaste.copied": "Kopiert",
"copypaste.copy_to_clipboard": "Kopier til utklippstavle", "copypaste.copy_to_clipboard": "Kopier til utklippstavle",
"directory.federated": "Fra det kjente strømiverset", "directory.federated": "Fra det kjente strømiverset",

View file

@ -21,6 +21,7 @@
"account.blocked": "Zablokowany(-a)", "account.blocked": "Zablokowany(-a)",
"account.browse_more_on_origin_server": "Zobacz więcej na oryginalnym profilu", "account.browse_more_on_origin_server": "Zobacz więcej na oryginalnym profilu",
"account.cancel_follow_request": "Wycofaj żądanie obserwowania", "account.cancel_follow_request": "Wycofaj żądanie obserwowania",
"account.copy": "Skopiuj odnośnik do profilu",
"account.direct": "Prywatna wzmianka @{name}", "account.direct": "Prywatna wzmianka @{name}",
"account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}", "account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}",
"account.domain_blocked": "Ukryto domenę", "account.domain_blocked": "Ukryto domenę",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Oznacz jako przeczytane", "conversation.mark_as_read": "Oznacz jako przeczytane",
"conversation.open": "Zobacz konwersację", "conversation.open": "Zobacz konwersację",
"conversation.with": "Z {names}", "conversation.with": "Z {names}",
"copy_icon_button.copied": "Skopiowano do schowka",
"copypaste.copied": "Skopiowano", "copypaste.copied": "Skopiowano",
"copypaste.copy_to_clipboard": "Skopiuj do schowka", "copypaste.copy_to_clipboard": "Skopiuj do schowka",
"directory.federated": "Ze znanego fediwersum", "directory.federated": "Ze znanego fediwersum",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqueado(a)", "account.blocked": "Bloqueado(a)",
"account.browse_more_on_origin_server": "Encontrar mais no perfil original", "account.browse_more_on_origin_server": "Encontrar mais no perfil original",
"account.cancel_follow_request": "Retirar pedido para seguir", "account.cancel_follow_request": "Retirar pedido para seguir",
"account.copy": "Copiar hiperligação para o perfil",
"account.direct": "Mencionar @{name} em privado", "account.direct": "Mencionar @{name} em privado",
"account.disable_notifications": "Parar de me notificar das publicações de @{name}", "account.disable_notifications": "Parar de me notificar das publicações de @{name}",
"account.domain_blocked": "Domínio bloqueado", "account.domain_blocked": "Domínio bloqueado",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marcar como lida", "conversation.mark_as_read": "Marcar como lida",
"conversation.open": "Ver conversa", "conversation.open": "Ver conversa",
"conversation.with": "Com {names}", "conversation.with": "Com {names}",
"copy_icon_button.copied": "Copiado para a área de transferência",
"copypaste.copied": "Copiado", "copypaste.copied": "Copiado",
"copypaste.copy_to_clipboard": "Copiar para a área de transferência", "copypaste.copy_to_clipboard": "Copiar para a área de transferência",
"directory.federated": "Do fediverso conhecido", "directory.federated": "Do fediverso conhecido",

View file

@ -21,6 +21,7 @@
"account.blocked": "E bllokuar", "account.blocked": "E bllokuar",
"account.browse_more_on_origin_server": "Shfletoni më tepër rreth profilit origjinal", "account.browse_more_on_origin_server": "Shfletoni më tepër rreth profilit origjinal",
"account.cancel_follow_request": "Tërhiq mbrapsht kërkesë për ndjekje", "account.cancel_follow_request": "Tërhiq mbrapsht kërkesë për ndjekje",
"account.copy": "Kopjoje lidhjen te profili",
"account.direct": "Përmendje private për @{name}", "account.direct": "Përmendje private për @{name}",
"account.disable_notifications": "Resht së njoftuari mua, kur poston @{name}", "account.disable_notifications": "Resht së njoftuari mua, kur poston @{name}",
"account.domain_blocked": "Përkatësia u bllokua", "account.domain_blocked": "Përkatësia u bllokua",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Vëri shenjë si të lexuar", "conversation.mark_as_read": "Vëri shenjë si të lexuar",
"conversation.open": "Shfaq bisedën", "conversation.open": "Shfaq bisedën",
"conversation.with": "Me {names}", "conversation.with": "Me {names}",
"copy_icon_button.copied": "U kopjua në të papastër",
"copypaste.copied": "U kopjua", "copypaste.copied": "U kopjua",
"copypaste.copy_to_clipboard": "Kopjoje në të papastër", "copypaste.copy_to_clipboard": "Kopjoje në të papastër",
"directory.federated": "Nga fedivers i njohur", "directory.federated": "Nga fedivers i njohur",

View file

@ -21,6 +21,7 @@
"account.blocked": "Блокиран", "account.blocked": "Блокиран",
"account.browse_more_on_origin_server": "Прегледајте још на оригиналном профилу", "account.browse_more_on_origin_server": "Прегледајте још на оригиналном профилу",
"account.cancel_follow_request": "Откажи праћење", "account.cancel_follow_request": "Откажи праћење",
"account.copy": "Копирај везу у профил",
"account.direct": "Приватно помени @{name}", "account.direct": "Приватно помени @{name}",
"account.disable_notifications": "Заустави обавештавање за објаве корисника @{name}", "account.disable_notifications": "Заустави обавештавање за објаве корисника @{name}",
"account.domain_blocked": "Домен је блокиран", "account.domain_blocked": "Домен је блокиран",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Означи као прочитано", "conversation.mark_as_read": "Означи као прочитано",
"conversation.open": "Прикажи разговор", "conversation.open": "Прикажи разговор",
"conversation.with": "Са {names}", "conversation.with": "Са {names}",
"copy_icon_button.copied": "Копирано",
"copypaste.copied": "Копирано", "copypaste.copied": "Копирано",
"copypaste.copy_to_clipboard": "Копирај", "copypaste.copy_to_clipboard": "Копирај",
"directory.federated": "Са знаног федиверзума", "directory.federated": "Са знаног федиверзума",
@ -390,6 +392,7 @@
"lists.search": "Претражи међу људима које пратите", "lists.search": "Претражи међу људима које пратите",
"lists.subheading": "Ваше листе", "lists.subheading": "Ваше листе",
"load_pending": "{count, plural, one {# нова ставка} few {# нове ставке} other {# нових ставки}}", "load_pending": "{count, plural, one {# нова ставка} few {# нове ставке} other {# нових ставки}}",
"loading_indicator.label": "Учитавање…",
"media_gallery.toggle_visible": "{number, plural, one {Сакриј слику} few {Сакриј слике} other {Сакриј слике}}", "media_gallery.toggle_visible": "{number, plural, one {Сакриј слику} few {Сакриј слике} other {Сакриј слике}}",
"moved_to_account_banner.text": "Ваш налог {disabledAccount} је тренутно онемогућен јер сте прешли на {movedToAccount}.", "moved_to_account_banner.text": "Ваш налог {disabledAccount} је тренутно онемогућен јер сте прешли на {movedToAccount}.",
"mute_modal.duration": "Трајање", "mute_modal.duration": "Трајање",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blockerad", "account.blocked": "Blockerad",
"account.browse_more_on_origin_server": "Läs mer på den ursprungliga profilen", "account.browse_more_on_origin_server": "Läs mer på den ursprungliga profilen",
"account.cancel_follow_request": "Återkalla din begäran om att få följa", "account.cancel_follow_request": "Återkalla din begäran om att få följa",
"account.copy": "Kopiera länk till profil",
"account.direct": "Nämn @{name} privat", "account.direct": "Nämn @{name} privat",
"account.disable_notifications": "Sluta notifiera mig när @{name} gör inlägg", "account.disable_notifications": "Sluta notifiera mig när @{name} gör inlägg",
"account.domain_blocked": "Domän blockerad", "account.domain_blocked": "Domän blockerad",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Markera som läst", "conversation.mark_as_read": "Markera som läst",
"conversation.open": "Visa konversation", "conversation.open": "Visa konversation",
"conversation.with": "Med {names}", "conversation.with": "Med {names}",
"copy_icon_button.copied": "Kopierad till urklipp",
"copypaste.copied": "Kopierad", "copypaste.copied": "Kopierad",
"copypaste.copy_to_clipboard": "Kopiera till urklipp", "copypaste.copy_to_clipboard": "Kopiera till urklipp",
"directory.federated": "Från känt fediversum", "directory.federated": "Från känt fediversum",

View file

@ -21,6 +21,7 @@
"account.blocked": "ปิดกั้นอยู่", "account.blocked": "ปิดกั้นอยู่",
"account.browse_more_on_origin_server": "เรียกดูเพิ่มเติมในโปรไฟล์ดั้งเดิม", "account.browse_more_on_origin_server": "เรียกดูเพิ่มเติมในโปรไฟล์ดั้งเดิม",
"account.cancel_follow_request": "ยกเลิกการติดตาม", "account.cancel_follow_request": "ยกเลิกการติดตาม",
"account.copy": "คัดลอกลิงก์ไปยังโปรไฟล์",
"account.direct": "กล่าวถึง @{name} แบบส่วนตัว", "account.direct": "กล่าวถึง @{name} แบบส่วนตัว",
"account.disable_notifications": "หยุดแจ้งเตือนฉันเมื่อ @{name} โพสต์", "account.disable_notifications": "หยุดแจ้งเตือนฉันเมื่อ @{name} โพสต์",
"account.domain_blocked": "ปิดกั้นโดเมนอยู่", "account.domain_blocked": "ปิดกั้นโดเมนอยู่",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "ทำเครื่องหมายว่าอ่านแล้ว", "conversation.mark_as_read": "ทำเครื่องหมายว่าอ่านแล้ว",
"conversation.open": "ดูการสนทนา", "conversation.open": "ดูการสนทนา",
"conversation.with": "กับ {names}", "conversation.with": "กับ {names}",
"copy_icon_button.copied": "คัดลอกไปยังคลิปบอร์ดแล้ว",
"copypaste.copied": "คัดลอกแล้ว", "copypaste.copied": "คัดลอกแล้ว",
"copypaste.copy_to_clipboard": "คัดลอกไปยังคลิปบอร์ด", "copypaste.copy_to_clipboard": "คัดลอกไปยังคลิปบอร์ด",
"directory.federated": "จากจักรวาลสหพันธ์ที่รู้จัก", "directory.federated": "จากจักรวาลสหพันธ์ที่รู้จัก",
@ -481,15 +483,15 @@
"onboarding.follows.title": "ปรับแต่งฟีดหน้าแรกของคุณ", "onboarding.follows.title": "ปรับแต่งฟีดหน้าแรกของคุณ",
"onboarding.profile.discoverable": "แสดงโปรไฟล์และโพสต์ในอัลกอริทึมการค้นพบ", "onboarding.profile.discoverable": "แสดงโปรไฟล์และโพสต์ในอัลกอริทึมการค้นพบ",
"onboarding.profile.display_name": "ชื่อที่แสดง", "onboarding.profile.display_name": "ชื่อที่แสดง",
"onboarding.profile.display_name_hint": "ชื่อเต็มหรือชื่อแบบสนุกสนานของคุณ", "onboarding.profile.display_name_hint": "ชื่อเต็มของคุณหรือชื่อแบบสนุกสนานของคุณ",
"onboarding.profile.indexable": "รวมโพสต์สาธารณะในผลลัพธ์การค้นหา", "onboarding.profile.indexable": "รวมโพสต์สาธารณะในผลลัพธ์การค้นหา",
"onboarding.profile.lead": "คุณสามารถกลับมาทำต่อได้เสมอในการตั้งค่า ซึ่งจะมีตัวเลือกในการปรับแต่งมากกว่า", "onboarding.profile.lead": "คุณสามารถกลับมาทำต่อได้เสมอในการตั้งค่า ซึ่งจะมีตัวเลือกในการปรับแต่งมากกว่า",
"onboarding.profile.note": "ชีวประวัติ", "onboarding.profile.note": "ชีวประวัติ",
"onboarding.profile.note_hint": "คุณสามารถ @กล่าวถึง ผู้คนอื่น ๆ หรือ #แฮชแท็ก", "onboarding.profile.note_hint": "คุณสามารถ @กล่าวถึง ผู้คนอื่น ๆ หรือ #แฮชแท็ก",
"onboarding.profile.save_and_continue": "บันทึกและดำเนินการต่อ", "onboarding.profile.save_and_continue": "บันทึกและดำเนินการต่อ",
"onboarding.profile.title": "การตั้งค่าโปรไฟล์", "onboarding.profile.title": "การตั้งค่าโปรไฟล์",
"onboarding.profile.upload_avatar": "อัปโหลดรูปโปรไฟล์", "onboarding.profile.upload_avatar": "อัปโหลดรูปภาพโปรไฟล์",
"onboarding.profile.upload_header": "อัปโหลดรูปส่วนหัวโปรไฟล์", "onboarding.profile.upload_header": "อัปโหลดส่วนหัวโปรไฟล์",
"onboarding.share.lead": "แจ้งให้ผู้คนทราบวิธีที่เขาสามารถค้นหาคุณใน Mastodon!", "onboarding.share.lead": "แจ้งให้ผู้คนทราบวิธีที่เขาสามารถค้นหาคุณใน Mastodon!",
"onboarding.share.message": "ฉันคือ {username} ใน #Mastodon! มาติดตามฉันที่ {url}", "onboarding.share.message": "ฉันคือ {username} ใน #Mastodon! มาติดตามฉันที่ {url}",
"onboarding.share.next_steps": "ขั้นตอนถัดไปที่เป็นไปได้:", "onboarding.share.next_steps": "ขั้นตอนถัดไปที่เป็นไปได้:",

View file

@ -21,6 +21,7 @@
"account.blocked": "Заблоковані", "account.blocked": "Заблоковані",
"account.browse_more_on_origin_server": "Переглянути більше в оригінальному профілі", "account.browse_more_on_origin_server": "Переглянути більше в оригінальному профілі",
"account.cancel_follow_request": "Відкликати запит на стеження", "account.cancel_follow_request": "Відкликати запит на стеження",
"account.copy": "Копіювати посилання на профіль",
"account.direct": "Особиста згадка @{name}", "account.direct": "Особиста згадка @{name}",
"account.disable_notifications": "Не повідомляти мене про дописи @{name}", "account.disable_notifications": "Не повідомляти мене про дописи @{name}",
"account.domain_blocked": "Домен заблоковано", "account.domain_blocked": "Домен заблоковано",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Позначити як прочитане", "conversation.mark_as_read": "Позначити як прочитане",
"conversation.open": "Переглянути бесіду", "conversation.open": "Переглянути бесіду",
"conversation.with": "З {names}", "conversation.with": "З {names}",
"copy_icon_button.copied": "Скопійовано до буфера обміну",
"copypaste.copied": "Скопійовано", "copypaste.copied": "Скопійовано",
"copypaste.copy_to_clipboard": "Копіювати до буфера обміну", "copypaste.copy_to_clipboard": "Копіювати до буфера обміну",
"directory.federated": "З відомого федесвіту", "directory.federated": "З відомого федесвіту",

View file

@ -21,6 +21,7 @@
"account.blocked": "已屏蔽", "account.blocked": "已屏蔽",
"account.browse_more_on_origin_server": "在原始个人资料页面上浏览详情", "account.browse_more_on_origin_server": "在原始个人资料页面上浏览详情",
"account.cancel_follow_request": "撤回关注请求", "account.cancel_follow_request": "撤回关注请求",
"account.copy": "复制资料卡链接",
"account.direct": "私下提及 @{name}", "account.direct": "私下提及 @{name}",
"account.disable_notifications": "当 @{name} 发布嘟文时不要通知我", "account.disable_notifications": "当 @{name} 发布嘟文时不要通知我",
"account.domain_blocked": "域名已屏蔽", "account.domain_blocked": "域名已屏蔽",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "标记为已读", "conversation.mark_as_read": "标记为已读",
"conversation.open": "查看对话", "conversation.open": "查看对话",
"conversation.with": "与 {names}", "conversation.with": "与 {names}",
"copy_icon_button.copied": "已复制到剪贴板",
"copypaste.copied": "已复制", "copypaste.copied": "已复制",
"copypaste.copy_to_clipboard": "复制到剪贴板", "copypaste.copy_to_clipboard": "复制到剪贴板",
"directory.federated": "来自已知的联邦宇宙", "directory.federated": "来自已知的联邦宇宙",

View file

@ -21,6 +21,7 @@
"account.blocked": "已封鎖", "account.blocked": "已封鎖",
"account.browse_more_on_origin_server": "前往原始的個人檔案頁瀏覽更多", "account.browse_more_on_origin_server": "前往原始的個人檔案頁瀏覽更多",
"account.cancel_follow_request": "撤回追蹤請求", "account.cancel_follow_request": "撤回追蹤請求",
"account.copy": "複製個人檔案連結",
"account.direct": "私下提及 @{name}", "account.direct": "私下提及 @{name}",
"account.disable_notifications": "當 @{name} 發文時不要再通知我", "account.disable_notifications": "當 @{name} 發文時不要再通知我",
"account.domain_blocked": "網域被封鎖", "account.domain_blocked": "網域被封鎖",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "標為已讀", "conversation.mark_as_read": "標為已讀",
"conversation.open": "檢視對話", "conversation.open": "檢視對話",
"conversation.with": "與 {names}", "conversation.with": "與 {names}",
"copy_icon_button.copied": "已複製到剪貼簿",
"copypaste.copied": "已複製", "copypaste.copied": "已複製",
"copypaste.copy_to_clipboard": "複製到剪貼簿", "copypaste.copy_to_clipboard": "複製到剪貼簿",
"directory.federated": "來自已知的聯盟網絡", "directory.federated": "來自已知的聯盟網絡",

View file

@ -21,6 +21,7 @@
"account.blocked": "已封鎖", "account.blocked": "已封鎖",
"account.browse_more_on_origin_server": "在該伺服器上的個人檔案頁面瀏覽更多", "account.browse_more_on_origin_server": "在該伺服器上的個人檔案頁面瀏覽更多",
"account.cancel_follow_request": "收回跟隨請求", "account.cancel_follow_request": "收回跟隨請求",
"account.copy": "複製個人檔案連結",
"account.direct": "私訊 @{name}", "account.direct": "私訊 @{name}",
"account.disable_notifications": "取消來自 @{name} 嘟文的通知", "account.disable_notifications": "取消來自 @{name} 嘟文的通知",
"account.domain_blocked": "已封鎖網域", "account.domain_blocked": "已封鎖網域",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "標記為已讀", "conversation.mark_as_read": "標記為已讀",
"conversation.open": "檢視對話", "conversation.open": "檢視對話",
"conversation.with": "與 {names}", "conversation.with": "與 {names}",
"copy_icon_button.copied": "已複製到剪貼簿",
"copypaste.copied": "已複製", "copypaste.copied": "已複製",
"copypaste.copy_to_clipboard": "複製到剪貼簿", "copypaste.copy_to_clipboard": "複製到剪貼簿",
"directory.federated": "來自已知聯邦宇宙", "directory.federated": "來自已知聯邦宇宙",

View file

@ -2382,8 +2382,7 @@ $ui-header-height: 55px;
> .scrollable { > .scrollable {
background: $ui-base-color; background: $ui-base-color;
border-bottom-left-radius: 4px; border-radius: 0 0 4px 4px;
border-bottom-right-radius: 4px;
} }
} }

View file

@ -1223,10 +1223,6 @@ code {
} }
.app-form { .app-form {
& > * {
margin-bottom: 16px;
}
&__avatar-input, &__avatar-input,
&__header-input { &__header-input {
display: block; display: block;
@ -1291,4 +1287,49 @@ code {
&__header-input { &__header-input {
aspect-ratio: 580/193; aspect-ratio: 580/193;
} }
&__toggle {
display: flex;
align-items: center;
gap: 16px;
color: $darker-text-color;
font-size: 14px;
line-height: 20px;
.icon {
flex: 0 0 auto;
}
.icon {
width: 24px;
height: 24px;
}
&__label {
flex: 1 1 auto;
strong {
color: $primary-text-color;
font-weight: 600;
}
.recommended {
position: absolute;
margin: 0 4px;
margin-top: -2px;
}
}
&__toggle {
flex: 0 0 auto;
display: flex;
align-items: center;
}
&__toggle > div {
display: flex;
border-inline-start: 1px solid lighten($ui-base-color, 8%);
padding-inline-start: 16px;
}
}
} }

View file

@ -611,6 +611,7 @@ es:
created_at: Denunciado created_at: Denunciado
delete_and_resolve: Eliminar publicaciones delete_and_resolve: Eliminar publicaciones
forwarded: Reenviado forwarded: Reenviado
forwarded_replies_explanation: Este informe es de un usuario remoto y sobre contenido remoto. Se le ha enviado porque el contenido reportado es en respuesta a uno de sus usuarios.
forwarded_to: Reenviado a %{domain} forwarded_to: Reenviado a %{domain}
mark_as_resolved: Marcar como resuelto mark_as_resolved: Marcar como resuelto
mark_as_sensitive: Marcar como sensible mark_as_sensitive: Marcar como sensible

View file

@ -613,6 +613,7 @@ eu:
created_at: Salatua created_at: Salatua
delete_and_resolve: Ezabatu bidalketak delete_and_resolve: Ezabatu bidalketak
forwarded: Birbidalia forwarded: Birbidalia
forwarded_replies_explanation: Salaketa hau beste instantzia bateko erabiltzaile baten edukiari buruzkoa da, eta zure instantziako erabiltzaile bati egidako erantzuna delako bidali dizugu mezu hau.
forwarded_to: 'Hona birbidalia: %{domain}' forwarded_to: 'Hona birbidalia: %{domain}'
mark_as_resolved: Markatu konpondutako gisa mark_as_resolved: Markatu konpondutako gisa
mark_as_sensitive: Markatu hunkigarri gisa mark_as_sensitive: Markatu hunkigarri gisa

View file

@ -384,7 +384,7 @@ gl:
domain_blocks: domain_blocks:
add_new: Engadir novo bloqueo de dominio add_new: Engadir novo bloqueo de dominio
confirm_suspension: confirm_suspension:
cancel: Cancelar cancel: Desbotar
confirm: Suspender confirm: Suspender
permanent_action: Ao retirar a suspensión non restableces os datos ou a relación. permanent_action: Ao retirar a suspensión non restableces os datos ou a relación.
preamble_html: Vas suspender a <strong>%{domain}</strong> e os seus subdominios. preamble_html: Vas suspender a <strong>%{domain}</strong> e os seus subdominios.
@ -611,6 +611,7 @@ gl:
created_at: Denunciado created_at: Denunciado
delete_and_resolve: Eliminar publicacións delete_and_resolve: Eliminar publicacións
forwarded: Reenviado forwarded: Reenviado
forwarded_replies_explanation: Esta denuncia procede dunha usuaria remota e acerca de contido remoto. Enviouseche unha copia porque o contido denunciado é unha resposta a unha das túas usuarias.
forwarded_to: Reenviado a %{domain} forwarded_to: Reenviado a %{domain}
mark_as_resolved: Marcar como resolto mark_as_resolved: Marcar como resolto
mark_as_sensitive: Marcar como sensible mark_as_sensitive: Marcar como sensible

View file

@ -611,7 +611,7 @@ hu:
created_at: Jelentve created_at: Jelentve
delete_and_resolve: Bejegyzések törlése delete_and_resolve: Bejegyzések törlése
forwarded: Továbbítva forwarded: Továbbítva
forwarded_replies_explanation: Ez a jelentés egy távoli felhasználótól származik, és távoli tartalomról szól. Azért lett neked továbbítva, mert a jelentett tartalom az egyik felhasználódnak küldött válasz. forwarded_replies_explanation: Ez a jelentés egy távoli flehasználótól származik, és távoli tartalomról szól. Azért lett neked továbbítva, mert a jelentett tartalom az egyik felhasználódnak küldött válasz.
forwarded_to: 'Továbbítva ide: %{domain}' forwarded_to: 'Továbbítva ide: %{domain}'
mark_as_resolved: Megjelölés megoldottként mark_as_resolved: Megjelölés megoldottként
mark_as_sensitive: Érzékenynek jelölés mark_as_sensitive: Érzékenynek jelölés

View file

@ -763,7 +763,7 @@ ko:
open: 누구나 가입 할 수 있음 open: 누구나 가입 할 수 있음
security: security:
authorized_fetch: 연합된 서버들에게서 인증 필수 authorized_fetch: 연합된 서버들에게서 인증 필수
authorized_fetch_hint: 연합된 서버들에게서 인증을 요구하는 것은 사용자 레벨과 서버 레벨의 차단 좀 더 확실하게 해줍니다. 한편으로는 성능적인 페널티, 답글의 전달 범위 감소, 몇몇 연합된 서비스들과의 호환성 문제가 있을 가능성이 있습니다. 추가적으로 이 기능은 전용 액터가 공개돤 게시물이나 계정을 페치하는 것은 막지 않습니다. authorized_fetch_hint: 연합된 서버들에게서 인증을 요구하는 것은 사용자 레벨과 서버 레벨의 차단 좀 더 확실하게 해줍니다. 한편으로는 성능적인 페널티, 답글의 전달 범위 감소, 몇몇 연합된 서비스들과의 호환성 문제가 있을 가능성이 있습니다. 추가적으로 이 기능은 전용 액터가 공개돤 게시물이나 계정을 페치하는 것은 막지 않습니다.
authorized_fetch_overridden_hint: 현재 이 값은 환경변수에 의해 설정되어 있기에 설정을 변경할 수 없습니다. authorized_fetch_overridden_hint: 현재 이 값은 환경변수에 의해 설정되어 있기에 설정을 변경할 수 없습니다.
federation_authentication: 연합 인증 필수 federation_authentication: 연합 인증 필수
title: 서버 설정 title: 서버 설정

View file

@ -534,7 +534,7 @@ nn:
total_reported: Rapportar om dei total_reported: Rapportar om dei
total_storage: Medievedlegg total_storage: Medievedlegg
totals_time_period_hint_html: Totalsum vist nedanfor gjeld data for alle tidsperiodar. totals_time_period_hint_html: Totalsum vist nedanfor gjeld data for alle tidsperiodar.
unknown_instance: Dette domenet er ukjent for denne serveren. unknown_instance: Domenet er ukjent for denne tenaren.
invites: invites:
deactivate_all: Slå av alle deactivate_all: Slå av alle
filter: filter:
@ -611,7 +611,7 @@ nn:
created_at: Rapportert created_at: Rapportert
delete_and_resolve: Slett innlegg delete_and_resolve: Slett innlegg
forwarded: Videresendt forwarded: Videresendt
forwarded_replies_explanation: Denne rapporten er fra en ekstern bruker og handler om eksternt innhold. Den er videresendt til deg fordi det rapporterte innholdet svarer til en av brukerne dine. forwarded_replies_explanation: Denne rapporten gjeld innhald på ein annan nettstad. Rapporten er vidaresend til deg fordi det rapporterte innhaldet er eit svar på noko ein av brukarane på nettstaden din har skrive.
forwarded_to: Videresendt til %{domain} forwarded_to: Videresendt til %{domain}
mark_as_resolved: Merk som løyst mark_as_resolved: Merk som løyst
mark_as_sensitive: Marker som ømtolig mark_as_sensitive: Marker som ømtolig
@ -1042,14 +1042,14 @@ nn:
hint_html: Berre ein ting til! Vi må bekrefte at du er et menneske (så vi kan halde spam ute!). Løys CAPTCHA-en nedanfor og klikk "Fortsett". hint_html: Berre ein ting til! Vi må bekrefte at du er et menneske (så vi kan halde spam ute!). Løys CAPTCHA-en nedanfor og klikk "Fortsett".
title: Sikkerheitssjekk title: Sikkerheitssjekk
confirmations: confirmations:
awaiting_review: Din e-post adresse er bekreftet! %{domain} ansatte gjennomgår nå registreringen din. Du vil motta en e-post hvis de godkjenner din konto! awaiting_review: Epostadressa di er stadfesta! Styrarane på %{domain} ser gjennom registreringa di. Du får ein epost frå dei om dei godkjenner brukarkontoen din.
awaiting_review_title: Din registrering blir vurdert awaiting_review_title: Me går gjennom registreringa di
clicking_this_link: klikke på denne lenken clicking_this_link: klikka på denne lenka
login_link: logg inn login_link: logga inn
proceed_to_login_html: Du kan nå fortsette til %{login_link}. proceed_to_login_html: No kan du %{login_link}.
redirect_to_app_html: Du burde bli omdirigert til <strong>%{app_name}</strong> -appen. Hvis det ikke skjedde, kan du prøve %{clicking_this_link} eller manuelt gå tilbake til appen. redirect_to_app_html: Du skulle vorte vidaresend til <strong>%{app_name}</strong>-appen. Viss det ikkje skjedde, kan du prøva å %{clicking_this_link} eller manuelt gå tilbake til appen.
registration_complete: Registreringen på %{domain} er nå fullført! registration_complete: Du har registrert deg som brukar på %{domain}.
welcome_title: Velkommen, %{name}! welcome_title: Velkomen, %{name}!
wrong_email_hint: Viss epostadressa er feil, kan du endra ho i kontoinnstillingane. wrong_email_hint: Viss epostadressa er feil, kan du endra ho i kontoinnstillingane.
delete_account: Slett konto delete_account: Slett konto
delete_account_html: Om du vil sletta kontoen din, kan du <a href="%{path}">gå hit</a>. Du vert spurd etter stadfesting. delete_account_html: Om du vil sletta kontoen din, kan du <a href="%{path}">gå hit</a>. Du vert spurd etter stadfesting.
@ -1111,7 +1111,7 @@ nn:
functional: Kontoen din er fullt operativt. functional: Kontoen din er fullt operativt.
pending: Søknaden din ventar på gjennomgang frå personalet vårt. Dette kan taka litt tid. Du får ein e-post om søknaden din vert godkjend. pending: Søknaden din ventar på gjennomgang frå personalet vårt. Dette kan taka litt tid. Du får ein e-post om søknaden din vert godkjend.
redirecting_to: Kontoen din er inaktiv fordi den for øyeblikket omdirigerer til %{acct}. redirecting_to: Kontoen din er inaktiv fordi den for øyeblikket omdirigerer til %{acct}.
self_destruct: Siden %{domain} stenger, vil du kun ha begrenset tilgang til kontoen din. self_destruct: Av di %{domain} er i ferd med å stenga, vil du berre få avgrensa tilgang til brukarkontoen din.
view_strikes: Vis tidligere advarsler mot kontoen din view_strikes: Vis tidligere advarsler mot kontoen din
too_fast: Skjemaet ble sendt inn for raskt, prøv på nytt. too_fast: Skjemaet ble sendt inn for raskt, prøv på nytt.
use_security_key: Bruk sikkerhetsnøkkel use_security_key: Bruk sikkerhetsnøkkel
@ -1583,8 +1583,8 @@ nn:
over_total_limit: Du har overskredet grensen på %{limit} planlagte tuter over_total_limit: Du har overskredet grensen på %{limit} planlagte tuter
too_soon: Den planlagte datoen må være i fremtiden too_soon: Den planlagte datoen må være i fremtiden
self_destruct: self_destruct:
lead_html: Dessverre stenger <strong>%{domain}</strong> for alltid. Hvis du hadde en konto der vil du ikke kunne fortsette å bruke den, men du kan fremdeles be om en sikkerhetskopi av dataene dine. lead_html: Diverre stengjer <strong>%{domain}</strong> dørene for godt. Viss du hadde ein brukarkonto der, vil du ikkje kunna halda fram å bruka han, men du kan få ut ein tryggingskopi av dataa dine.
title: Denne serveren stenger title: Denne tenaren stengjer
sessions: sessions:
activity: Siste aktivitet activity: Siste aktivitet
browser: Nettlesar browser: Nettlesar

View file

@ -1533,7 +1533,7 @@
posting_defaults: Innleggsstandarder posting_defaults: Innleggsstandarder
public_timelines: Offentlige tidslinjer public_timelines: Offentlige tidslinjer
privacy: privacy:
hint_html: "<strong>Tilpass hvordan du vil at din profil og dine innlegg skal bli funnet.</strong> En rekke funksjoner i Mastodon kan hjelpe deg med å nå et bredere publikum når de aktiverte. Ta deg et øyeblikk til å vurdere disse innstillingene for å forsikre deg om at de passer deg og ditt bruk." hint_html: "<strong>Tilpass hvordan du vil at din profil og dine innlegg skal bli funnet.</strong> En rekke funksjoner i Mastodon kan hjelpe deg med å nå et bredere publikum når det er aktivert. Ta deg et øyeblikk til å vurdere disse innstillingene for å forsikre deg om at de passer deg og ditt bruk."
privacy: Personvern privacy: Personvern
privacy_hint_html: Kontrollér hvor mye du ønsker å dele til fordel for andre. Folk oppdager interessante profiler og kule app'er ved å bla gjennom andres følgere og ved å se hvilke app'er de bruker, men du kan velge å holde det skjult. privacy_hint_html: Kontrollér hvor mye du ønsker å dele til fordel for andre. Folk oppdager interessante profiler og kule app'er ved å bla gjennom andres følgere og ved å se hvilke app'er de bruker, men du kan velge å holde det skjult.
reach: Rekkevidde reach: Rekkevidde

View file

@ -623,6 +623,7 @@ sr:
created_at: Пријављена created_at: Пријављена
delete_and_resolve: Обриши објаве delete_and_resolve: Обриши објаве
forwarded: Прослеђено forwarded: Прослеђено
forwarded_replies_explanation: Овај извештај је од удаљеног корисника и о удаљеном садржају. Прослеђен вам је јер је пријављени садржај у одговору једном од ваших корисника.
forwarded_to: Прослеђено ка %{domain} forwarded_to: Прослеђено ка %{domain}
mark_as_resolved: Означи као решену mark_as_resolved: Означи као решену
mark_as_sensitive: Обележи као осетљиво mark_as_sensitive: Обележи као осетљиво

View file

@ -560,7 +560,7 @@ th:
enabled: เปิดใช้งานอยู่ enabled: เปิดใช้งานอยู่
inbox_url: URL ของรีเลย์ inbox_url: URL ของรีเลย์
pending: กำลังรอการอนุมัติของรีเลย์ pending: กำลังรอการอนุมัติของรีเลย์
save_and_enable: บันทึกแล้วเปิดใช้งาน save_and_enable: บันทึกแลเปิดใช้งาน
setup: ตั้งค่าการเชื่อมต่อรีเลย์ setup: ตั้งค่าการเชื่อมต่อรีเลย์
signatures_not_enabled: รีเลย์อาจทำงานไม่ถูกต้องขณะที่มีการเปิดใช้งานโหมดปลอดภัยหรือโหมดการติดต่อกับภายนอกแบบจำกัด signatures_not_enabled: รีเลย์อาจทำงานไม่ถูกต้องขณะที่มีการเปิดใช้งานโหมดปลอดภัยหรือโหมดการติดต่อกับภายนอกแบบจำกัด
status: สถานะ status: สถานะ
@ -599,7 +599,7 @@ th:
created_at: รายงานเมื่อ created_at: รายงานเมื่อ
delete_and_resolve: ลบโพสต์ delete_and_resolve: ลบโพสต์
forwarded: ส่งต่อแล้ว forwarded: ส่งต่อแล้ว
forwarded_replies_explanation: รายงานนี้มาจากผู้ใช้ระยะไกล และเป็นรายงานเกี่ยวกับเนื้อหาระยะไกล ซึ่งถูกส่งต่อมาหาคุณเนื่องจากเนื้อหาที่ถูกรายงานอยู่ในการตอบกลับไปยังหนึ่งในผู้ใช้ของคุณ forwarded_replies_explanation: รายงานนี้มาจากผู้ใช้ระยะไกลและเกี่ยวกับเนื้อหาระยะไกล มีการส่งต่อรายงานไปยังคุณเนื่องจากเนื้อหาที่รายงานอยู่ในการตอบกลับหนึ่งในผู้ใช้ของคุณ
forwarded_to: ส่งต่อไปยัง %{domain} แล้ว forwarded_to: ส่งต่อไปยัง %{domain} แล้ว
mark_as_resolved: ทำเครื่องหมายว่าแก้ปัญหาแล้ว mark_as_resolved: ทำเครื่องหมายว่าแก้ปัญหาแล้ว
mark_as_sensitive: ทำเครื่องหมายว่าละเอียดอ่อน mark_as_sensitive: ทำเครื่องหมายว่าละเอียดอ่อน

View file

@ -611,6 +611,7 @@ tr:
created_at: Şikayet edildi created_at: Şikayet edildi
delete_and_resolve: Gönderileri sil delete_and_resolve: Gönderileri sil
forwarded: İletildi forwarded: İletildi
forwarded_replies_explanation: Bu bildirim başka bir sunucudaki kullanıcı ve içerik ile ilgili. Bildirilen içerik kullanıcılarınızdan birine yanıt şeklinde olduğu için size yönlendirildi.
forwarded_to: "%{domain}'e iletildi" forwarded_to: "%{domain}'e iletildi"
mark_as_resolved: Giderildi olarak işaretle mark_as_resolved: Giderildi olarak işaretle
mark_as_sensitive: Hassas olarak işaretle mark_as_sensitive: Hassas olarak işaretle

View file

@ -599,6 +599,7 @@ zh-CN:
created_at: 举报时间 created_at: 举报时间
delete_and_resolve: 删除嘟文 delete_and_resolve: 删除嘟文
forwarded: 已转发 forwarded: 已转发
forwarded_replies_explanation: 该举报来自外站用户,涉及外站内容。之所以转发给您,是因为被举报的内容是对您站点一位用户的回复。
forwarded_to: 转发举报至 %{domain} forwarded_to: 转发举报至 %{domain}
mark_as_resolved: 标记为已处理 mark_as_resolved: 标记为已处理
mark_as_sensitive: 标记为敏感内容 mark_as_sensitive: 标记为敏感内容

View file

@ -9,13 +9,3 @@ if Rake::Task.task_defined?('spec:system')
Rake::Task['spec:system'].enhance ['spec:enable_system_specs'] Rake::Task['spec:system'].enhance ['spec:enable_system_specs']
end end
if Rake::Task.task_defined?('spec:search')
namespace :spec do
task :enable_search_specs do # rubocop:disable Rails/RakeEnvironment
ENV['RUN_SEARCH_SPECS'] = 'true'
end
end
Rake::Task['spec:search'].enhance ['spec:enable_search_specs']
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
Fabricator(:account_deletion_request) do
account
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
Fabricator(:import) do
account
type :following
data { attachment_fixture('imports.txt') }
end

View file

@ -13,14 +13,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en) recipient.user.update(locale: :en)
end end
it 'renders the headers' do it 'renders the email' do
expect(mail.subject).to eq("New report for cb6e6126.ngrok.io (##{report.id})") expect(mail)
expect(mail.to).to eq [recipient.user_email] .to be_present
expect(mail.from).to eq ['notifications@localhost'] .and(deliver_to(recipient.user_email))
end .and(deliver_from('notifications@localhost'))
.and(have_subject("New report for cb6e6126.ngrok.io (##{report.id})"))
it 'renders the body' do .and(have_body_text("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n"))
expect(mail.body.encoded).to eq("Mike,\r\n\r\nJohn has reported Mike\r\n\r\nView: https://cb6e6126.ngrok.io/admin/reports/#{report.id}\r\n")
end end
end end
@ -33,14 +32,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en) recipient.user.update(locale: :en)
end end
it 'renders the headers' do it 'renders the email' do
expect(mail.subject).to eq("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io") expect(mail)
expect(mail.to).to eq [recipient.user_email] .to be_present
expect(mail.from).to eq ['notifications@localhost'] .and(deliver_to(recipient.user_email))
end .and(deliver_from('notifications@localhost'))
.and(have_subject("#{appeal.account.username} is appealing a moderation decision on cb6e6126.ngrok.io"))
it 'renders the body' do .and(have_body_text("#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"))
expect(mail.body.encoded).to match "#{appeal.account.username} is appealing a moderation decision by #{appeal.strike.account.username}"
end end
end end
@ -53,14 +51,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en) recipient.user.update(locale: :en)
end end
it 'renders the headers' do it 'renders the email' do
expect(mail.subject).to eq("New account up for review on cb6e6126.ngrok.io (#{user.account.username})") expect(mail)
expect(mail.to).to eq [recipient.user_email] .to be_present
expect(mail.from).to eq ['notifications@localhost'] .and(deliver_to(recipient.user_email))
end .and(deliver_from('notifications@localhost'))
.and(have_subject("New account up for review on cb6e6126.ngrok.io (#{user.account.username})"))
it 'renders the body' do .and(have_body_text('The details of the new account are below. You can approve or reject this application.'))
expect(mail.body.encoded).to match 'The details of the new account are below. You can approve or reject this application.'
end end
end end
@ -95,14 +92,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en) recipient.user.update(locale: :en)
end end
it 'renders the headers' do it 'renders the email' do
expect(mail.subject).to eq('New trends up for review on cb6e6126.ngrok.io') expect(mail)
expect(mail.to).to eq [recipient.user_email] .to be_present
expect(mail.from).to eq ['notifications@localhost'] .and(deliver_to(recipient.user_email))
end .and(deliver_from('notifications@localhost'))
.and(have_subject('New trends up for review on cb6e6126.ngrok.io'))
it 'renders the body' do .and(have_body_text('The following items need a review before they can be displayed publicly'))
expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly'
end end
end end
@ -114,14 +110,13 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en) recipient.user.update(locale: :en)
end end
it 'renders the headers' do it 'renders the email' do
expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!') expect(mail)
expect(mail.to).to eq [recipient.user_email] .to be_present
expect(mail.from).to eq ['notifications@localhost'] .and(deliver_to(recipient.user_email))
end .and(deliver_from('notifications@localhost'))
.and(have_subject('New Mastodon versions are available for cb6e6126.ngrok.io!'))
it 'renders the body' do .and(have_body_text('New Mastodon versions have been released, you may want to update!'))
expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!'
end end
end end
@ -133,18 +128,16 @@ RSpec.describe AdminMailer do
recipient.user.update(locale: :en) recipient.user.update(locale: :en)
end end
it 'renders the headers', :aggregate_failures do it 'renders the email' do
expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!') expect(mail)
expect(mail.to).to eq [recipient.user_email] .to be_present
expect(mail.from).to eq ['notifications@localhost'] .and(deliver_to(recipient.user_email))
.and(deliver_from('notifications@localhost'))
expect(mail['Importance'].value).to eq 'high' .and(have_subject('Critical Mastodon updates are available for cb6e6126.ngrok.io!'))
expect(mail['Priority'].value).to eq 'urgent' .and(have_body_text('New critical versions of Mastodon have been released, you may want to update as soon as possible!'))
expect(mail['X-Priority'].value).to eq '1' .and(have_header('Importance', 'high'))
end .and(have_header('Priority', 'urgent'))
.and(have_header('X-Priority', '1'))
it 'renders the body' do
expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!'
end end
end end
end end

View file

@ -8,24 +8,27 @@ RSpec.describe NotificationMailer do
let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') } let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') } let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
shared_examples 'headers' do |type, thread| shared_examples 'standard headers' do |type|
it 'renders the to and from headers' do it 'renders the email' do
expect(mail[:to].value).to eq "#{receiver.account.username} <#{receiver.email}>" expect(mail)
expect(mail.from).to eq ['notifications@localhost'] .to be_present
.and(have_header('To', "#{receiver.account.username} <#{receiver.email}>"))
.and(have_header('List-ID', "<#{type}.alice.cb6e6126.ngrok.io>"))
.and(have_header('List-Unsubscribe', %r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>}))
.and(have_header('List-Unsubscribe', /&type=#{type}/))
.and(have_header('List-Unsubscribe-Post', 'List-Unsubscribe=One-Click'))
.and(deliver_to("#{receiver.account.username} <#{receiver.email}>"))
.and(deliver_from('notifications@localhost'))
end
end end
it 'renders the list headers' do shared_examples 'thread headers' do
expect(mail['List-ID'].value).to eq "<#{type}.alice.cb6e6126.ngrok.io>" it 'renders the email with conversation thread headers' do
expect(mail['List-Unsubscribe'].value).to match(%r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>}) conversation_header_regex = /<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/
expect(mail['List-Unsubscribe'].value).to match("&type=#{type}") expect(mail)
expect(mail['List-Unsubscribe-Post'].value).to eq 'List-Unsubscribe=One-Click' .to be_present
end .and(have_header('In-Reply-To', conversation_header_regex))
.and(have_header('References', conversation_header_regex))
if thread
it 'renders the thread headers' do
expect(mail['In-Reply-To'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
expect(mail['References'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
end
end end
end end
@ -35,15 +38,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).mention } let(:mail) { prepared_mailer_for(receiver.account).mention }
include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
include_examples 'headers', 'mention', true include_examples 'standard headers', 'mention'
include_examples 'thread headers'
it 'renders the subject' do it 'renders the email' do
expect(mail.subject).to eq('You were mentioned by bob') expect(mail)
end .to be_present
.and(have_subject('You were mentioned by bob'))
it 'renders the body' do .and(have_body_text('You were mentioned by bob'))
expect(mail.body.encoded).to match('You were mentioned by bob') .and(have_body_text('The body of the foreign status'))
expect(mail.body.encoded).to include 'The body of the foreign status'
end end
end end
@ -53,14 +56,13 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).follow } let(:mail) { prepared_mailer_for(receiver.account).follow }
include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
include_examples 'headers', 'follow', false include_examples 'standard headers', 'follow'
it 'renders the subject' do it 'renders the email' do
expect(mail.subject).to eq('bob is now following you') expect(mail)
end .to be_present
.and(have_subject('bob is now following you'))
it 'renders the body' do .and(have_body_text('bob is now following you'))
expect(mail.body.encoded).to match('bob is now following you')
end end
end end
@ -70,15 +72,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(own_status.account).favourite } let(:mail) { prepared_mailer_for(own_status.account).favourite }
include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
include_examples 'headers', 'favourite', true include_examples 'standard headers', 'favourite'
include_examples 'thread headers'
it 'renders the subject' do it 'renders the email' do
expect(mail.subject).to eq('bob favorited your post') expect(mail)
end .to be_present
.and(have_subject('bob favorited your post'))
it 'renders the body' do .and(have_body_text('Your post was favorited by bob'))
expect(mail.body.encoded).to match('Your post was favorited by bob') .and(have_body_text('The body of the own status'))
expect(mail.body.encoded).to include 'The body of the own status'
end end
end end
@ -88,15 +90,15 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(own_status.account).reblog } let(:mail) { prepared_mailer_for(own_status.account).reblog }
include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
include_examples 'headers', 'reblog', true include_examples 'standard headers', 'reblog'
include_examples 'thread headers'
it 'renders the subject' do it 'renders the email' do
expect(mail.subject).to eq('bob boosted your post') expect(mail)
end .to be_present
.and(have_subject('bob boosted your post'))
it 'renders the body' do .and(have_body_text('Your post was boosted by bob'))
expect(mail.body.encoded).to match('Your post was boosted by bob') .and(have_body_text('The body of the own status'))
expect(mail.body.encoded).to include 'The body of the own status'
end end
end end
@ -106,14 +108,13 @@ RSpec.describe NotificationMailer do
let(:mail) { prepared_mailer_for(receiver.account).follow_request } let(:mail) { prepared_mailer_for(receiver.account).follow_request }
include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob' include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'
include_examples 'headers', 'follow_request', false include_examples 'standard headers', 'follow_request'
it 'renders the subject' do it 'renders the email' do
expect(mail.subject).to eq('Pending follower: bob') expect(mail)
end .to be_present
.and(have_subject('Pending follower: bob'))
it 'renders the body' do .and(have_body_text('bob has requested to follow you'))
expect(mail.body.encoded).to match('bob has requested to follow you')
end end
end end

View file

@ -10,9 +10,12 @@ describe UserMailer do
it 'renders confirmation instructions' do it 'renders confirmation instructions' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.confirmation_instructions.title')
expect(mail.body.encoded).to include 'spec' expect(mail)
expect(mail.body.encoded).to include Rails.configuration.x.local_domain .to be_present
.and(have_body_text(I18n.t('devise.mailer.confirmation_instructions.title')))
.and(have_body_text('spec'))
.and(have_body_text(Rails.configuration.x.local_domain))
end end
include_examples 'localized subject', include_examples 'localized subject',
@ -25,13 +28,17 @@ describe UserMailer do
it 'renders reconfirmation instructions' do it 'renders reconfirmation instructions' do
receiver.update!(email: 'new-email@example.com', locale: nil) receiver.update!(email: 'new-email@example.com', locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.reconfirmation_instructions.title')
expect(mail.body.encoded).to include 'spec' expect(mail)
expect(mail.body.encoded).to include Rails.configuration.x.local_domain .to be_present
expect(mail.subject).to eq I18n.t('devise.mailer.reconfirmation_instructions.subject', .and(have_body_text(I18n.t('devise.mailer.reconfirmation_instructions.title')))
instance: Rails.configuration.x.local_domain, .and(have_body_text('spec'))
locale: I18n.default_locale) .and(have_body_text(Rails.configuration.x.local_domain))
end end
include_examples 'localized subject',
'devise.mailer.confirmation_instructions.subject',
instance: Rails.configuration.x.local_domain
end end
describe '#reset_password_instructions' do describe '#reset_password_instructions' do
@ -39,8 +46,11 @@ describe UserMailer do
it 'renders reset password instructions' do it 'renders reset password instructions' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.reset_password_instructions.title')
expect(mail.body.encoded).to include 'spec' expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.reset_password_instructions.title')))
.and(have_body_text('spec'))
end end
include_examples 'localized subject', include_examples 'localized subject',
@ -52,7 +62,10 @@ describe UserMailer do
it 'renders password change notification' do it 'renders password change notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.password_change.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.password_change.title')))
end end
include_examples 'localized subject', include_examples 'localized subject',
@ -64,7 +77,10 @@ describe UserMailer do
it 'renders email change notification' do it 'renders email change notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.email_changed.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.email_changed.title')))
end end
include_examples 'localized subject', include_examples 'localized subject',
@ -77,8 +93,11 @@ describe UserMailer do
it 'renders warning notification' do it 'renders warning notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)
expect(mail.body.encoded).to include strike.text expect(mail)
.to be_present
.and(have_body_text(I18n.t('user_mailer.warning.title.suspend', acct: receiver.account.acct)))
.and(have_body_text(strike.text))
end end
end end
@ -88,7 +107,10 @@ describe UserMailer do
it 'renders webauthn credential deleted notification' do it 'renders webauthn credential deleted notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.deleted.title')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('devise.mailer.webauthn_credential.deleted.title')))
end end
include_examples 'localized subject', include_examples 'localized subject',
@ -103,7 +125,10 @@ describe UserMailer do
it 'renders suspicious sign in notification' do it 'renders suspicious sign in notification' do
receiver.update!(locale: nil) receiver.update!(locale: nil)
expect(mail.body.encoded).to include I18n.t('user_mailer.suspicious_sign_in.explanation')
expect(mail)
.to be_present
.and(have_body_text(I18n.t('user_mailer.suspicious_sign_in.explanation')))
end end
include_examples 'localized subject', include_examples 'localized subject',
@ -115,8 +140,10 @@ describe UserMailer do
let(:mail) { described_class.appeal_approved(receiver, appeal) } let(:mail) { described_class.appeal_approved(receiver, appeal) }
it 'renders appeal_approved notification' do it 'renders appeal_approved notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at)) expect(mail)
expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_approved.title') .to be_present
.and(have_subject(I18n.t('user_mailer.appeal_approved.subject', date: I18n.l(appeal.created_at))))
.and(have_body_text(I18n.t('user_mailer.appeal_approved.title')))
end end
end end
@ -125,8 +152,10 @@ describe UserMailer do
let(:mail) { described_class.appeal_rejected(receiver, appeal) } let(:mail) { described_class.appeal_rejected(receiver, appeal) }
it 'renders appeal_rejected notification' do it 'renders appeal_rejected notification' do
expect(mail.subject).to eq I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at)) expect(mail)
expect(mail.body.encoded).to include I18n.t('user_mailer.appeal_rejected.title') .to be_present
.and(have_subject(I18n.t('user_mailer.appeal_rejected.subject', date: I18n.l(appeal.created_at))))
.and(have_body_text(I18n.t('user_mailer.appeal_rejected.title')))
end end
end end
@ -134,8 +163,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_enabled(receiver) } let(:mail) { described_class.two_factor_enabled(receiver) }
it 'renders two_factor_enabled mail' do it 'renders two_factor_enabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_enabled.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_enabled.explanation') .to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_enabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_enabled.explanation')))
end end
end end
@ -143,8 +174,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_disabled(receiver) } let(:mail) { described_class.two_factor_disabled(receiver) }
it 'renders two_factor_disabled mail' do it 'renders two_factor_disabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_disabled.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_disabled.explanation') .to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_disabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_disabled.explanation')))
end end
end end
@ -152,8 +185,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_enabled(receiver) } let(:mail) { described_class.webauthn_enabled(receiver) }
it 'renders webauthn_enabled mail' do it 'renders webauthn_enabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_enabled.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_enabled.explanation') .to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_enabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_enabled.explanation')))
end end
end end
@ -161,8 +196,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_disabled(receiver) } let(:mail) { described_class.webauthn_disabled(receiver) }
it 'renders webauthn_disabled mail' do it 'renders webauthn_disabled mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_disabled.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_disabled.explanation') .to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_disabled.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_disabled.explanation')))
end end
end end
@ -170,8 +207,10 @@ describe UserMailer do
let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) } let(:mail) { described_class.two_factor_recovery_codes_changed(receiver) }
it 'renders two_factor_recovery_codes_changed mail' do it 'renders two_factor_recovery_codes_changed mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation') .to be_present
.and(have_subject(I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')))
.and(have_body_text(I18n.t('devise.mailer.two_factor_recovery_codes_changed.explanation')))
end end
end end
@ -180,8 +219,10 @@ describe UserMailer do
let(:mail) { described_class.webauthn_credential_added(receiver, credential) } let(:mail) { described_class.webauthn_credential_added(receiver, credential) }
it 'renders webauthn_credential_added mail' do it 'renders webauthn_credential_added mail' do
expect(mail.subject).to eq I18n.t('devise.mailer.webauthn_credential.added.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('devise.mailer.webauthn_credential.added.explanation') .to be_present
.and(have_subject(I18n.t('devise.mailer.webauthn_credential.added.subject')))
.and(have_body_text(I18n.t('devise.mailer.webauthn_credential.added.explanation')))
end end
end end
@ -189,8 +230,10 @@ describe UserMailer do
let(:mail) { described_class.welcome(receiver) } let(:mail) { described_class.welcome(receiver) }
it 'renders welcome mail' do it 'renders welcome mail' do
expect(mail.subject).to eq I18n.t('user_mailer.welcome.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('user_mailer.welcome.explanation') .to be_present
.and(have_subject(I18n.t('user_mailer.welcome.subject')))
.and(have_body_text(I18n.t('user_mailer.welcome.explanation')))
end end
end end
@ -199,8 +242,10 @@ describe UserMailer do
let(:mail) { described_class.backup_ready(receiver, backup) } let(:mail) { described_class.backup_ready(receiver, backup) }
it 'renders backup_ready mail' do it 'renders backup_ready mail' do
expect(mail.subject).to eq I18n.t('user_mailer.backup_ready.subject') expect(mail)
expect(mail.body.encoded).to include I18n.t('user_mailer.backup_ready.explanation') .to be_present
.and(have_subject(I18n.t('user_mailer.backup_ready.subject')))
.and(have_body_text(I18n.t('user_mailer.backup_ready.explanation')))
end end
end end
end end

View file

@ -23,12 +23,14 @@ RSpec.describe AccountRelationshipsPresenter do
let(:options) { {} } let(:options) { {} }
it 'sets default maps' do it 'sets default maps' do
expect(presenter.following).to eq default_map expect(presenter).to have_attributes(
expect(presenter.followed_by).to eq default_map following: default_map,
expect(presenter.blocking).to eq default_map followed_by: default_map,
expect(presenter.muting).to eq default_map blocking: default_map,
expect(presenter.requested).to eq default_map muting: default_map,
expect(presenter.domain_blocking).to eq default_map requested: default_map,
domain_blocking: default_map
)
end end
end end

View file

@ -22,9 +22,12 @@ RSpec.describe FamiliarFollowersPresenter do
it 'returns followers you follow' do it 'returns followers you follow' do
result = subject.accounts.first result = subject.accounts.first
expect(result).to_not be_nil expect(result)
expect(result.id).to eq requested_accounts.first.id .to be_present
expect(result.accounts).to contain_exactly(familiar_follower) .and have_attributes(
id: requested_accounts.first.id,
accounts: contain_exactly(familiar_follower)
)
end end
context 'when requested account hides followers' do context 'when requested account hides followers' do
@ -35,9 +38,12 @@ RSpec.describe FamiliarFollowersPresenter do
it 'does not return followers you follow' do it 'does not return followers you follow' do
result = subject.accounts.first result = subject.accounts.first
expect(result).to_not be_nil expect(result)
expect(result.id).to eq requested_accounts.first.id .to be_present
expect(result.accounts).to be_empty .and have_attributes(
id: requested_accounts.first.id,
accounts: be_empty
)
end end
end end
@ -49,9 +55,12 @@ RSpec.describe FamiliarFollowersPresenter do
it 'does not return followers you follow' do it 'does not return followers you follow' do
result = subject.accounts.first result = subject.accounts.first
expect(result).to_not be_nil expect(result)
expect(result.id).to eq requested_accounts.first.id .to be_present
expect(result.accounts).to be_empty .and have_attributes(
id: requested_accounts.first.id,
accounts: be_empty
)
end end
end end
end end

View file

@ -22,11 +22,13 @@ RSpec.describe StatusRelationshipsPresenter do
let(:options) { {} } let(:options) { {} }
it 'sets default maps' do it 'sets default maps' do
expect(presenter.reblogs_map).to eq default_map expect(presenter).to have_attributes(
expect(presenter.favourites_map).to eq default_map reblogs_map: eq(default_map),
expect(presenter.bookmarks_map).to eq default_map favourites_map: eq(default_map),
expect(presenter.mutes_map).to eq default_map bookmarks_map: eq(default_map),
expect(presenter.pins_map).to eq default_map mutes_map: eq(default_map),
pins_map: eq(default_map)
)
end end
end end
@ -80,18 +82,30 @@ RSpec.describe StatusRelationshipsPresenter do
it 'sets @filters_map to filter top-level status' do it 'sets @filters_map to filter top-level status' do
matched_filters = presenter.filters_map[statuses[0].id] matched_filters = presenter.filters_map[statuses[0].id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1' expect(matched_filters)
expect(matched_filters[0].keyword_matches).to eq ['banned'] .to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
keyword_matches: contain_exactly('banned')
)
)
end end
it 'sets @filters_map to filter reblogged status' do it 'sets @filters_map to filter reblogged status' do
matched_filters = presenter.filters_map[statuses[1].reblog_of_id] matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1' expect(matched_filters)
expect(matched_filters[0].keyword_matches).to eq ['irrelevant'] .to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
keyword_matches: contain_exactly('irrelevant')
)
)
end end
end end
@ -107,18 +121,30 @@ RSpec.describe StatusRelationshipsPresenter do
it 'sets @filters_map to filter top-level status' do it 'sets @filters_map to filter top-level status' do
matched_filters = presenter.filters_map[statuses[0].id] matched_filters = presenter.filters_map[statuses[0].id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1' expect(matched_filters)
expect(matched_filters[0].status_matches).to eq [statuses[0].id] .to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
status_matches: contain_exactly(statuses.first.id)
)
)
end end
it 'sets @filters_map to filter reblogged status' do it 'sets @filters_map to filter reblogged status' do
matched_filters = presenter.filters_map[statuses[1].reblog_of_id] matched_filters = presenter.filters_map[statuses[1].reblog_of_id]
expect(matched_filters.size).to eq 1
expect(matched_filters[0].filter.title).to eq 'filter1' expect(matched_filters)
expect(matched_filters[0].status_matches).to eq [statuses[1].reblog_of_id] .to be_an(Array)
.and have_attributes(size: 1)
.and contain_exactly(
have_attributes(
filter: have_attributes(title: 'filter1'),
status_matches: contain_exactly(statuses.second.reblog_of_id)
)
)
end end
end end
end end

View file

@ -4,7 +4,6 @@ ENV['RAILS_ENV'] ||= 'test'
# This needs to be defined before Rails is initialized # This needs to be defined before Rails is initialized
RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false) RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false)
RUN_SEARCH_SPECS = ENV.fetch('RUN_SEARCH_SPECS', false)
if RUN_SYSTEM_SPECS if RUN_SYSTEM_SPECS
STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020') STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
@ -21,6 +20,7 @@ require 'webmock/rspec'
require 'paperclip/matchers' require 'paperclip/matchers'
require 'capybara/rspec' require 'capybara/rspec'
require 'chewy/rspec' require 'chewy/rspec'
require 'email_spec/rspec'
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
@ -54,20 +54,28 @@ RSpec.configure do |config|
case type case type
when :system when :system
!RUN_SYSTEM_SPECS !RUN_SYSTEM_SPECS
when :search
!RUN_SEARCH_SPECS
end end
} }
# By default, skip the elastic search integration specs
config.filter_run_excluding search: true
config.fixture_path = Rails.root.join('spec', 'fixtures') config.fixture_path = Rails.root.join('spec', 'fixtures')
config.use_transactional_fixtures = true config.use_transactional_fixtures = true
config.order = 'random' config.order = 'random'
config.infer_spec_type_from_file_location! config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace! config.filter_rails_from_backtrace!
# Set type to `cli` for all CLI specs
config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata| config.define_derived_metadata(file_path: Regexp.new('spec/lib/mastodon/cli')) do |metadata|
metadata[:type] = :cli metadata[:type] = :cli
end end
# Set `search` metadata true for all specs in spec/search/
config.define_derived_metadata(file_path: Regexp.new('spec/search/*')) do |metadata|
metadata[:search] = true
end
config.include Devise::Test::ControllerHelpers, type: :controller config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :helper config.include Devise::Test::ControllerHelpers, type: :helper
config.include Devise::Test::ControllerHelpers, type: :view config.include Devise::Test::ControllerHelpers, type: :view

View file

@ -1,53 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'GET /api/v1/accounts/{account_id}' do
it 'returns account entity as 200 OK' do
account = Fabricate(:account)
get "/api/v1/accounts/#{account.id}"
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 404 if account not found' do
get '/api/v1/accounts/1'
aggregate_failures do
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when with token' do
it 'returns account entity as 200 OK if token is valid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end
it 'returns 403 if scope of token is invalid' do
account = Fabricate(:account)
user = Fabricate(:user, account: account)
token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token
get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" }
aggregate_failures do
expect(response).to have_http_status(403)
expect(body_as_json[:error]).to eq('This action is outside the authorized scopes')
end
end
end
end

View file

@ -2,65 +2,108 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Api::V1::AccountsController do describe '/api/v1/accounts' do
render_views
let(:user) { Fabricate(:user) } let(:user) { Fabricate(:user) }
let(:scopes) { '' } let(:scopes) { '' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
before do describe 'GET /api/v1/accounts/:id' do
allow(controller).to receive(:doorkeeper_token) { token } context 'when logged out' do
let(:account) { Fabricate(:account) }
it 'returns account entity as 200 OK', :aggregate_failures do
get "/api/v1/accounts/#{account.id}"
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
end end
describe 'POST #create' do context 'when the account does not exist' do
let(:app) { Fabricate(:application) } it 'returns http not found' do
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) } get '/api/v1/accounts/1'
expect(response).to have_http_status(404)
expect(body_as_json[:error]).to eq('Record not found')
end
end
context 'when logged in' do
subject do
get "/api/v1/accounts/#{account.id}", headers: headers
end
let(:account) { Fabricate(:account) }
let(:scopes) { 'read:accounts' }
it 'returns account entity as 200 OK', :aggregate_failures do
subject
expect(response).to have_http_status(200)
expect(body_as_json[:id]).to eq(account.id.to_s)
end
it_behaves_like 'forbidden for wrong scope', 'write:statuses'
end
end
describe 'POST /api/v1/accounts' do
subject do
post '/api/v1/accounts', headers: headers, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
end
let(:client_app) { Fabricate(:application) }
let(:token) { Doorkeeper::AccessToken.find_or_create_for(application: client_app, resource_owner: nil, scopes: 'read write', use_refresh_token: false) }
let(:agreement) { nil } let(:agreement) { nil }
before do
post :create, params: { username: 'test', password: '12345678', email: 'hello@world.tld', agreement: agreement }
end
context 'when given truthy agreement' do context 'when given truthy agreement' do
let(:agreement) { 'true' } let(:agreement) { 'true' }
it 'creates a user', :aggregate_failures do it 'creates a user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(body_as_json[:access_token]).to_not be_blank expect(body_as_json[:access_token]).to_not be_blank
user = User.find_by(email: 'hello@world.tld') user = User.find_by(email: 'hello@world.tld')
expect(user).to_not be_nil expect(user).to_not be_nil
expect(user.created_by_application_id).to eq app.id expect(user.created_by_application_id).to eq client_app.id
end end
end end
context 'when given no agreement' do context 'when given no agreement' do
it 'returns http unprocessable entity' do it 'returns http unprocessable entity' do
subject
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
end end
end end
end end
describe 'POST #follow' do describe 'POST /api/v1/accounts/:id/follow' do
let(:scopes) { 'write:follows' } let(:scopes) { 'write:follows' }
let(:my_actor_type) { 'Person' } let(:my_actor_type) { 'Person' }
let(:lock_follow_from_bot) { false } let(:lock_follow_from_bot) { false }
let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) } let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) }
context 'when posting to an other account' do context 'when posting to an other account' do
subject do
post "/api/v1/accounts/#{other_account.id}/follow", headers: headers
end
before do before do
other_account.user.settings['lock_follow_from_bot'] = lock_follow_from_bot other_account.user.settings['lock_follow_from_bot'] = lock_follow_from_bot
other_account.user.save! other_account.user.save!
user.account.update!(actor_type: my_actor_type) user.account.update!(actor_type: my_actor_type)
post :follow, params: { id: other_account.id }
end end
context 'with unlocked account' do context 'with unlocked account' do
let(:locked) { false } let(:locked) { false }
it 'creates a following relation between user and target user', :aggregate_failures do it 'creates a following relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
json = body_as_json json = body_as_json
@ -78,6 +121,8 @@ RSpec.describe Api::V1::AccountsController do
let(:locked) { true } let(:locked) { true }
it 'creates a follow request relation between user and target user', :aggregate_failures do it 'creates a follow request relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
json = body_as_json json = body_as_json
@ -97,10 +142,14 @@ RSpec.describe Api::V1::AccountsController do
let(:my_actor_type) { 'Service' } let(:my_actor_type) { 'Service' }
it 'returns http success' do it 'returns http success' do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns JSON with following=false and requested=true' do it 'returns JSON with following=false and requested=true' do
subject
json = body_as_json json = body_as_json
expect(json[:following]).to be false expect(json[:following]).to be false
@ -108,6 +157,8 @@ RSpec.describe Api::V1::AccountsController do
end end
it 'creates a follow request relation between user and target user' do it 'creates a follow request relation between user and target user' do
subject
expect(user.account.requested?(other_account)).to be true expect(user.account.requested?(other_account)).to be true
end end
@ -123,48 +174,53 @@ RSpec.describe Api::V1::AccountsController do
end end
it 'changes reblogs option' do it 'changes reblogs option' do
post :follow, params: { id: other_account.id, reblogs: true } post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { reblogs: true }
json = body_as_json expect(body_as_json).to include({
following: true,
expect(json[:following]).to be true showing_reblogs: true,
expect(json[:showing_reblogs]).to be true notifying: false,
expect(json[:notifying]).to be false })
end end
it 'changes notify option' do it 'changes notify option' do
post :follow, params: { id: other_account.id, notify: true } post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { notify: true }
json = body_as_json expect(body_as_json).to include({
following: true,
expect(json[:following]).to be true showing_reblogs: false,
expect(json[:showing_reblogs]).to be false notifying: true,
expect(json[:notifying]).to be true })
end end
it 'changes languages option' do it 'changes languages option' do
post :follow, params: { id: other_account.id, languages: %w(en es) } post "/api/v1/accounts/#{other_account.id}/follow", headers: headers, params: { languages: %w(en es) }
json = body_as_json expect(body_as_json).to include({
following: true,
expect(json[:following]).to be true showing_reblogs: false,
expect(json[:showing_reblogs]).to be false notifying: false,
expect(json[:notifying]).to be false languages: match_array(%w(en es)),
expect(json[:languages]).to match_array %w(en es) })
end end
end end
end end
describe 'POST #unfollow' do describe 'POST /api/v1/accounts/:id/unfollow' do
subject do
post "/api/v1/accounts/#{other_account.id}/unfollow", headers: headers
end
let(:scopes) { 'write:follows' } let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.follow!(other_account) user.account.follow!(other_account)
post :unfollow, params: { id: other_account.id }
end end
it 'removes the following relation between user and target user', :aggregate_failures do it 'removes the following relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be false expect(user.account.following?(other_account)).to be false
end end
@ -172,16 +228,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #remove_from_followers' do describe 'POST /api/v1/accounts/:id/remove_from_followers' do
subject do
post "/api/v1/accounts/#{other_account.id}/remove_from_followers", headers: headers
end
let(:scopes) { 'write:follows' } let(:scopes) { 'write:follows' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
other_account.follow!(user.account) other_account.follow!(user.account)
post :remove_from_followers, params: { id: other_account.id }
end end
it 'removes the followed relation between user and target user', :aggregate_failures do it 'removes the followed relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.followed_by?(other_account)).to be false expect(user.account.followed_by?(other_account)).to be false
end end
@ -189,16 +250,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #block' do describe 'POST /api/v1/accounts/:id/block' do
subject do
post "/api/v1/accounts/#{other_account.id}/block", headers: headers
end
let(:scopes) { 'write:blocks' } let(:scopes) { 'write:blocks' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.follow!(other_account) user.account.follow!(other_account)
post :block, params: { id: other_account.id }
end end
it 'creates a blocking relation', :aggregate_failures do it 'creates a blocking relation', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be false expect(user.account.following?(other_account)).to be false
expect(user.account.blocking?(other_account)).to be true expect(user.account.blocking?(other_account)).to be true
@ -207,16 +273,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #unblock' do describe 'POST /api/v1/accounts/:id/unblock' do
subject do
post "/api/v1/accounts/#{other_account.id}/unblock", headers: headers
end
let(:scopes) { 'write:blocks' } let(:scopes) { 'write:blocks' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.block!(other_account) user.account.block!(other_account)
post :unblock, params: { id: other_account.id }
end end
it 'removes the blocking relation between user and target user', :aggregate_failures do it 'removes the blocking relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.blocking?(other_account)).to be false expect(user.account.blocking?(other_account)).to be false
end end
@ -224,16 +295,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #mute' do describe 'POST /api/v1/accounts/:id/mute' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers
end
let(:scopes) { 'write:mutes' } let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.follow!(other_account) user.account.follow!(other_account)
post :mute, params: { id: other_account.id }
end end
it 'mutes notifications', :aggregate_failures do it 'mutes notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true expect(user.account.muting?(other_account)).to be true
@ -243,16 +319,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #mute with notifications set to false' do describe 'POST /api/v1/accounts/:id/mute with notifications set to false' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers, params: { notifications: false }
end
let(:scopes) { 'write:mutes' } let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.follow!(other_account) user.account.follow!(other_account)
post :mute, params: { id: other_account.id, notifications: false }
end end
it 'does not mute notifications', :aggregate_failures do it 'does not mute notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true expect(user.account.muting?(other_account)).to be true
@ -262,16 +343,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #mute with nonzero duration set' do describe 'POST /api/v1/accounts/:id/mute with nonzero duration set' do
subject do
post "/api/v1/accounts/#{other_account.id}/mute", headers: headers, params: { duration: 300 }
end
let(:scopes) { 'write:mutes' } let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.follow!(other_account) user.account.follow!(other_account)
post :mute, params: { id: other_account.id, duration: 300 }
end end
it 'mutes notifications', :aggregate_failures do it 'mutes notifications', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.following?(other_account)).to be true expect(user.account.following?(other_account)).to be true
expect(user.account.muting?(other_account)).to be true expect(user.account.muting?(other_account)).to be true
@ -281,16 +367,21 @@ RSpec.describe Api::V1::AccountsController do
it_behaves_like 'forbidden for wrong scope', 'read:accounts' it_behaves_like 'forbidden for wrong scope', 'read:accounts'
end end
describe 'POST #unmute' do describe 'POST /api/v1/accounts/:id/unmute' do
subject do
post "/api/v1/accounts/#{other_account.id}/unmute", headers: headers
end
let(:scopes) { 'write:mutes' } let(:scopes) { 'write:mutes' }
let(:other_account) { Fabricate(:account, username: 'bob') } let(:other_account) { Fabricate(:account, username: 'bob') }
before do before do
user.account.mute!(other_account) user.account.mute!(other_account)
post :unmute, params: { id: other_account.id }
end end
it 'removes the muting relation between user and target user', :aggregate_failures do it 'removes the muting relation between user and target user', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(user.account.muting?(other_account)).to be false expect(user.account.muting?(other_account)).to be false
end end

View file

@ -8,18 +8,12 @@ RSpec.describe 'Account actions' do
let(:scopes) { 'admin:write admin:write:accounts' } let(:scopes) { 'admin:write admin:write:accounts' }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
let(:mailer) { instance_double(ActionMailer::MessageDelivery, deliver_later!: nil) }
before do
allow(UserMailer).to receive(:warning).with(target_account.user, anything).and_return(mailer)
end
shared_examples 'a successful notification delivery' do shared_examples 'a successful notification delivery' do
it 'notifies the user about the action taken' do it 'notifies the user about the action taken' do
subject expect { subject }
.to have_enqueued_job(ActionMailer::MailDeliveryJob)
expect(UserMailer).to have_received(:warning).with(target_account.user, anything).once .with('UserMailer', 'warning', 'deliver_now!', args: [User, AccountWarning])
expect(mailer).to have_received(:deliver_later!).once
end end
end end

View file

@ -2,24 +2,26 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Api::V1::StatusesController do describe '/api/v1/statuses' do
render_views
let(:user) { Fabricate(:user) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: scopes) }
context 'with an oauth token' do context 'with an oauth token' do
before do let(:user) { Fabricate(:user) }
allow(controller).to receive(:doorkeeper_token) { token } let(:client_app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: client_app, scopes: scopes) }
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
describe 'GET /api/v1/statuses/:id' do
subject do
get "/api/v1/statuses/#{status.id}", headers: headers
end end
describe 'GET #show' do
let(:scopes) { 'read:statuses' } let(:scopes) { 'read:statuses' }
let(:status) { Fabricate(:status, account: user.account) } let(:status) { Fabricate(:status, account: user.account) }
it_behaves_like 'forbidden for wrong scope', 'write write:statuses'
it 'returns http success' do it 'returns http success' do
get :show, params: { id: status.id } subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
@ -31,11 +33,10 @@ RSpec.describe Api::V1::StatusesController do
end end
it 'returns filter information', :aggregate_failures do it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id } subject
json = body_as_json
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json[:filtered][0]).to include({ expect(body_as_json[:filtered][0]).to include({
filter: a_hash_including({ filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s, id: user.account.custom_filters.first.id.to_s,
title: 'filter1', title: 'filter1',
@ -55,11 +56,10 @@ RSpec.describe Api::V1::StatusesController do
end end
it 'returns filter information', :aggregate_failures do it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id } subject
json = body_as_json
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json[:filtered][0]).to include({ expect(body_as_json[:filtered][0]).to include({
filter: a_hash_including({ filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s, id: user.account.custom_filters.first.id.to_s,
title: 'filter1', title: 'filter1',
@ -78,11 +78,10 @@ RSpec.describe Api::V1::StatusesController do
end end
it 'returns filter information', :aggregate_failures do it 'returns filter information', :aggregate_failures do
get :show, params: { id: status.id } subject
json = body_as_json
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json[:reblog][:filtered][0]).to include({ expect(body_as_json[:reblog][:filtered][0]).to include({
filter: a_hash_including({ filter: a_hash_including({
id: user.account.custom_filters.first.id.to_s, id: user.account.custom_filters.first.id.to_s,
title: 'filter1', title: 'filter1',
@ -94,7 +93,7 @@ RSpec.describe Api::V1::StatusesController do
end end
end end
describe 'GET #context' do describe 'GET /api/v1/statuses/:id/context' do
let(:scopes) { 'read:statuses' } let(:scopes) { 'read:statuses' }
let(:status) { Fabricate(:status, account: user.account) } let(:status) { Fabricate(:status, account: user.account) }
let!(:thread) { Fabricate(:status, account: user.account, thread: status) } let!(:thread) { Fabricate(:status, account: user.account, thread: status) }
@ -142,7 +141,8 @@ RSpec.describe Api::V1::StatusesController do
end end
it 'returns http success' do it 'returns http success' do
get :context, params: { id: status.id } get "/api/v1/statuses/#{status.id}/context", headers: headers
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
@ -190,15 +190,20 @@ RSpec.describe Api::V1::StatusesController do
end end
end end
describe 'POST #create' do describe 'POST /api/v1/statuses' do
let(:scopes) { 'write:statuses' } subject do
post '/api/v1/statuses', headers: headers, params: params
context 'with a basic status body' do
before do
post :create, params: { status: 'Hello world' }
end end
let(:scopes) { 'write:statuses' }
let(:params) { { status: 'Hello world' } }
it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
context 'with a basic status body' do
it 'returns rate limit headers', :aggregate_failures do it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s
@ -209,22 +214,22 @@ RSpec.describe Api::V1::StatusesController do
let!(:alice) { Fabricate(:account, username: 'alice') } let!(:alice) { Fabricate(:account, username: 'alice') }
let!(:bob) { Fabricate(:account, username: 'bob') } let!(:bob) { Fabricate(:account, username: 'bob') }
before do let(:params) { { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] } }
post :create, params: { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] }
end
it 'returns serialized extra accounts in body', :aggregate_failures do it 'returns serialized extra accounts in body', :aggregate_failures do
subject
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }] expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }]
end end
end end
context 'with missing parameters' do context 'with missing parameters' do
before do let(:params) { {} }
post :create, params: {}
end
it 'returns rate limit headers', :aggregate_failures do it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(422) expect(response).to have_http_status(422)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
end end
@ -234,10 +239,11 @@ RSpec.describe Api::V1::StatusesController do
before do before do
rate_limiter = RateLimiter.new(user.account, family: :statuses) rate_limiter = RateLimiter.new(user.account, family: :statuses)
300.times { rate_limiter.record! } 300.times { rate_limiter.record! }
post :create, params: { status: 'Hello world' }
end end
it 'returns rate limit headers', :aggregate_failures do it 'returns rate limit headers', :aggregate_failures do
subject
expect(response).to have_http_status(429) expect(response).to have_http_status(429)
expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s
expect(response.headers['X-RateLimit-Remaining']).to eq '0' expect(response.headers['X-RateLimit-Remaining']).to eq '0'
@ -245,29 +251,37 @@ RSpec.describe Api::V1::StatusesController do
end end
end end
describe 'DELETE #destroy' do describe 'DELETE /api/v1/statuses/:id' do
subject do
delete "/api/v1/statuses/#{status.id}", headers: headers
end
let(:scopes) { 'write:statuses' } let(:scopes) { 'write:statuses' }
let(:status) { Fabricate(:status, account: user.account) } let(:status) { Fabricate(:status, account: user.account) }
before do it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
post :destroy, params: { id: status.id }
end
it 'removes the status', :aggregate_failures do it 'removes the status', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(Status.find_by(id: status.id)).to be_nil expect(Status.find_by(id: status.id)).to be_nil
end end
end end
describe 'PUT #update' do describe 'PUT /api/v1/statuses/:id' do
subject do
put "/api/v1/statuses/#{status.id}", headers: headers, params: { status: 'I am updated' }
end
let(:scopes) { 'write:statuses' } let(:scopes) { 'write:statuses' }
let(:status) { Fabricate(:status, account: user.account) } let(:status) { Fabricate(:status, account: user.account) }
before do it_behaves_like 'forbidden for wrong scope', 'read read:statuses'
put :update, params: { id: status.id, status: 'I am updated' }
end
it 'updates the status', :aggregate_failures do it 'updates the status', :aggregate_failures do
subject
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(status.reload.text).to eq 'I am updated' expect(status.reload.text).to eq 'I am updated'
end end
@ -275,49 +289,49 @@ RSpec.describe Api::V1::StatusesController do
end end
context 'without an oauth token' do context 'without an oauth token' do
before do
allow(controller).to receive(:doorkeeper_token).and_return(nil)
end
context 'with a private status' do context 'with a private status' do
let(:status) { Fabricate(:status, account: user.account, visibility: :private) } let(:status) { Fabricate(:status, visibility: :private) }
describe 'GET #show' do describe 'GET /api/v1/statuses/:id' do
it 'returns http unauthorized' do it 'returns http unauthorized' do
get :show, params: { id: status.id } get "/api/v1/statuses/#{status.id}"
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
describe 'GET #context' do describe 'GET /api/v1/statuses/:id/context' do
before do before do
Fabricate(:status, account: user.account, thread: status) Fabricate(:status, thread: status)
end end
it 'returns http unauthorized' do it 'returns http unauthorized' do
get :context, params: { id: status.id } get "/api/v1/statuses/#{status.id}/context"
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end
end end
context 'with a public status' do context 'with a public status' do
let(:status) { Fabricate(:status, account: user.account, visibility: :public) } let(:status) { Fabricate(:status, visibility: :public) }
describe 'GET #show' do describe 'GET /api/v1/statuses/:id' do
it 'returns http success' do it 'returns http success' do
get :show, params: { id: status.id } get "/api/v1/statuses/#{status.id}"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
end end
describe 'GET #context' do describe 'GET /api/v1/statuses/:id/context' do
before do before do
Fabricate(:status, account: user.account, thread: status) Fabricate(:status, thread: status)
end end
it 'returns http success' do it 'returns http success' do
get :context, params: { id: status.id } get "/api/v1/statuses/#{status.id}/context"
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
end end

View file

@ -60,7 +60,7 @@ RSpec.configure do |config|
end end
end end
config.around :each, type: :search do |example| config.around :each, :search do |example|
search_data_manager.populate_indexes search_data_manager.populate_indexes
example.run example.run
search_data_manager.remove_indexes search_data_manager.remove_indexes
@ -73,6 +73,6 @@ RSpec.configure do |config|
end end
def search_examples_present? def search_examples_present?
RUN_SEARCH_SPECS RSpec.world.filtered_examples.values.flatten.any? { |example| example.metadata[:search] == true }
end end
end end

View file

@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'rails_helper'
describe AccountRefreshWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ResolveAccountService, call: true) }
describe '#perform' do
before do
allow(ResolveAccountService).to receive(:new).and_return(service)
end
context 'when account does not exist' do
it 'returns immediately without processing' do
worker.perform(123_123_123)
expect(service).to_not have_received(:call)
end
end
context 'when account exists' do
context 'when account does not need refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: recent_webfinger_at) }
it 'returns immediately without processing' do
worker.perform(account.id)
expect(service).to_not have_received(:call)
end
end
context 'when account needs refreshing' do
let(:account) { Fabricate(:account, last_webfingered_at: outdated_webfinger_at) }
it 'schedules an account update' do
worker.perform(account.id)
expect(service).to have_received(:call)
end
end
def recent_webfinger_at
(Account::BACKGROUND_REFRESH_INTERVAL - 3.days).ago
end
def outdated_webfinger_at
(Account::BACKGROUND_REFRESH_INTERVAL + 3.days).ago
end
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::PostUpgradeWorker do
let(:worker) { described_class.new }
describe '#perform' do
let(:domain) { 'host.example' }
it 'updates relevant values' do
account = Fabricate(:account, domain: domain, last_webfingered_at: 1.day.ago, protocol: :ostatus)
worker.perform(domain)
expect(account.reload.last_webfingered_at).to be_nil
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
describe ActivityPub::SynchronizeFeaturedTagsCollectionWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ActivityPub::FetchFeaturedTagsCollectionService, call: true) }
describe '#perform' do
before do
allow(ActivityPub::FetchFeaturedTagsCollectionService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
let(:url) { 'https://host.example' }
it 'sends the account and url to the service' do
worker.perform(account.id, url)
expect(service).to have_received(:call).with(account, url)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, url)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::SuspensionWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(SuspendAccountService, call: true) }
describe '#perform' do
before do
allow(SuspendAccountService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
it 'sends the account to the service' do
worker.perform(account.id)
expect(service).to have_received(:call).with(account)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'rails_helper'
describe AfterAccountDomainBlockWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(AfterBlockDomainFromAccountService, call: true) }
describe '#perform' do
before do
allow(AfterBlockDomainFromAccountService).to receive(:new).and_return(service)
end
let(:account) { Fabricate(:account) }
let(:domain) { 'host.example' }
it 'sends the account and domain to the service' do
worker.perform(account.id, domain)
expect(service).to have_received(:call).with(account, domain)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, domain)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'rails_helper'
describe BackupWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(BackupService, call: true) }
describe '#perform' do
before do
allow(BackupService).to receive(:new).and_return(service)
end
let(:backup) { Fabricate(:backup) }
let!(:other_backup) { Fabricate(:backup, user: backup.user) }
it 'sends the backup to the service and removes other backups' do
expect do
worker.perform(backup.id)
end.to change(UserMailer.deliveries, :size).by(1)
expect(service).to have_received(:call).with(backup)
expect { other_backup.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
context 'when sidekiq retries are exhausted' do
it 'destroys the backup' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [backup.id] }) do
worker.perform(backup.id)
end
expect { backup.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'rails_helper'
describe DeleteMuteWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(UnmuteService, call: true) }
describe '#perform' do
before do
allow(UnmuteService).to receive(:new).and_return(service)
end
context 'with an expired mute' do
let(:mute) { Fabricate(:mute, expires_at: 1.day.ago) }
it 'sends the mute to the service' do
worker.perform(mute.id)
expect(service).to have_received(:call).with(mute.account, mute.target_account)
end
end
context 'with an unexpired mute' do
let(:mute) { Fabricate(:mute, expires_at: 1.day.from_now) }
it 'does not send the mute to the service' do
worker.perform(mute.id)
expect(service).to_not have_received(:call)
end
end
context 'with a non-existent mute' do
it 'does not send the mute to the service' do
worker.perform(123_123_123)
expect(service).to_not have_received(:call)
end
end
end
end

View file

@ -12,6 +12,7 @@ describe FeedInsertWorker do
describe 'perform' do describe 'perform' do
let(:follower) { Fabricate(:account) } let(:follower) { Fabricate(:account) }
let(:status) { Fabricate(:status) } let(:status) { Fabricate(:status) }
let(:list) { Fabricate(:list) }
context 'when there are no records' do context 'when there are no records' do
it 'skips push with missing status' do it 'skips push with missing status' do
@ -46,11 +47,29 @@ describe FeedInsertWorker do
it 'pushes the status onto the home timeline without filter' do it 'pushes the status onto the home timeline without filter' do
instance = instance_double(FeedManager, push_to_home: nil, filter?: false) instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance) allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, follower.id) result = subject.perform(status.id, follower.id, :home)
expect(result).to be_nil expect(result).to be_nil
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil) expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
end end
it 'pushes the status onto the tags timeline without filter' do
instance = instance_double(FeedManager, push_to_home: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, follower.id, :tags)
expect(result).to be_nil
expect(instance).to have_received(:push_to_home).with(follower, status, update: nil)
end
it 'pushes the status onto the list timeline without filter' do
instance = instance_double(FeedManager, push_to_list: nil, filter?: false)
allow(FeedManager).to receive(:instance).and_return(instance)
result = subject.perform(status.id, list.id, :list)
expect(result).to be_nil
expect(instance).to have_received(:push_to_list).with(list, status, update: nil)
end
end end
context 'with notification' do context 'with notification' do

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'rails_helper'
describe ImportWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(ImportService, call: true) }
describe '#perform' do
before do
allow(ImportService).to receive(:new).and_return(service)
end
let(:import) { Fabricate(:import) }
it 'sends the import to the service' do
worker.perform(import.id)
expect(service).to have_received(:call).with(import)
expect { import.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end

View file

@ -2,12 +2,38 @@
require 'rails_helper' require 'rails_helper'
describe PostProcessMediaWorker do describe PostProcessMediaWorker, :paperclip_processing do
let(:worker) { described_class.new } let(:worker) { described_class.new }
describe 'perform' do describe '#perform' do
it 'runs without error for missing record' do let(:media_attachment) { Fabricate(:media_attachment) }
expect { worker.perform(nil) }.to_not raise_error
it 'reprocesses and updates the media attachment' do
worker.perform(media_attachment.id)
expect(media_attachment.processing).to eq('complete')
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
context 'when sidekiq retries are exhausted' do
it 'sets state to failed' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [media_attachment.id] }) do
worker.perform(media_attachment.id)
end
expect(media_attachment.reload.processing).to eq('failed')
end
it 'returns true for non-existent record' do
described_class.within_sidekiq_retries_exhausted_block({ 'args' => [123_123_123] }) do
expect(worker.perform(123_123_123)).to be(true)
end
end
end end
end end
end end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rails_helper'
describe PublishAnnouncementReactionWorker do
let(:worker) { described_class.new }
describe '#perform' do
before { Fabricate(:account, user: Fabricate(:user, current_sign_in_at: 1.hour.ago)) }
let(:announcement) { Fabricate(:announcement) }
let(:name) { 'name value' }
it 'sends the announcement and name to the service when subscribed' do
allow(redis).to receive(:exists?).and_return(true)
allow(redis).to receive(:publish)
worker.perform(announcement.id, name)
expect(redis).to have_received(:publish)
end
it 'does not send the announcement and name to the service when not subscribed' do
allow(redis).to receive(:exists?).and_return(false)
allow(redis).to receive(:publish)
worker.perform(announcement.id, name)
expect(redis).to_not have_received(:publish)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, name)
expect(result).to be(true)
end
end
end

View file

@ -5,9 +5,48 @@ require 'rails_helper'
describe RedownloadAvatarWorker do describe RedownloadAvatarWorker do
let(:worker) { described_class.new } let(:worker) { described_class.new }
describe 'perform' do describe '#perform' do
it 'runs without error for missing record' do it 'returns nil for non-existent record' do
expect { worker.perform(nil) }.to_not raise_error result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil for suspended account' do
account = Fabricate(:account, suspended_at: 10.days.ago)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil with a domain block' do
account = Fabricate(:account, domain: 'host.example')
Fabricate(:domain_block, domain: account.domain, reject_media: true)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil without an avatar remote url' do
account = Fabricate(:account, avatar_remote_url: '')
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil when avatar file name is present' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, avatar_remote_url: 'https://example.host/file', avatar_file_name: 'test.jpg')
expect(worker.perform(account.id)).to be_nil
end
it 'reprocesses a remote avatar' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, avatar_remote_url: 'https://example.host/file')
account.update_column(:avatar_file_name, nil) # rubocop:disable Rails/SkipsModelValidations
result = worker.perform(account.id)
expect(result).to be(true)
expect(account.reload.avatar_file_name).to_not be_nil
end end
end end
end end

View file

@ -5,9 +5,48 @@ require 'rails_helper'
describe RedownloadHeaderWorker do describe RedownloadHeaderWorker do
let(:worker) { described_class.new } let(:worker) { described_class.new }
describe 'perform' do describe '#perform' do
it 'runs without error for missing record' do it 'returns nil for non-existent record' do
expect { worker.perform(nil) }.to_not raise_error result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil for suspended account' do
account = Fabricate(:account, suspended_at: 10.days.ago)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil with a domain block' do
account = Fabricate(:account, domain: 'host.example')
Fabricate(:domain_block, domain: account.domain, reject_media: true)
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil without an header remote url' do
account = Fabricate(:account, header_remote_url: '')
expect(worker.perform(account.id)).to be_nil
end
it 'returns nil when header file name is present' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, header_remote_url: 'https://example.host/file', header_file_name: 'test.jpg')
expect(worker.perform(account.id)).to be_nil
end
it 'reprocesses a remote header' do
stub_request(:get, 'https://example.host/file').to_return request_fixture('avatar.txt')
account = Fabricate(:account, header_remote_url: 'https://example.host/file')
account.update_column(:header_file_name, nil) # rubocop:disable Rails/SkipsModelValidations
result = worker.perform(account.id)
expect(result).to be(true)
expect(account.reload.header_file_name).to_not be_nil
end end
end end
end end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'rails_helper'
describe RedownloadMediaWorker do
let(:worker) { described_class.new }
describe '#perform' do
it 'returns nil for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be_nil
end
it 'returns nil without a remote_url' do
media_attachment = Fabricate(:media_attachment, remote_url: '')
result = worker.perform(media_attachment.id)
expect(result).to be_nil
end
context 'with a valid remote url' do
let(:url) { 'https://example.host/file.txt' }
before { stub_request(:get, url).to_return(status: 200) }
it 'processes downloads for valid record' do
media_attachment = Fabricate(:media_attachment, remote_url: url)
worker.perform(media_attachment.id)
expect(a_request(:get, url)).to have_been_made
end
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
describe RemovalWorker do
let(:worker) { described_class.new }
let(:service) { instance_double(RemoveStatusService, call: true) }
describe '#perform' do
before do
allow(RemoveStatusService).to receive(:new).and_return(service)
end
let(:status) { Fabricate(:status) }
it 'sends the status to the service' do
worker.perform(status.id)
expect(service).to have_received(:call).with(status)
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123)
expect(result).to be(true)
end
end
end

View file

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'rails_helper'
describe Scheduler::SelfDestructScheduler do
let(:worker) { described_class.new }
describe '#perform' do
let!(:account) { Fabricate(:account, domain: nil, suspended_at: nil) }
context 'when not in self destruct mode' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(false)
end
it 'returns without processing' do
worker.perform
expect(account.reload.suspended_at).to be_nil
end
end
context 'when in self-destruct mode' do
before do
allow(SelfDestructHelper).to receive(:self_destruct?).and_return(true)
end
context 'when sidekiq is overwhelmed' do
before do
stats = instance_double(Sidekiq::Stats, enqueued: described_class::MAX_ENQUEUED**2)
allow(Sidekiq::Stats).to receive(:new).and_return(stats)
end
it 'returns without processing' do
worker.perform
expect(account.reload.suspended_at).to be_nil
end
end
context 'when sidekiq is operational' do
it 'suspends local non-suspended accounts' do
worker.perform
expect(account.reload.suspended_at).to_not be_nil
end
it 'suspends local suspended accounts marked for deletion' do
account.update(suspended_at: 10.days.ago)
deletion_request = Fabricate(:account_deletion_request, account: account)
worker.perform
expect(account.reload.suspended_at).to be > 1.day.ago
expect { deletion_request.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
end
end

View file

@ -5,9 +5,21 @@ require 'rails_helper'
describe Webhooks::DeliveryWorker do describe Webhooks::DeliveryWorker do
let(:worker) { described_class.new } let(:worker) { described_class.new }
describe 'perform' do describe '#perform' do
it 'runs without error' do let(:webhook) { Fabricate(:webhook) }
expect { worker.perform(nil, nil) }.to_not raise_error
it 'reprocesses and updates the webhook' do
stub_request(:post, webhook.url).to_return(status: 200, body: '')
worker.perform(webhook.id, 'body')
expect(a_request(:post, webhook.url)).to have_been_made.at_least_once
end
it 'returns true for non-existent record' do
result = worker.perform(123_123_123, '')
expect(result).to be(true)
end end
end end
end end

View file

@ -2569,9 +2569,9 @@ __metadata:
linkType: soft linkType: soft
"@material-symbols/svg-600@npm:^0.14.0": "@material-symbols/svg-600@npm:^0.14.0":
version: 0.14.0 version: 0.14.1
resolution: "@material-symbols/svg-600@npm:0.14.0" resolution: "@material-symbols/svg-600@npm:0.14.1"
checksum: e6547a9a0b2072f4109f2e4e0863367ea2507efce740c427a8544100db02ffff52f33608aac1a355f4977e2c0b2ce6cdd6bfee9177bb13cee0b28418f948b5a5 checksum: fb5252285bbeccc45a4b131e8b165470b5b57e146bc7ea586eb82e580037d1218f6dad5fee4e6822c357041ff547f34c9c7432cce0a811b14f7e41d8ae23009b
languageName: node languageName: node
linkType: hard linkType: hard
@ -13812,17 +13812,17 @@ __metadata:
linkType: hard linkType: hard
"react-redux-loading-bar@npm:^5.0.4": "react-redux-loading-bar@npm:^5.0.4":
version: 5.0.4 version: 5.0.5
resolution: "react-redux-loading-bar@npm:5.0.4" resolution: "react-redux-loading-bar@npm:5.0.5"
dependencies: dependencies:
prop-types: "npm:^15.7.2" prop-types: "npm:^15.7.2"
react-lifecycles-compat: "npm:^3.0.4" react-lifecycles-compat: "npm:^3.0.4"
peerDependencies: peerDependencies:
react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-redux: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 react-redux: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
redux: ^3.0.0 || ^4.0.0 redux: ^3.0.0 || ^4.0.0 || ^5.0.0
checksum: 11eea2ef6dfae232e278eceb83d07f9f57a2ece3ef23ce888dccf24964b669c9ee83a6db12b1f3c757b5b3410f7a7ccda96a4b4216c4ad9b42bf831ccea7a4a2 checksum: 0dbac046c5b8b6bd209ccfc25ccc55dc9158cd737b42b68fd1900dfe46a59c9c7e2b0082d8901b749e7cf2d7e23074590aae74f350a814f205105f47895a6214
languageName: node languageName: node
linkType: hard linkType: hard