From 9565b17d5d743d7ae4028d484b407d649210f6d2 Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Thu, 24 Mar 2022 11:23:46 +0100
Subject: New Crowdin updates (#17829)
* New translations en.json (Kurmanji (Kurdish))
* New translations doorkeeper.en.yml (Kurmanji (Kurdish))
* New translations en.yml (Kurmanji (Kurdish))
* New translations en.yml (Kurmanji (Kurdish))
* New translations simple_form.en.yml (Kurmanji (Kurdish))
* New translations en.yml (Thai)
* New translations en.yml (Thai)
* New translations en.yml (Thai)
* New translations en.yml (Thai)
* New translations en.json (Japanese)
* New translations en.json (Japanese)
* New translations doorkeeper.en.yml (Indonesian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.json (Persian)
* New translations en.yml (Thai)
* New translations en.yml (Thai)
* New translations en.yml (Korean)
* New translations en.yml (Portuguese)
* New translations en.yml (Hungarian)
* New translations en.yml (Armenian)
* New translations en.yml (Georgian)
* New translations en.yml (Lithuanian)
* New translations en.yml (Macedonian)
* New translations en.yml (Dutch)
* New translations en.yml (Norwegian)
* New translations en.yml (Punjabi)
* New translations en.yml (Polish)
* New translations en.yml (Albanian)
* New translations en.yml (Basque)
* New translations en.yml (Serbian (Cyrillic))
* New translations en.yml (Turkish)
* New translations en.yml (Ukrainian)
* New translations en.yml (Chinese Traditional)
* New translations en.yml (Urdu (Pakistan))
* New translations en.yml (Icelandic)
* New translations en.yml (Portuguese, Brazilian)
* New translations en.yml (Indonesian)
* New translations en.yml (Tamil)
* New translations en.yml (Spanish, Argentina)
* New translations en.yml (Finnish)
* New translations en.yml (Greek)
* New translations en.yml (Galician)
* New translations en.yml (Slovak)
* New translations en.yml (Chinese Simplified)
* New translations en.yml (Swedish)
* New translations en.yml (Arabic)
* New translations en.yml (French)
* New translations en.yml (Spanish)
* New translations en.yml (Catalan)
* New translations en.yml (Hebrew)
* New translations en.yml (Italian)
* New translations en.yml (Japanese)
* New translations en.yml (Russian)
* New translations en.yml (Slovenian)
* New translations en.yml (German)
* New translations en.yml (Vietnamese)
* New translations en.yml (Persian)
* New translations en.yml (Romanian)
* New translations en.yml (Afrikaans)
* New translations en.yml (Bulgarian)
* New translations en.yml (Czech)
* New translations en.yml (Danish)
* New translations en.yml (Spanish, Mexico)
* New translations en.yml (Scottish Gaelic)
* New translations en.yml (Occitan)
* New translations en.yml (Bengali)
* New translations en.yml (Marathi)
* New translations en.yml (Silesian)
* New translations en.yml (Taigi)
* New translations en.yml (Ido)
* New translations en.yml (Kabyle)
* New translations en.yml (Sanskrit)
* New translations en.yml (Sardinian)
* New translations en.yml (Corsican)
* New translations en.yml (Sorani (Kurdish))
* New translations en.yml (Serbian (Latin))
* New translations en.yml (Asturian)
* New translations en.yml (Kannada)
* New translations en.yml (Cornish)
* New translations en.yml (Sinhala)
* New translations en.yml (Breton)
* New translations en.yml (Malayalam)
* New translations en.yml (Tatar)
* New translations en.yml (Chinese Traditional, Hong Kong)
* New translations en.yml (Uyghur)
* New translations en.yml (Esperanto)
* New translations en.yml (Welsh)
* New translations en.yml (Telugu)
* New translations en.yml (Malay)
* New translations en.yml (Hindi)
* New translations en.yml (Latvian)
* New translations en.yml (Estonian)
* New translations en.yml (Kazakh)
* New translations en.yml (Norwegian Nynorsk)
* New translations en.yml (Croatian)
* New translations en.yml (Standard Moroccan Tamazight)
* New translations en.yml (Catalan)
* New translations en.yml (Chinese Traditional)
* New translations en.yml (Latvian)
* New translations en.yml (Icelandic)
* New translations en.yml (Swedish)
* New translations en.yml (Thai)
* New translations en.yml (Portuguese)
* New translations en.yml (Thai)
* New translations en.yml (Russian)
* New translations en.yml (Russian)
* New translations en.yml (Thai)
* New translations en.yml (Danish)
* New translations en.yml (Spanish)
* New translations en.yml (Spanish, Argentina)
* New translations en.json (Japanese)
* New translations en.yml (German)
* New translations en.yml (Hungarian)
* New translations en.yml (Italian)
* New translations en.yml (Kurmanji (Kurdish))
* New translations en.yml (Chinese Simplified)
* New translations en.yml (Indonesian)
* New translations en.yml (Turkish)
* New translations en.json (Persian)
* New translations simple_form.en.yml (Persian)
* New translations en.yml (Thai)
* New translations simple_form.en.yml (Thai)
* New translations simple_form.en.yml (Persian)
* New translations simple_form.en.yml (Persian)
* New translations en.yml (Thai)
* New translations en.json (Dutch)
* New translations en.json (Dutch)
* New translations en.yml (Dutch)
* New translations en.json (Dutch)
* New translations en.yml (Dutch)
* New translations en.json (Dutch)
* New translations simple_form.en.yml (Dutch)
* New translations en.json (Dutch)
* New translations en.json (Dutch)
* New translations en.json (Dutch)
* New translations doorkeeper.en.yml (Dutch)
* New translations doorkeeper.en.yml (Dutch)
* New translations en.json (Dutch)
* New translations doorkeeper.en.yml (Dutch)
* New translations en.json (Dutch)
* New translations en.json (Dutch)
* New translations en.yml (Dutch)
* New translations en.json (Dutch)
* New translations en.yml (Dutch)
* New translations simple_form.en.yml (Dutch)
* New translations simple_form.en.yml (Dutch)
* New translations doorkeeper.en.yml (Dutch)
* New translations activerecord.en.yml (Dutch)
* New translations en.yml (Dutch)
* New translations doorkeeper.en.yml (Dutch)
* New translations en.yml (Korean)
* New translations en.yml (Galician)
* Run `bundle exec i18n-tasks normalize`
* Run `yarn manage:translations`
Co-authored-by: Yamagishi Kazutoshi
---
config/locales/activerecord.nl.yml | 2 +-
config/locales/ca.yml | 1 +
config/locales/da.yml | 1 +
config/locales/de.yml | 3 ++
config/locales/doorkeeper.id.yml | 33 +++++++++++++
config/locales/doorkeeper.ku.yml | 4 +-
config/locales/doorkeeper.nl.yml | 46 ++++++++++++++++--
config/locales/es-AR.yml | 1 +
config/locales/es.yml | 1 +
config/locales/gl.yml | 1 +
config/locales/hu.yml | 1 +
config/locales/id.yml | 1 +
config/locales/is.yml | 1 +
config/locales/it.yml | 1 +
config/locales/ko.yml | 1 +
config/locales/ku.yml | 11 +++--
config/locales/lv.yml | 1 +
config/locales/nl.yml | 96 +++++++++++++++++++-------------------
config/locales/pt-PT.yml | 1 +
config/locales/ru.yml | 4 ++
config/locales/simple_form.fa.yml | 9 ++++
config/locales/simple_form.ku.yml | 2 +-
config/locales/simple_form.nl.yml | 48 +++++++++----------
config/locales/simple_form.th.yml | 2 +-
config/locales/sv.yml | 2 +
config/locales/th.yml | 13 ++++++
config/locales/tr.yml | 1 +
config/locales/zh-CN.yml | 1 +
config/locales/zh-TW.yml | 1 +
29 files changed, 203 insertions(+), 87 deletions(-)
(limited to 'config')
diff --git a/config/locales/activerecord.nl.yml b/config/locales/activerecord.nl.yml
index 6bbdc5b40..b5a122001 100644
--- a/config/locales/activerecord.nl.yml
+++ b/config/locales/activerecord.nl.yml
@@ -24,7 +24,7 @@ nl:
status:
attributes:
reblog:
- taken: van toot bestaat al
+ taken: van bericht bestaat al
user:
attributes:
email:
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 054db2e5b..6c5fc95fe 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -490,6 +490,7 @@ ca:
other: Intents fallits en %{count} diferents dies.
no_failures_recorded: Sense errors registrats.
title: Disponibilitat
+ warning: El darrer intent de connectar a aquest servidor no ha tingut èxit
back_to_all: Totes
back_to_limited: Limitades
back_to_warning: Avís
diff --git a/config/locales/da.yml b/config/locales/da.yml
index 34e56ded4..b264dffae 100644
--- a/config/locales/da.yml
+++ b/config/locales/da.yml
@@ -490,6 +490,7 @@ da:
other: Mislykkede forsøg på %{count} forskellige dage.
no_failures_recorded: Ingen fejl noteret.
title: Tilgængelighed
+ warning: Seneste forsøg på at oprette forbindelse til denne server mislykkedes
back_to_all: Alle
back_to_limited: Begrænset
back_to_warning: Advarsel
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 4768f4e80..d595fdd42 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -373,6 +373,7 @@ de:
enable: Aktivieren
enabled: Aktiviert
enabled_msg: Das Emoji wurde aktiviert
+ image_hint: PNG oder GIF bis %{size}
list: Liste
listed: Gelistet
new:
@@ -489,6 +490,7 @@ de:
other: Fehlgeschlagener Versuch am %{count}. Tag.
no_failures_recorded: Keine Fehler bei der Aufzeichnung.
title: Verfügbarkeit
+ warning: Der letzte Versuch, sich mit diesem Server zu verbinden, war nicht erfolgreich
back_to_all: Alle
back_to_limited: Beschränkt
back_to_warning: Warnung
@@ -1436,6 +1438,7 @@ de:
disallowed_hashtags:
one: 'enthält einen verbotenen Hashtag: %{tags}'
other: 'enthält verbotene Hashtags: %{tags}'
+ edited_at_html: Bearbeitet %{date}
errors:
in_reply_not_found: Der Beitrag, auf den du antworten möchtest, scheint nicht zu existieren.
open_in_web: Im Web öffnen
diff --git a/config/locales/doorkeeper.id.yml b/config/locales/doorkeeper.id.yml
index 050d97dc5..9a3fed94d 100644
--- a/config/locales/doorkeeper.id.yml
+++ b/config/locales/doorkeeper.id.yml
@@ -73,6 +73,10 @@ id:
index:
authorized_at: Diberi hak otorisasi pada %{date}
description_html: Ini adalah aplikasi yang dapat mengakses akun Anda menggunakan API. Jika ada aplikasi yang tidak Anda kenal di sini, atau aplikasi yang berperilaku aneh, Anda dapat mencabut hak aksesnya.
+ last_used_at: Terakhir dipakai pada %{date}
+ never_used: Tidak pernah dipakai
+ scopes: Hak akses
+ superapp: Internal
title: Aplikasi yang anda izinkan
errors:
messages:
@@ -108,6 +112,33 @@ id:
authorized_applications:
destroy:
notice: Aplikasi dicabut.
+ grouped_scopes:
+ access:
+ read: Akses baca-saja
+ read/write: Akses baca dan tulis
+ write: Akses tulis-saja
+ title:
+ accounts: Akun
+ admin/accounts: Administrasi akun
+ admin/all: Semua fungsi administratif
+ admin/reports: Administrasi laporan
+ all: Segalanya
+ blocks: Blokir
+ bookmarks: Markah
+ conversations: Percakapan
+ crypto: Enkripsi end-to-end
+ favourites: Favorit
+ filters: Saringan
+ follow: Hubungan
+ follows: Mengikuti
+ lists: Daftar
+ media: Lampiran media
+ mutes: Bisukan
+ notifications: Notifikasi
+ push: Notifikasi dorong
+ reports: Laporan
+ search: Pencarian
+ statuses: Kiriman
layouts:
admin:
nav:
@@ -122,6 +153,7 @@ id:
admin:write: ubah semua data di server
admin:write:accounts: lakukan aksi moderasi akun
admin:write:reports: lakukan aksi moderasi laporan
+ crypto: menggunakan enkripsi end-to-end
follow: mengikuti, blokir, menghapus blokir, dan berhenti mengikuti akun
push: terima notifikasi dorong
read: membaca data pada akun anda
@@ -141,6 +173,7 @@ id:
write:accounts: ubah profil Anda
write:blocks: blokir akun dan domain
write:bookmarks: status markah
+ write:conversations: bisukan dan hapus percakapan
write:favourites: status favorit
write:filters: buat saringan
write:follows: ikuti orang
diff --git a/config/locales/doorkeeper.ku.yml b/config/locales/doorkeeper.ku.yml
index 3a98486e3..6db8bb73d 100644
--- a/config/locales/doorkeeper.ku.yml
+++ b/config/locales/doorkeeper.ku.yml
@@ -130,7 +130,7 @@ ku:
favourites: Bijarte
filters: Parzûn
follow: Pêwendî
- follows: Şopîner
+ follows: Dişopîne
lists: Rêzok
media: Pêvekên medya
mutes: Bêdengkirin
@@ -162,7 +162,7 @@ ku:
read:bookmarks: şûnpelên xwe bibîne
read:favourites: bijarteyên xwe bibîne
read:filters: parzûnûn xwe bibîne
- read:follows: şopînerên xwe bibîne
+ read:follows: ên tu dişopînî bibîne
read:lists: rêzoka xwe bibîne
read:mutes: ajimêrên bêdeng kirî bibîne
read:notifications: agahdariyên xwe bibîne
diff --git a/config/locales/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml
index cb0c70aab..76f3b88c3 100644
--- a/config/locales/doorkeeper.nl.yml
+++ b/config/locales/doorkeeper.nl.yml
@@ -60,6 +60,8 @@ nl:
error:
title: Er is een fout opgetreden
new:
+ prompt_html: "%{client_name} heeft toestemming nodig om toegang te krijgen tot jouw account. Het betreft een third-party-toepassing.Als je dit niet vertrouwt, moet je geen toestemming verlenen."
+ review_permissions: Toestemmingen beoordelen
title: Autorisatie vereist
show:
title: Kopieer deze autorisatiecode en plak het in de toepassing.
@@ -69,6 +71,11 @@ nl:
confirmations:
revoke: Weet je het zeker?
index:
+ authorized_at: Toestemming verleent op %{date}
+ last_used_at: Voor het laatst gebruikt op %{date}
+ never_used: Nooit gebruikt
+ scopes: Toestemmingen
+ superapp: Intern
title: Jouw geautoriseerde toepassingen
errors:
messages:
@@ -104,6 +111,33 @@ nl:
authorized_applications:
destroy:
notice: Toepassing ingetrokken.
+ grouped_scopes:
+ access:
+ read: Alleen leestoegang
+ read/write: Lees- en schrijftoegang
+ write: Alleen schrijftoegang
+ title:
+ accounts: Accounts
+ admin/accounts: Accountbeheer
+ admin/all: Alle beheerfuncties
+ admin/reports: Rapportagebeheer
+ all: Alles
+ blocks: Blokkeren
+ bookmarks: Bladwijzers
+ conversations: Gesprekken
+ crypto: End-to-end-encryptie
+ favourites: Favorieten
+ filters: Filters
+ follow: Relaties
+ follows: Volgend
+ lists: Lijsten
+ media: Mediabijlagen
+ mutes: Negeren
+ notifications: Meldingen
+ push: Pushmeldingen
+ reports: Rapportages
+ search: Zoeken
+ statuses: Berichten
layouts:
admin:
nav:
@@ -118,6 +152,7 @@ nl:
admin:write: wijzig alle gegevens op de server
admin:write:accounts: moderatieacties op accounts uitvoeren
admin:write:reports: moderatieacties op rapportages uitvoeren
+ crypto: end-to-end-encryptie gebruiken
follow: relaties tussen accounts bewerken
push: jouw pushmeldingen ontvangen
read: alle gegevens van jouw account lezen
@@ -130,14 +165,15 @@ nl:
read:lists: jouw lijsten bekijken
read:mutes: jouw genegeerde gebruikers bekijken
read:notifications: jouw meldingen bekijken
- read:reports: jouw gerapporteerde toots bekijken
+ read:reports: jouw gerapporteerde berichten bekijken
read:search: namens jou zoeken
- read:statuses: alle toots bekijken
+ read:statuses: alle berichten bekijken
write: alle gegevens van jouw account bewerken
write:accounts: jouw profiel bewerken
write:blocks: accounts en domeinen blokkeren
- write:bookmarks: toots aan bladwijzers toevoegen
- write:favourites: toots als favoriet markeren
+ write:bookmarks: berichten aan bladwijzers toevoegen
+ write:conversations: gespreken negeren en verwijderen
+ write:favourites: berichten als favoriet markeren
write:filters: filters aanmaken
write:follows: mensen volgen
write:lists: lijsten aanmaken
@@ -145,4 +181,4 @@ nl:
write:mutes: mensen en gesprekken negeren
write:notifications: meldingen verwijderen
write:reports: andere mensen rapporteren
- write:statuses: toots publiceren
+ write:statuses: berichten plaatsen
diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml
index 7dee30a27..2acc958a7 100644
--- a/config/locales/es-AR.yml
+++ b/config/locales/es-AR.yml
@@ -490,6 +490,7 @@ es-AR:
other: Intentos fallidos en %{count} días.
no_failures_recorded: No hay fallos en el registro.
title: Disponibilidad
+ warning: El último intento de conexión a este servidor no fue exitoso
back_to_all: Todos
back_to_limited: Limitados
back_to_warning: Advertencia
diff --git a/config/locales/es.yml b/config/locales/es.yml
index bcce44e20..31970da48 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -490,6 +490,7 @@ es:
other: Intentos fallidos en %{count} días diferentes.
no_failures_recorded: No hay fallos en el registro.
title: Disponibilidad
+ warning: El último intento de conexión a este servidor no ha tenido éxito
back_to_all: Todos
back_to_limited: Limitados
back_to_warning: Advertencia
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index f3a4beb70..27642844c 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -490,6 +490,7 @@ gl:
other: Intentos fallidos durante %{count} días distintos.
no_failures_recorded: Non hai fallos rexistrados.
title: Dispoñibilidade
+ warning: Fallou o último intento de conectar con este servidor
back_to_all: Todo
back_to_limited: Limitado
back_to_warning: Aviso
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 000184317..ec2d4fa94 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -492,6 +492,7 @@ hu:
other: Sikertelen próbálkozás %{count} különböző napon.
no_failures_recorded: Nem rögzítettünk hibát.
title: Elérhetőség
+ warning: Sikertelen volt az utolsó csatlakozási próbálkozás ehhez a szerverhez
back_to_all: Mind
back_to_limited: Korlátozott
back_to_warning: Figyelmeztetés
diff --git a/config/locales/id.yml b/config/locales/id.yml
index 97443b4a5..63b9066ce 100644
--- a/config/locales/id.yml
+++ b/config/locales/id.yml
@@ -477,6 +477,7 @@ id:
other: Upaya gagal dalam %{count} hari berbeda.
no_failures_recorded: Tidak ada kegagalan tercatat.
title: Ketersediaan
+ warning: Upaya terakhir untuk menyambung ke server ini tidak berhasil
back_to_all: Semua
back_to_limited: Terbatas
back_to_warning: Peringatan
diff --git a/config/locales/is.yml b/config/locales/is.yml
index 04d193975..92eb0e31e 100644
--- a/config/locales/is.yml
+++ b/config/locales/is.yml
@@ -490,6 +490,7 @@ is:
other: Misheppnaðar tilraunir á %{count} mismunandi dögum.
no_failures_recorded: Engar misheppnaðar tilraunir á skrá.
title: Tiltækileiki
+ warning: Síðasta tilraun til að tengjast þessum netþjóni mistókst
back_to_all: Allt
back_to_limited: Takmarkað
back_to_warning: Aðvörun
diff --git a/config/locales/it.yml b/config/locales/it.yml
index d96e58540..5af135fbc 100644
--- a/config/locales/it.yml
+++ b/config/locales/it.yml
@@ -490,6 +490,7 @@ it:
other: Tentativo fallito %{count} giorni differenti.
no_failures_recorded: Nessun fallimento registrato.
title: Disponibilità
+ warning: L'ultimo tentativo di connessione a questo server non è riuscito
back_to_all: Tutto
back_to_limited: Limitato
back_to_warning: Avviso
diff --git a/config/locales/ko.yml b/config/locales/ko.yml
index 7dacd90d3..19cabb1ce 100644
--- a/config/locales/ko.yml
+++ b/config/locales/ko.yml
@@ -481,6 +481,7 @@ ko:
other: 실패한 전달 시도 총 %{count}일.
no_failures_recorded: 실패 기록이 없습니다.
title: 가용성
+ warning: 이 서버에 대한 마지막 연결 시도가 성공적이지 않았습니다
back_to_all: 전체
back_to_limited: 제한됨
back_to_warning: 경고
diff --git a/config/locales/ku.yml b/config/locales/ku.yml
index 5245f85ec..7dac9ae44 100644
--- a/config/locales/ku.yml
+++ b/config/locales/ku.yml
@@ -133,7 +133,7 @@ ku:
enabled: Çalakkirî
enabled_msg: Ajimêra %{username} bi serkeftî hat çalak kirin
followers: Şopîner
- follows: Dişopînê
+ follows: Dişopîne
header: Jormalper
inbox_url: Peyamên hatî URl
invite_request_text: Sedemên tevlêbûnê
@@ -492,6 +492,7 @@ ku:
other: Hewldanên têkçûyî di %{count} rojên cuda de.
no_failures_recorded: Di tomarê de têkçûn tune.
title: Berdestbûnî
+ warning: Hewldana dawî ji bo girêdana bi vê rajekarê re bi ser neket
back_to_all: Hemû
back_to_limited: Sînorkirî
back_to_warning: Hişyarî
@@ -543,7 +544,7 @@ ku:
title: Giştî
total_blocked_by_us: Ji aliyê me ve hatiye astengkirin
total_followed_by_them: Ji aliyê wan ve hatiye şopandin
- total_followed_by_us: Ji aliyê ve me hate şopandin
+ total_followed_by_us: Ji aliyê me ve hatiye şopandin
total_reported: Giliyên derheqê wan de
total_storage: Pêvekên medyayê
totals_time_period_hint_html: Tevahiyên ku li jêr têne xuyakirin daneyên hemû deman dihewîne.
@@ -975,7 +976,7 @@ ku:
close: An jî, tu dikarî tenê ev çarçoveyê bigirî.
return: Profîla vê bikarhênerê nîşan bike
web: Biçe tevneyê
- title: Bişopîne %{acct}
+ title: "%{acct} bişopîne"
challenge:
confirm: Bidomîne
hint_html: "Nîşe:Ji bo demjimêreke din em ê pêborîna te careke din ji te nexwazin."
@@ -1247,7 +1248,7 @@ ku:
follow:
body: "%{name} niha te dişopîne!"
subject: "%{name} niha te dişopîne"
- title: Şopînereke nû
+ title: Şopînera nû
follow_request:
action: Daxwazên şopandinê bi rê ve bibe
body: "%{name} daxwaza şopandina te kir"
@@ -1416,7 +1417,7 @@ ku:
notifications: Agahdarî
preferences: Hilbijarte
profile: Profîl
- relationships: Yên tê şopandin û şopîner
+ relationships: Şopandin û şopîner
statuses_cleanup: Bi xweberî ve jêbirina şandiya
strikes: Binpêkirinên çavdêriyê
two_factor_authentication: Piştrastkirinê du-faktorî
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 8f6df9961..3440da7ef 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -501,6 +501,7 @@ lv:
zero: Neizdevušies mēģinājumi %{count} dienās.
no_failures_recorded: Nav reģistrētu kļūdu.
title: Pieejamība
+ warning: Pēdējais mēģinājums izveidot savienojumu ar šo serveri ir bijis neveiksmīgs
back_to_all: Visas
back_to_limited: Ierobežotās
back_to_warning: Brīdinājums
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index a51ef07af..276fdb9b2 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -1,7 +1,7 @@
---
nl:
about:
- about_hashtag_html: Dit zijn openbare toots die getagged zijn met #%{hashtag}. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt.
+ about_hashtag_html: Dit zijn openbare berichten die getagged zijn met #%{hashtag}. Je kunt er op reageren of iets anders mee doen als je op Mastodon (of ergens anders in de fediverse) een account hebt.
about_mastodon_html: Mastodon is een sociaal netwerk dat gebruikt maakt van open webprotocollen en vrije software. Het is net zoals e-mail gedecentraliseerd.
about_this: Over deze server
active_count_after: actief
@@ -31,7 +31,7 @@ nl:
source_code: Broncode
status_count_after:
one: toot
- other: toots
+ other: berichten
status_count_before: Zij schreven
tagline: Vrienden volgen en nieuwe ontdekken
terms: Gebruiksvoorwaarden
@@ -41,7 +41,7 @@ nl:
reason: 'Reden:'
rejecting_media: 'Mediabestanden van deze server worden niet verwerkt en er worden geen thumbnails getoond. Je moet handmatig naar deze server doorklikken om de mediabestanden te kunnen bekijken:'
rejecting_media_title: Mediabestanden geweigerd
- silenced: Toots van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt.
+ silenced: Berichten van deze server worden nergens weergegeven, behalve op jouw eigen starttijdlijn wanneer je het account volgt.
silenced_title: Beperkte servers
suspended: Je bent niet in staat om iemand van deze server te volgen, en er worden geen gegevens van deze server verwerkt of opgeslagen, en met deze server uitgewisseld.
suspended_title: Opgeschorte servers
@@ -74,9 +74,9 @@ nl:
following: Je moet dit account wel al volgen, alvorens je het kan aanbevelen
posts:
one: Toot
- other: Toots
- posts_tab_heading: Toots
- posts_with_replies: Toots en reacties
+ other: Berichten
+ posts_tab_heading: Berichten
+ posts_with_replies: Berichten en reacties
roles:
admin: Beheerder
bot: Bot
@@ -193,7 +193,7 @@ nl:
targeted_reports: Door anderen gerapporteerd
silence: Beperken
silenced: Beperkt
- statuses: Toots
+ statuses: Berichten
subscribe: Abonneren
suspended: Opgeschort
suspension_irreversible: De gegevens van dit account zijn onomkeerbaar verwijderd. Je kunt het opschorten van dit account ongedaan maken zodat het weer valt te gebruiken, maar de verwijderde gegevens worden hiermee niet hersteld.
@@ -229,7 +229,7 @@ nl:
destroy_custom_emoji: Lokale emoji verwijderen
destroy_domain_allow: Domeingoedkeuring verwijderen
destroy_domain_block: Domeinblokkade verwijderen
- destroy_email_domain_block: E-maildomeinblokkade verwijderen
+ destroy_email_domain_block: Blokkade van e-maildomein verwijderen
destroy_ip_block: IP-regel verwijderen
destroy_status: Toot verwijderen
destroy_unavailable_domain: Niet beschikbaar domein verwijderen
@@ -245,16 +245,16 @@ nl:
reset_password_user: Wachtwoord opnieuw instellen
resolve_report: Rapportage oplossen
sensitive_account: De media in jouw account als gevoelig markeren
- silence_account: Account negeren
+ silence_account: Account beperken
suspend_account: Account opschorten
unassigned_report: Rapportage niet langer toewijzen
unsensitive_account: De media in jouw account niet langer als gevoelig markeren
- unsilence_account: Account niet langer negeren
+ unsilence_account: Account niet langer beperken
unsuspend_account: Account niet langer opschorten
update_announcement: Mededeling bijwerken
update_custom_emoji: Lokale emoji bijwerken
update_domain_block: Domeinblokkade bijwerken
- update_status: Toot bijwerken
+ update_status: Bericht bijwerken
actions:
assigned_to_self_report_html: "%{name} heeft rapportage %{target} aan zichzelf toegewezen"
change_email_user_html: "%{name} veranderde het e-mailadres van gebruiker %{target}"
@@ -274,7 +274,7 @@ nl:
destroy_domain_block_html: Domein %{target} is door %{name} gedeblokkeerd
destroy_email_domain_block_html: "%{name} heeft het e-maildomein %{target} gedeblokkeerd"
destroy_ip_block_html: "%{name} verwijderde regel voor IP %{target}"
- destroy_status_html: Toot van %{target} is door %{name} verwijderd
+ destroy_status_html: Bericht van %{target} is door %{name} verwijderd
destroy_unavailable_domain_html: "%{name} heeft de bezorging voor domein %{target} hervat"
disable_2fa_user_html: De vereiste tweestapsverificatie voor %{target} is door %{name} uitgeschakeld
disable_custom_emoji_html: Emoji %{target} is door %{name} uitgeschakeld
@@ -297,8 +297,8 @@ nl:
update_announcement_html: "%{name} heeft de mededeling %{target} bijgewerkt"
update_custom_emoji_html: Emoji %{target} is door %{name} bijgewerkt
update_domain_block_html: "%{name} heeft de domeinblokkade bijgewerkt voor %{target}"
- update_status_html: "%{name} heeft de toots van %{target} bijgewerkt"
- deleted_status: "(verwijderde toot}"
+ update_status_html: "%{name} heeft de berichten van %{target} bijgewerkt"
+ deleted_status: "(verwijderd bericht}"
empty: Geen logs gevonden.
filter_by_action: Op actie filteren
filter_by_user: Op gebruiker filteren
@@ -461,11 +461,11 @@ nl:
relays:
add_new: Nieuwe relayserver toevoegen
delete: Verwijderen
- description_html: Een federatierelay is een tussenliggende server die grote hoeveelheden openbare toots uitwisselt tussen servers die zich hierop hebben geabonneerd. Het kan kleine en middelgrote servers helpen om content uit de fediverse te ontdekken, waarvoor anders lokale gebruikers handmatig mensen van externe servers moeten volgen.
+ description_html: Een federatierelay is een tussenliggende server die grote hoeveelheden openbare berichten uitwisselt tussen servers die zich hierop hebben geabonneerd. Het kan kleine en middelgrote servers helpen om content van de fediverse te ontdekken, waarvoor anders lokale gebruikers handmatig mensen van externe servers moeten volgen.
disable: Uitschakelen
disabled: Uitgeschakeld
enable: Inschakelen
- enable_hint: Eenmaal ingeschakeld gaat jouw server zich op alle openbare toots van deze relayserver abonneren en stuurt het de openbare toots van jouw server naar de relayserver.
+ enable_hint: Eenmaal ingeschakeld gaat jouw server zich op alle openbare berichten van deze relayserver abonneren en stuurt het de openbare berichten van jouw server naar de relayserver.
enabled: Ingeschakeld
inbox_url: Relay-URL
pending: Aan het wachten op toestemming van de relayserver
@@ -506,7 +506,7 @@ nl:
reported_by: Gerapporteerd door
resolved: Opgelost
resolved_msg: Rapportage succesvol opgelost!
- status: Toot
+ status: Bericht
title: Rapportages
unassign: Niet langer toewijzen
unresolved: Onopgelost
@@ -520,7 +520,7 @@ nl:
title: Serverregels
settings:
activity_api_enabled:
- desc_html: Wekelijks overzicht van de hoeveelheid lokale toots, actieve gebruikers en nieuwe registraties
+ desc_html: Wekelijks overzicht van de hoeveelheid lokale berichten, actieve gebruikers en nieuwe registraties
title: Statistieken over gebruikersactiviteit via de API publiceren
bootstrap_timeline_accounts:
desc_html: Meerdere gebruikersnamen met komma's scheiden. Deze accounts worden in ieder geval aan nieuwe gebruikers aanbevolen
@@ -533,7 +533,7 @@ nl:
title: Aangepaste CSS
default_noindex:
desc_html: Heeft invloed op alle gebruikers die deze instelling niet zelf hebben veranderd
- title: Toots van gebruikers standaard niet door zoekmachines laten indexeren
+ title: Berichten van gebruikers standaard niet door zoekmachines laten indexeren
domain_blocks:
all: Aan iedereen
disabled: Aan niemand
@@ -561,7 +561,7 @@ nl:
desc_html: Wordt op de voorpagina weergegeven wanneer registratie van nieuwe accounts is uitgeschakeld
En ook hier kan je HTML gebruiken
title: Bericht wanneer registratie is uitgeschakeld
deletion:
- desc_html: Toestaan dat iedereen hun eigen account kan verwijderen
+ desc_html: Toestaan dat iedereen diens eigen account kan verwijderen
title: Verwijderen account toestaan
min_invite_role:
disabled: Niemand
@@ -606,7 +606,7 @@ nl:
title: Hashtags toestaan om trending te worden zonder voorafgaande beoordeling
trends:
desc_html: Eerder beoordeelde hashtags die op dit moment trending zijn openbaar tonen
- title: Trending hashtags
+ title: Trends
site_uploads:
delete: Geüpload bestand verwijderen
destroyed_msg: Verwijderen website-upload geslaagd!
@@ -615,8 +615,8 @@ nl:
deleted: Verwijderd
media:
title: Media
- no_status_selected: Er werden geen toots gewijzigd, omdat er geen enkele werd geselecteerd
- title: Toots van account
+ no_status_selected: Er werden geen berichten gewijzigd, omdat er geen enkele werd geselecteerd
+ title: Berichten van account
with_media: Met media
system_checks:
database_schema_check:
@@ -662,14 +662,14 @@ nl:
guide_link: https://crowdin.com/project/mastodon/nl
guide_link_text: Iedereen kan bijdragen.
sensitive_content: Gevoelige inhoud
- toot_layout: Lay-out van toots
+ toot_layout: Lay-out van berichten
application_mailer:
notification_preferences: E-mailvoorkeuren wijzigen
salutation: "%{name},"
settings: 'E-mailvoorkeuren wijzigen: %{link}'
view: 'Bekijk:'
view_profile: Profiel bekijken
- view_status: Toot bekijken
+ view_status: Bericht bekijken
applications:
created: Aanmaken toepassing geslaagd
destroyed: Verwijderen toepassing geslaagd
@@ -768,8 +768,8 @@ nl:
success_msg: Jouw account is succesvol verwijderd
warning:
before: 'Lees deze tekst zorgvuldig voordat je verder gaat:'
- caches: Toots en media die op andere servers zijn opgeslagen kunnen daar achterblijven
- data_removal: Jouw toots en andere gegevens worden permanent verwijderd
+ caches: Berichten en media die op andere servers zijn opgeslagen kunnen daar achterblijven
+ data_removal: Jouw berichten en andere gegevens worden permanent verwijderd
email_change_html: Je kunt je e-mailadres wijzigen zonder dat je jouw account hoeft te verwijderen
email_contact_html: Wanneer het nog steeds niet aankomt, kun je voor hulp e-mailen naar %{email}
email_reconfirmation_html: Wanneer je de bevestigingsmail niet hebt ontvangen, kun je deze opnieuw aanvragen
@@ -805,7 +805,7 @@ nl:
archive_takeout:
date: Datum
download: Jouw archief downloaden
- hint_html: Je kunt een archief opvragen van jouw toots en geüploade media. De geëxporteerde gegevens zijn in het ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen. Je kunt elke 7 dagen een kopie van je archief aanvragen.
+ hint_html: Je kunt een archief opvragen van jouw berichten en geüploade media. De geëxporteerde gegevens zijn in het ActivityPub-formaat, dat door hiervoor geschikte software valt uit te lezen. Je kunt elke 7 dagen een kopie van je archief aanvragen.
in_progress: Jouw archief wordt samengesteld...
request: Jouw archief opvragen
size: Omvang
@@ -820,7 +820,7 @@ nl:
add_new: Nieuwe toevoegen
errors:
limit: Je hebt al het maximaal aantal hashtags uitgelicht
- hint_html: "Wat zijn uitgelichte hashtags? Deze worden prominent op jouw openbare profiel getoond en stelt mensen in staat om jouw openbare toots per hashtag te bekijken. Het zijn een goed hulpmiddel om creatieve werkzaamheden of langetermijnprojecten bij te houden."
+ hint_html: "Wat zijn uitgelichte hashtags? Deze worden prominent op jouw openbare profiel getoond en stelt mensen in staat om jouw openbare berichten per hashtag te bekijken. Het zijn een goed hulpmiddel om creatieve werkzaamheden of langetermijnprojecten bij te houden."
filters:
contexts:
account: Profielen
@@ -901,7 +901,7 @@ nl:
limit: Je hebt het maximaal aantal lijsten bereikt
media_attachments:
validations:
- images_and_video: Een video kan niet aan een toot met afbeeldingen worden gekoppeld
+ images_and_video: Een video kan niet aan een bericht met afbeeldingen worden gekoppeld
not_ready: Kan geen bestanden toevoegen die nog niet zijn verwerkt. Probeer het later opnieuw!
too_many: Er kunnen niet meer dan 4 afbeeldingen toegevoegd worden
migrations:
@@ -954,8 +954,8 @@ nl:
other: "%{count} nieuwe meldingen sinds jouw laatste bezoek \U0001F418"
title: Tijdens jouw afwezigheid...
favourite:
- body: 'Jouw toot werd door %{name} aan hun favorieten toegevoegd:'
- subject: "%{name} voegde jouw toot als favoriet toe"
+ body: 'Jouw bericht werd door %{name} aan diens favorieten toegevoegd:'
+ subject: "%{name} voegde jouw bericht als favoriet toe"
title: Nieuwe favoriet
follow:
body: "%{name} volgt jou nu!"
@@ -974,11 +974,11 @@ nl:
poll:
subject: Een poll van %{name} is beëindigd
reblog:
- body: 'Jouw toot werd door %{name} geboost:'
- subject: "%{name} boostte jouw toot"
+ body: 'Jouw bericht werd door %{name} geboost:'
+ subject: "%{name} boostte jouw bericht"
title: Nieuwe boost
status:
- subject: "%{name} heeft zojuist een toot geplaatst"
+ subject: "%{name} heeft zojuist een bericht geplaatst"
notifications:
email_events: E-mailmeldingen voor gebeurtenissen
email_events_hint: 'Selecteer gebeurtenissen waarvoor je meldingen wilt ontvangen:'
@@ -997,7 +997,7 @@ nl:
code_hint: Voer de code in die door de authenticatie-app werd gegenereerd
description_html: Na het instellen van tweestapsverificatie met een authenticatie-app, kun je alleen inloggen als je jouw mobiele telefoon bij je hebt. Hiermee genereer je namelijk de in te voeren toegangscode.
enable: Inschakelen
- instructions_html: "Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon. Van nu af aan genereert deze app toegangscodes die je bij het inloggen moet invoeren."
+ instructions_html: "Scan deze QR-code in Google Authenticator of een soortgelijke app op jouw mobiele telefoon. Vanaf nu genereert deze app toegangscodes die je bij het inloggen moet invoeren."
manual_instructions: 'Voor het geval je de QR-code niet kunt scannen en het handmatig moet invoeren, vind je hieronder de geheime code in platte tekst:'
setup: Instellen
wrong_code: De ingevoerde code is ongeldig! Klopt de systeemtijd van de server en die van jouw apparaat?
@@ -1053,17 +1053,17 @@ nl:
remote_interaction:
favourite:
proceed: Doorgaan met toevoegen aan jouw favorieten
- prompt: 'Je wilt de volgende toot aan jouw favorieten toevoegen:'
+ prompt: 'Je wilt het volgende bericht aan jouw favorieten toevoegen:'
reblog:
proceed: Doorgaan met boosten
- prompt: 'Je wilt de volgende toot boosten:'
+ prompt: 'Je wilt het volgende bericht boosten:'
reply:
proceed: Doorgaan met reageren
- prompt: 'Je wilt op de volgende toot reageren:'
+ prompt: 'Je wilt op het volgende bericht reageren:'
scheduled_statuses:
- over_daily_limit: Je hebt de limiet van %{limit} in te plannen toots voor die dag overschreden
- over_total_limit: Je hebt de limiet van %{limit} in te plannen toots overschreden
- too_soon: De datum voor de ingeplande toot moet in de toekomst liggen
+ over_daily_limit: Je hebt de limiet van %{limit} in te plannen berichten voor vandaag overschreden
+ over_total_limit: Je hebt de limiet van %{limit} in te plannen berichten overschreden
+ too_soon: De datum voor het ingeplande bericht moet in de toekomst liggen
sessions:
activity: Laatst actief
browser: Webbrowser
@@ -1093,7 +1093,7 @@ nl:
adobe_air: Adobe Air
android: Android
blackberry: Blackberry
- chrome_os: ChromeOS
+ chrome_os: Chrome OS
firefox_os: Firefox OS
ios: iOS
linux: Linux
@@ -1144,12 +1144,12 @@ nl:
one: 'bevatte een niet toegestane hashtag: %{tags}'
other: 'bevatte niet toegestane hashtags: %{tags}'
errors:
- in_reply_not_found: De toot waarop je probeert te reageren lijkt niet te bestaan.
+ in_reply_not_found: Het bericht waarop je probeert te reageren lijkt niet te bestaan.
open_in_web: In de webapp openen
over_character_limit: Limiet van %{max} tekens overschreden
pin_errors:
- limit: Je hebt het maximaal aantal toots al vastgezet
- ownership: Een toot van iemand anders kan niet worden vastgezet
+ limit: Je hebt het maximaal aantal bericht al vastgemaakt
+ ownership: Een bericht van iemand anders kan niet worden vastgemaakt
reblog: Een boost kan niet worden vastgezet
poll:
total_people:
@@ -1174,7 +1174,7 @@ nl:
unlisted: Minder openbaar
unlisted_long: Aan iedereen tonen, maar niet op openbare tijdlijnen
stream_entries:
- pinned: Vastgemaakte toot
+ pinned: Vastgemaakt bericht
reblogged: boostte
sensitive_content: Gevoelige inhoud
tags:
@@ -1332,7 +1332,7 @@ nl:
otp_lost_help_html: Als je toegang tot beiden kwijt bent geraakt, neem dan contact op via %{email}
seamless_external_login: Je bent ingelogd via een externe dienst, daarom zijn wachtwoorden en e-mailinstellingen niet beschikbaar.
signed_in_as: 'Ingelogd als:'
- suspicious_sign_in_confirmation: Het lijkt er op dat je nog niet eerder op dit apparaat bent ingelogd, en je bent een tijdje niet ingelogd, dus sturen we een beveiligingscode naar je e-mailadres om te bevestigen dat jij het bent.
+ suspicious_sign_in_confirmation: Het lijkt er op dat je nog niet eerder op dit apparaat bent ingelogd, dus sturen we een beveiligingscode naar jouw e-mailadres om te bevestigen dat jij het bent.
verification:
explanation_html: 'Je kunt jezelf verifiëren als de eigenaar van de links in de metadata van jouw profiel. Hiervoor moet op de gelinkte website een link terug naar jouw Mastodonprofiel staan. Deze link moet het rel="me"
-attribuut bevatten. De omschrijving van de link maakt niet uit. Hier is een voorbeeld:'
verification: Verificatie
diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml
index 0fdfff517..348545cbf 100644
--- a/config/locales/pt-PT.yml
+++ b/config/locales/pt-PT.yml
@@ -490,6 +490,7 @@ pt-PT:
other: Tentativas em %{count} dias diferentes.
no_failures_recorded: Sem falhas registadas.
title: Disponibilidade
+ warning: A última tentativa de conectar a este servidor não foi bem sucedida
back_to_all: Todas
back_to_limited: Limitadas
back_to_warning: Aviso
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index 579ea6462..4440f3336 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -480,6 +480,7 @@ ru:
availability:
no_failures_recorded: Сбоев в записи нет.
title: Доступность
+ warning: Последняя попытка подключения к этому серверу не удалась
back_to_all: Все узлы
back_to_limited: Все ограниченные узлы
back_to_warning: Все узлы требующие внимания
@@ -742,6 +743,7 @@ ru:
none: "%{name} отправил(а) предупреждение %{target}"
sensitive: "%{name} отметил(а) учетную запись %{target} как деликатную"
silence: "%{name} ограничил(а) учетную запись %{target}"
+ appeal_approved: Обжаловано
appeal_pending: Обжалование в обработке
system_checks:
database_schema_check:
@@ -806,6 +808,8 @@ ru:
empty: Вы еще не определили пресеты предупреждений.
title: Управление шаблонами предупреждений
admin_mailer:
+ new_appeal:
+ subject: "%{username} обжалует решение модерации на %{instance}"
new_pending_account:
body: Ниже указана информация учётной записи. Вы можете одобрить или отклонить заявку.
subject: Новая учётная запись для рассмотрения на %{instance} (%{username})
diff --git a/config/locales/simple_form.fa.yml b/config/locales/simple_form.fa.yml
index 679d7d8fd..7fdd3b7a8 100644
--- a/config/locales/simple_form.fa.yml
+++ b/config/locales/simple_form.fa.yml
@@ -27,6 +27,8 @@ fa:
scheduled_at: برای انتشار فوری اعلامیه، خالی بگذارید
starts_at: اختیاری. در صورتی که اعلامیهتان محدود به بازهٔ زمانی خاصی است
text: میتوانید مانند یک بوق معمولی بنویسید. یادتان باشد که اعلامیهٔ شما فضای صفحهٔ کاربران را اشغال خواهد کرد
+ appeal:
+ text: فقط یک بار میتوانید برای اخطار اعتراض کنید
defaults:
autofollow: کسانی که از راه دعوتنامه عضو میشوند به طور خودکار پیگیر شما خواهند شد
avatar: یکی از قالبهای PNG یا GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
@@ -35,6 +37,7 @@ fa:
current_password: به دلایل امنیتی لطفاً رمز این حساب را وارد کنید
current_username: برای تأیید، لطفاً نام کاربری حساب فعلی را وارد کنید
digest: تنها وقتی فرستاده میشود که مدتی طولانی فعالیتی نداشته باشید و در این مدت برای شما پیغام خصوصیای نوشته شده باشد
+ discoverable: اجازه دهید حسابتان از طریق پیشنهادها، پرطرفدارها و سایر قابلیتها، توسط افراد غریبه قابل کشف باشد
email: به شما ایمیل تأییدی فرستاده خواهد شد
fields: شما میتوانید تا چهار مورد را در یک جدول در نمایهٔ خود نمایش دهید
header: یکی از قالبهای PNG یا GIF یا JPG. بیشترین اندازه %{size}. تصویر به اندازهٔ %{dimensions} پیکسل تبدیل خواهد شد
@@ -60,6 +63,7 @@ fa:
domain_allow:
domain: این دامین خواهد توانست دادهها از این سرور را دریافت کند و دادههای از این دامین در اینجا پردازش و ذخیره خواهند شد
email_domain_block:
+ domain: این میتواند نام دامنهای باشد که در نشانی رایانامه یا رکورد MX استفاده میشود. پس از ثبت نام بررسی خواهند شد.
with_dns_records: تلاشی برای resolve کردن رکوردهای ساناد دامنهٔ دادهشده انجام شده و نتیجه نیز مسدود خواهد شد
featured_tag:
name: 'شاید بخواهید چنین چیزهایی را به کار ببرید:'
@@ -116,6 +120,8 @@ fa:
scheduled_at: زمانبندی انتشار
starts_at: آغاز رویداد
text: اعلامیه
+ appeal:
+ text: توضیح دهید که چرا این تصمیم باید معکوس شود
defaults:
autofollow: دعوت از دیگران برای عضو شدن و پیگیری حساب شما
avatar: تصویر نمایه
@@ -194,6 +200,7 @@ fa:
sign_up_requires_approval: محدود کردن ثبت نامها
severity: قانون
notification_emails:
+ appeal: شخصی به تصمیم ناظر اعتراض کرد
digest: فرستادن رایانامههای خلاصه
favourite: وقتی کسی نوشتهٔ شما را پسندید ایمیل بفرست
follow: وقتی کسی پیگیر شما شد ایمیل بفرست
@@ -201,6 +208,8 @@ fa:
mention: وقتی کسی از شما نام برد ایمیل بفرست
pending_account: وقتی حساب تازهای نیاز به بازبینی داشت ایمیل بفرست
reblog: وقتی کسی نوشتهٔ شما را بازبوقید ایمیل بفرست
+ report: گزارش جدیدی فرستاده شد
+ trending_tag: روند جدیدی نیازمند بازبینی است
rule:
text: قانون
tag:
diff --git a/config/locales/simple_form.ku.yml b/config/locales/simple_form.ku.yml
index 4f38ae030..dcf723591 100644
--- a/config/locales/simple_form.ku.yml
+++ b/config/locales/simple_form.ku.yml
@@ -144,7 +144,7 @@ ku:
inbox_url: URLya guhêzkera wergirtî
irreversible: Li şûna veşartinê jê bibe
locale: Zimanê navrûyê
- locked: Ajimêr qefl bike
+ locked: Ajimêr kilît bike
max_uses: Hejmara bikaranîna herî zêde
new_password: Pêborîna nû
note: Jiyanname
diff --git a/config/locales/simple_form.nl.yml b/config/locales/simple_form.nl.yml
index 13d86443c..33968b508 100644
--- a/config/locales/simple_form.nl.yml
+++ b/config/locales/simple_form.nl.yml
@@ -7,18 +7,18 @@ nl:
account_migration:
acct: Vul de gebruikersnaam@domein van het account in, waarnaartoe je wilt verhuizen
account_warning_preset:
- text: Je kunt voor toots specifieke tekst gebruiken, zoals URL's, hashtags en vermeldingen
+ text: Je kunt specifieke tekst voor berichten gebruiken, zoals URL's, hashtags en vermeldingen
title: Optioneel. Niet zichtbaar voor de ontvanger
admin_account_action:
- include_statuses: De gebruiker ziet welke toots verantwoordelijk zijn voor de moderatieactie of waarschuwing
- send_email_notification: De gebruiker ontvangt een uitleg over wat er met hun account is gebeurd
- text_html: Optioneel. Je kunt voor toots specifieke tekst gebruiken. Om tijd te besparen kun je presets voor waarschuwingen toevoegen
+ include_statuses: De gebruiker ziet welke berichten verantwoordelijk zijn voor de moderatieactie of waarschuwing
+ send_email_notification: De gebruiker ontvangt een uitleg over wat er met diens account is gebeurd
+ text_html: Optioneel. Je kunt specifieke tekst voor berichten gebruiken. Om tijd te besparen kun je presets voor waarschuwingen toevoegen
type_html: Kies wat er met %{acct} moet gebeuren
types:
- disable: Voorkom dat de gebruiker hun account gebruikt, maar verwijder of verberg de inhoud niet.
+ disable: Voorkom dat de gebruiker diens account gebruikt, maar verwijder of verberg de inhoud niet.
none: Gebruik dit om een waarschuwing naar de gebruiker te sturen, zonder dat nog een andere actie wordt uitgevoerd.
sensitive: Forceer dat alle mediabijlagen van deze gebruiker als gevoelig worden gemarkeerd.
- silence: Voorkom dat de gebruiker openbare toots kan versturen, verberg hun toots en meldingen voor mensen die hen niet volgen.
+ silence: Voorkom dat de gebruiker openbare berichten kan versturen, verberg diens berichten en meldingen voor mensen die diegene niet volgen.
suspend: Alle interacties van en met dit account blokkeren en de inhoud verwijderen. Dit kan binnen dertig dagen worden teruggedraaid.
warning_preset_id: Optioneel. Je kunt nog steeds handmatig tekst toevoegen aan het eind van de voorinstelling
announcement:
@@ -26,7 +26,7 @@ nl:
ends_at: Optioneel. De publicatie van de mededeling wordt op dit tijdstip automatisch beëindigd
scheduled_at: Laat leeg om de mededeling meteen te publiceren
starts_at: Optioneel. In het geval dat jouw mededeling aan een bepaald tijdvak is gebonden
- text: Je kunt voor toots specifieke tekst gebruiken. Let op de ruimte die de mededeling op het scherm van de gebruiker inneemt
+ text: Je kunt specifieke tekst voor berichten gebruiken. Let op de ruimte die de mededeling op het scherm van de gebruiker inneemt
defaults:
autofollow: Mensen die zich via de uitnodiging hebben geregistreerd, volgen jou automatisch
avatar: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
@@ -39,20 +39,20 @@ nl:
fields: Je kan maximaal 4 items als een tabel op je profiel weergeven
header: PNG, GIF of JPG. Maximaal %{size}. Wordt teruggeschaald naar %{dimensions}px
inbox_url: Kopieer de URL van de voorpagina van de relayserver die je wil gebruiken
- irreversible: Gefilterde toots verdwijnen onomkeerbaar, zelfs als de filter later wordt verwijderd
+ irreversible: Gefilterde berichten verdwijnen onomkeerbaar, zelfs als de filter later wordt verwijderd
locale: De taal van de gebruikersomgeving, e-mails en pushmeldingen
locked: Door het goedkeuren van volgers handmatig bepalen wie jou mag volgen
password: Gebruik tenminste 8 tekens
phrase: Komt overeen ongeacht hoofd-/kleine letters of een inhoudswaarschuwing
scopes: Tot welke API's heeft de toepassing toegang. Wanneer je een toestemming van het bovenste niveau kiest, hoef je geen individuele toestemmingen meer te kiezen.
- setting_aggregate_reblogs: Geen nieuwe boosts tonen voor toots die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts)
+ setting_aggregate_reblogs: Geen nieuwe boosts tonen voor berichten die recentelijk nog zijn geboost (heeft alleen effect op nieuw ontvangen boosts)
setting_default_sensitive: Gevoelige media wordt standaard verborgen en kan met één klik worden getoond
setting_display_media_default: Als gevoelig gemarkeerde media verbergen
setting_display_media_hide_all: Media altijd verbergen
setting_display_media_show_all: Media altijd tonen
setting_hide_network: Wie jij volgt en wie jou volgen wordt niet op jouw profiel getoond
- setting_noindex: Heeft invloed op jouw openbare profiel en toots
- setting_show_application: De toepassing de je gebruikt om te tooten wordt in de gedetailleerde weergave van de toot getoond
+ setting_noindex: Heeft invloed op jouw openbare profiel en pagina's met berichten
+ setting_show_application: De toepassing de je gebruikt om berichten te plaatsen wordt in de gedetailleerde weergave van het bericht getoond
setting_use_blurhash: Wazige kleurovergangen zijn gebaseerd op de kleuren van de verborgen media, waarmee elk detail verdwijnt
setting_use_pending_items: De tijdlijn wordt bijgewerkt door op het aantal nieuwe items te klikken, in plaats van dat deze automatisch wordt bijgewerkt
username: Jouw gebruikersnaam is uniek op %{domain}
@@ -85,7 +85,7 @@ nl:
tag:
name: Je kunt elk woord met een hoofdletter beginnen, om zo bijvoorbeeld de tekst leesbaarder te maken
user:
- chosen_languages: Alleen toots in de aangevinkte talen worden op de openbare tijdlijnen getoond
+ chosen_languages: Alleen berichten in de aangevinkte talen worden op de openbare tijdlijnen getoond
labels:
account:
fields:
@@ -99,7 +99,7 @@ nl:
text: Tekst van preset
title: Titel
admin_account_action:
- include_statuses: Gerapporteerde toots aan de e-mail toevoegen
+ include_statuses: Gerapporteerde berichten aan de e-mail toevoegen
send_email_notification: Meld dit per e-mail aan de gebruiker
text: Aangepaste waarschuwing
type: Actie
@@ -146,22 +146,22 @@ nl:
setting_advanced_layout: Geavanceerde webomgeving inschakelen
setting_aggregate_reblogs: Boosts in tijdlijnen groeperen
setting_auto_play_gif: Speel geanimeerde GIF's automatisch af
- setting_boost_modal: Vraag voor het boosten van een toot een bevestiging
- setting_crop_images: Afbeeldingen bijsnijden tot 16x9 in toots op tijdlijnen
- setting_default_language: Taal van jouw toots
- setting_default_privacy: Standaardzichtbaarheid van jouw toots
+ setting_boost_modal: Vraag voor het boosten van een bericht een bevestiging
+ setting_crop_images: Afbeeldingen bijsnijden tot 16x9 in berichten op tijdlijnen
+ setting_default_language: Taal van jouw berichten
+ setting_default_privacy: Zichtbaarheid van nieuwe berichten
setting_default_sensitive: Media altijd als gevoelig markeren
- setting_delete_modal: Vraag voor het verwijderen van een toot een bevestiging
+ setting_delete_modal: Vraag voor het verwijderen van een bericht een bevestiging
setting_disable_swiping: Swipebewegingen uitschakelen
setting_display_media: Mediaweergave
setting_display_media_default: Standaard
setting_display_media_hide_all: Alles verbergen
setting_display_media_show_all: Alles tonen
- setting_expand_spoilers: Altijd toots met inhoudswaarschuwingen uitklappen
+ setting_expand_spoilers: Altijd berichten met inhoudswaarschuwingen uitklappen
setting_hide_network: Jouw volgers en wie je volgt verbergen
- setting_noindex: Jouw toots niet door zoekmachines laten indexeren
+ setting_noindex: Jouw berichten niet door zoekmachines laten indexeren
setting_reduce_motion: Langzamere animaties
- setting_show_application: Toepassing onthullen die je voor het verzenden van toots gebruikt
+ setting_show_application: Toepassing onthullen die je voor het verzenden van berichten gebruikt
setting_system_font_ui: Standaardlettertype van jouw systeem gebruiken
setting_theme: Thema website
setting_trends: Trends van vandaag tonen
@@ -195,19 +195,19 @@ nl:
severity: Regel
notification_emails:
digest: Periodiek e-mails met een samenvatting versturen
- favourite: Wanneer iemand jouw toot aan hun favorieten heeft toegevoegd
+ favourite: Wanneer iemand jouw bericht aan diens favorieten heeft toegevoegd
follow: Wanneer iemand jou is gaan volgen
follow_request: Wanneer iemand jou wil volgen
mention: Wanneer iemand jou heeft vermeld
pending_account: Wanneer een nieuw account moet worden beoordeeld
- reblog: Wanneer iemand jouw toot heeft geboost
+ reblog: Wanneer iemand jouw bericht heeft geboost
rule:
text: Regel
tag:
listable: Toestaan dat deze hashtag in zoekopdrachten en aanbevelingen te zien valt
name: Hashtag
trendable: Toestaan dat deze hashtag onder trends te zien valt
- usable: Toestaan dat deze hashtag in toots gebruikt mag worden
+ usable: Toestaan dat deze hashtag in berichten gebruikt mag worden
'no': Nee
recommended: Aanbevolen
required:
diff --git a/config/locales/simple_form.th.yml b/config/locales/simple_form.th.yml
index 6127cb9d5..b954b50fe 100644
--- a/config/locales/simple_form.th.yml
+++ b/config/locales/simple_form.th.yml
@@ -74,7 +74,7 @@ th:
text: นี่จะช่วยให้เราตรวจทานใบสมัครของคุณ
ip_block:
comment: ไม่จำเป็น จดจำเหตุผลที่คุณเพิ่มกฎนี้
- ip: ป้อนที่อยู่ IPv4 หรือ IPv6 คุณสามารถปิดกั้นทั้งช่วงได้โดยใช้ไวยากรณ์ CIDR ระวังอย่าล็อคตัวเองออก!
+ ip: ป้อนที่อยู่ IPv4 หรือ IPv6 คุณสามารถปิดกั้นทั้งช่วงได้โดยใช้ไวยากรณ์ CIDR ระวังอย่าล็อคตัวคุณเองออก!
severities:
no_access: ปิดกั้นการเข้าถึงทรัพยากรทั้งหมด
sign_up_requires_approval: การลงทะเบียนใหม่จะต้องมีการอนุมัติของคุณ
diff --git a/config/locales/sv.yml b/config/locales/sv.yml
index 40337ce69..441044516 100644
--- a/config/locales/sv.yml
+++ b/config/locales/sv.yml
@@ -404,6 +404,8 @@ sv:
status: Status
title: Följ rekommendationer
instances:
+ availability:
+ warning: Det senaste försöket att ansluta till denna värddator har misslyckats
back_to_all: Alla
back_to_limited: Begränsat
back_to_warning: Varning
diff --git a/config/locales/th.yml b/config/locales/th.yml
index beafc4da4..d6248b7a8 100644
--- a/config/locales/th.yml
+++ b/config/locales/th.yml
@@ -556,6 +556,9 @@ th:
other: "%{count} หมายเหตุ"
action_log: รายการบันทึกการตรวจสอบ
action_taken_by: ใช้การกระทำโดย
+ actions:
+ resolve_description_html: จะไม่ใช้การกระทำกับบัญชีที่รายงาน ไม่มีการบันทึกการดำเนินการ และจะปิดรายงาน
+ actions_description_html: ตัดสินใจว่าการกระทำใดที่จะใช้เพื่อแก้ปัญหารายงานนี้ หากคุณใช้การกระทำที่เป็นการลงโทษกับบัญชีที่รายงาน จะส่งการแจ้งเตือนอีเมลถึงเขา ยกเว้นเมื่อมีการเลือกหมวดหมู่ สแปม
are_you_sure: คุณแน่ใจหรือไม่?
assign_to_self: มอบหมายให้ฉัน
assigned: ผู้ควบคุมที่ได้รับมอบหมาย
@@ -883,6 +886,7 @@ th:
confirming: กำลังรอการยืนยันอีเมลให้เสร็จสมบูรณ์
functional: บัญชีของคุณทำงานได้อย่างเต็มที่
pending: ใบสมัครของคุณกำลังรอดำเนินการตรวจทานโดยพนักงานของเรา นี่อาจใช้เวลาสักครู่ คุณจะได้รับอีเมลหากใบสมัครของคุณได้รับการอนุมัติ
+ view_strikes: ดูการดำเนินการที่ผ่านมากับบัญชีของคุณ
too_fast: ส่งแบบฟอร์มเร็วเกินไป ลองอีกครั้ง
trouble_logging_in: มีปัญหาในการเข้าสู่ระบบ?
use_security_key: ใช้กุญแจความปลอดภัย
@@ -954,6 +958,7 @@ th:
submit: ส่งการอุทธรณ์
associated_report: รายงานที่เกี่ยวข้อง
created_at: ลงวันที่
+ description_html: นี่คือการกระทำที่ใช้กับบัญชีของคุณและคำเตือนที่ส่งถึงคุณโดยพนักงานของ %{instance}
recipient: ส่งถึง
status: 'โพสต์ #%{id}'
title: "%{action} จาก %{date}"
@@ -1410,9 +1415,11 @@ th:
user_mailer:
appeal_approved:
action: ไปยังบัญชีของคุณ
+ explanation: อนุมัติการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว บัญชีของคุณอยู่ในสถานะที่ดีอีกครั้งหนึ่ง
subject: อนุมัติการอุทธรณ์ของคุณจาก %{date} แล้ว
title: อนุมัติการอุทธรณ์แล้ว
appeal_rejected:
+ explanation: ปฏิเสธการอุทธรณ์การดำเนินการกับบัญชีของคุณเมื่อ %{strike_date} ที่คุณได้ส่งเมื่อ %{appeal_date} แล้ว
subject: ปฏิเสธการอุทธรณ์ของคุณจาก %{date} แล้ว
title: ปฏิเสธการอุทธรณ์แล้ว
backup_ready:
@@ -1431,6 +1438,12 @@ th:
categories:
spam: สแปม
violation: เนื้อหาละเมิดหลักเกณฑ์ชุมชนดังต่อไปนี้
+ explanation:
+ delete_statuses: มีการพบว่าบางโพสต์ของคุณละเมิดหนึ่งหลักเกณฑ์ชุมชนหรือมากกว่าและได้รับการเอาออกโดยผู้ควบคุมของ %{instance} ในเวลาต่อมา
+ disable: คุณไม่สามารถใช้บัญชีของคุณได้อีกต่อไป แต่โปรไฟล์และข้อมูลอื่น ๆ ของคุณยังคงอยู่ในสภาพเดิม คุณสามารถขอข้อมูลสำรองของข้อมูลของคุณ เปลี่ยนการตั้งค่าบัญชี หรือลบบัญชีของคุณ
+ mark_statuses_as_sensitive: ทำเครื่องหมายบางโพสต์ของคุณว่าละเอียดอ่อนโดยผู้ควบคุมของ %{instance} แล้ว นี่หมายความว่าผู้คนจะต้องแตะสื่อในโพสต์ก่อนที่จะแสดงตัวอย่าง คุณสามารถทำเครื่องหมายสื่อว่าละเอียดอ่อนด้วยตัวคุณเองเมื่อโพสต์ในอนาคต
+ sensitive: จากนี้ไป จะทำเครื่องหมายไฟล์สื่อที่อัปโหลดทั้งหมดของคุณว่าละเอียดอ่อนและซ่อนอยู่หลังการคลิกไปยังคำเตือน
+ silence: คุณยังคงสามารถใช้บัญชีของคุณแต่เฉพาะผู้คนที่กำลังติดตามคุณอยู่แล้วเท่านั้นที่จะเห็นโพสต์ของคุณในเซิร์ฟเวอร์นี้ และอาจแยกคุณออกจากคุณลักษณะการค้นพบต่าง ๆ อย่างไรก็ตาม ผู้อื่นอาจยังติดตามคุณด้วยตนเอง
reason: 'เหตุผล:'
statuses: 'โพสต์ที่อ้างถึง:'
subject:
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 6e6477b92..47a55326b 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -490,6 +490,7 @@ tr:
other: "%{count} farklı gün başarısız girişim."
no_failures_recorded: Kayıtlı başarısızlık yok.
title: Ulaşılabilirlik
+ warning: Bu sunucuya önceki bağlanma denemesi başarısız olmuştu
back_to_all: Tümü
back_to_limited: Sınırlı
back_to_warning: Uyarı
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 6561a5716..6341b4eed 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -479,6 +479,7 @@ zh-CN:
other: 在 %{count} 天中尝试失败。
no_failures_recorded: 没有失败记录。
title: 可用性
+ warning: 上一次连接到此服务器的尝试失败了
back_to_all: 全部
back_to_limited: 受限
back_to_warning: 警告
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
index 43a71a5dd..1b64f9893 100644
--- a/config/locales/zh-TW.yml
+++ b/config/locales/zh-TW.yml
@@ -479,6 +479,7 @@ zh-TW:
other: 錯誤嘗試於 %{count} 天。
no_failures_recorded: 報告中沒有錯誤。
title: 可用狀態
+ warning: 上一次嘗試連線至本伺服器失敗
back_to_all: 所有
back_to_limited: 受限制的
back_to_warning: 警告
--
cgit
From f65eaa5aae8a71431bdcfb9c49c869cbdbc4da3f Mon Sep 17 00:00:00 2001
From: Claire
Date: Fri, 25 Mar 2022 21:00:59 +0100
Subject: Add admin dashboard checks for Elasticsearch version (#17863)
---
app/lib/admin/system_check.rb | 1 +
app/lib/admin/system_check/elasticsearch_check.rb | 39 +++++++++++++++++++++++
config/locales/en.yml | 5 +++
3 files changed, 45 insertions(+)
create mode 100644 app/lib/admin/system_check/elasticsearch_check.rb
(limited to 'config')
diff --git a/app/lib/admin/system_check.rb b/app/lib/admin/system_check.rb
index afb20cb47..877a42ef6 100644
--- a/app/lib/admin/system_check.rb
+++ b/app/lib/admin/system_check.rb
@@ -5,6 +5,7 @@ class Admin::SystemCheck
Admin::SystemCheck::DatabaseSchemaCheck,
Admin::SystemCheck::SidekiqProcessCheck,
Admin::SystemCheck::RulesCheck,
+ Admin::SystemCheck::ElasticsearchCheck,
].freeze
def self.perform
diff --git a/app/lib/admin/system_check/elasticsearch_check.rb b/app/lib/admin/system_check/elasticsearch_check.rb
new file mode 100644
index 000000000..1b48a5415
--- /dev/null
+++ b/app/lib/admin/system_check/elasticsearch_check.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
+ def pass?
+ return true unless Chewy.enabled?
+
+ running_version.present? && compatible_version?
+ end
+
+ def message
+ if running_version.present?
+ Admin::SystemCheck::Message.new(:elasticsearch_version_check, I18n.t('admin.system_checks.elasticsearch_version_check.version_comparison', running_version: running_version, required_version: required_version))
+ else
+ Admin::SystemCheck::Message.new(:elasticsearch_running_check)
+ end
+ end
+
+ private
+
+ def running_version
+ @running_version ||= begin
+ Chewy.client.info['version']['number']
+ rescue Faraday::ConnectionFailed
+ nil
+ end
+ end
+
+ def required_version
+ '7.x'
+ end
+
+ def compatible_version?
+ Gem::Version.new(running_version) >= Gem::Version.new(required_version)
+ end
+
+ def missing_queues
+ @missing_queues ||= Sidekiq::ProcessSet.new.reduce(SIDEKIQ_QUEUES) { |queues, process| queues - process['queues'] }
+ end
+end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a6ded38f7..db29922fa 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -780,6 +780,11 @@ en:
message_html: You haven't defined any server rules.
sidekiq_process_check:
message_html: No Sidekiq process running for the %{value} queue(s). Please review your Sidekiq configuration
+ elasticsearch_running_check:
+ message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search
+ elasticsearch_version_check:
+ message_html: "Incompatible Elasticsearch version: %{value}"
+ version_comparison: "Elasticsearch %{running_version} is running while %{required_version} is required"
tags:
review: Review status
updated_msg: Hashtag settings updated successfully
--
cgit
From f572a68a0cec49551948858dc0957bc7703e580d Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Fri, 25 Mar 2022 21:41:17 +0100
Subject: Chore: i18n-tasks normalize (#17873)
---
config/locales/en.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
(limited to 'config')
diff --git a/config/locales/en.yml b/config/locales/en.yml
index db29922fa..5fa3c012e 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -775,16 +775,16 @@ en:
system_checks:
database_schema_check:
message_html: There are pending database migrations. Please run them to ensure the application behaves as expected
+ elasticsearch_running_check:
+ message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search
+ elasticsearch_version_check:
+ message_html: 'Incompatible Elasticsearch version: %{value}'
+ version_comparison: Elasticsearch %{running_version} is running while %{required_version} is required
rules_check:
action: Manage server rules
message_html: You haven't defined any server rules.
sidekiq_process_check:
message_html: No Sidekiq process running for the %{value} queue(s). Please review your Sidekiq configuration
- elasticsearch_running_check:
- message_html: Could not connect to Elasticsearch. Please check that it is running, or disable full-text search
- elasticsearch_version_check:
- message_html: "Incompatible Elasticsearch version: %{value}"
- version_comparison: "Elasticsearch %{running_version} is running while %{required_version} is required"
tags:
review: Review status
updated_msg: Hashtag settings updated successfully
--
cgit
From cefa526c6d3a45df2d0fcb7643ced828e2e87dea Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Sat, 26 Mar 2022 02:53:34 +0100
Subject: Refactor formatter (#17828)
* Refactor formatter
* Move custom emoji pre-rendering logic to view helpers
* Move more methods out of Formatter
* Fix code style issues
* Remove Formatter
* Add inline poll options to RSS feeds
* Remove unused helper method
* Fix code style issues
* Various fixes and improvements
* Fix test
---
app/chewy/statuses_index.rb | 2 +-
app/controllers/api/web/embeds_controller.rb | 2 +-
app/helpers/accounts_helper.rb | 6 +-
app/helpers/admin/trends/statuses_helper.rb | 5 +-
app/helpers/application_helper.rb | 4 +
app/helpers/formatting_helper.rb | 19 +
app/helpers/routing_helper.rb | 3 +-
app/helpers/statuses_helper.rb | 14 -
app/lib/activitypub/activity/create.rb | 4 +-
app/lib/emoji_formatter.rb | 98 ++++
app/lib/extractor.rb | 82 +++-
app/lib/feed_manager.rb | 3 +-
app/lib/formatter.rb | 294 -----------
app/lib/html_aware_formatter.rb | 38 ++
app/lib/plain_text_formatter.rb | 30 ++
app/lib/rss/serializer.rb | 23 +-
app/lib/text_formatter.rb | 158 ++++++
app/mailers/application_mailer.rb | 1 +
app/serializers/activitypub/actor_serializer.rb | 7 +-
app/serializers/activitypub/note_serializer.rb | 6 +-
app/serializers/rest/account_serializer.rb | 7 +-
app/serializers/rest/announcement_serializer.rb | 4 +-
app/serializers/rest/status_edit_serializer.rb | 4 +-
app/serializers/rest/status_serializer.rb | 4 +-
app/services/fetch_link_card_service.rb | 2 +-
app/views/accounts/_bio.html.haml | 6 +-
app/views/admin/accounts/show.html.haml | 6 +-
app/views/admin/reports/_status.html.haml | 6 +-
app/views/admin/reports/show.html.haml | 2 +-
app/views/directories/index.html.haml | 2 +-
app/views/disputes/strikes/show.html.haml | 2 +-
app/views/notification_mailer/_status.html.haml | 4 +-
app/views/notification_mailer/_status.text.erb | 2 +-
app/views/notification_mailer/digest.text.erb | 2 +-
app/views/statuses/_detailed_status.html.haml | 5 +-
app/views/statuses/_poll.html.haml | 4 +-
app/views/statuses/_simple_status.html.haml | 5 +-
app/views/user_mailer/warning.html.haml | 2 +-
config/initializers/twitter_regex.rb | 26 -
spec/lib/emoji_formatter_spec.rb | 55 +++
spec/lib/formatter_spec.rb | 626 ------------------------
spec/lib/html_aware_formatter.rb | 44 ++
spec/lib/plain_text_formatter_spec.rb | 24 +
spec/lib/text_formatter_spec.rb | 313 ++++++++++++
44 files changed, 932 insertions(+), 1024 deletions(-)
create mode 100644 app/helpers/formatting_helper.rb
create mode 100644 app/lib/emoji_formatter.rb
delete mode 100644 app/lib/formatter.rb
create mode 100644 app/lib/html_aware_formatter.rb
create mode 100644 app/lib/plain_text_formatter.rb
create mode 100644 app/lib/text_formatter.rb
create mode 100644 spec/lib/emoji_formatter_spec.rb
delete mode 100644 spec/lib/formatter_spec.rb
create mode 100644 spec/lib/html_aware_formatter.rb
create mode 100644 spec/lib/plain_text_formatter_spec.rb
create mode 100644 spec/lib/text_formatter_spec.rb
(limited to 'config')
diff --git a/app/chewy/statuses_index.rb b/app/chewy/statuses_index.rb
index 65cbb6fcd..d119f7cac 100644
--- a/app/chewy/statuses_index.rb
+++ b/app/chewy/statuses_index.rb
@@ -57,7 +57,7 @@ class StatusesIndex < Chewy::Index
field :id, type: 'long'
field :account_id, type: 'long'
- field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
+ field :text, type: 'text', value: ->(status) { [status.spoiler_text, PlainTextFormatter.new(status.text, status.local?).to_s].concat(status.ordered_media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
field :stemmed, type: 'text', analyzer: 'content'
end
diff --git a/app/controllers/api/web/embeds_controller.rb b/app/controllers/api/web/embeds_controller.rb
index 741ba910f..58f6345e6 100644
--- a/app/controllers/api/web/embeds_controller.rb
+++ b/app/controllers/api/web/embeds_controller.rb
@@ -15,7 +15,7 @@ class Api::Web::EmbedsController < Api::Web::BaseController
return not_found if oembed.nil?
begin
- oembed[:html] = Formatter.instance.sanitize(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
+ oembed[:html] = Sanitize.fragment(oembed[:html], Sanitize::Config::MASTODON_OEMBED)
rescue ArgumentError
return not_found
end
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
index a33961724..557f60f26 100644
--- a/app/helpers/accounts_helper.rb
+++ b/app/helpers/accounts_helper.rb
@@ -2,10 +2,12 @@
module AccountsHelper
def display_name(account, **options)
+ str = account.display_name.presence || account.username
+
if options[:custom_emojify]
- Formatter.instance.format_display_name(account, **options)
+ prerender_custom_emojis(h(str), account.emojis)
else
- account.display_name.presence || account.username
+ str
end
end
diff --git a/app/helpers/admin/trends/statuses_helper.rb b/app/helpers/admin/trends/statuses_helper.rb
index d16e3dd12..214c1e2a6 100644
--- a/app/helpers/admin/trends/statuses_helper.rb
+++ b/app/helpers/admin/trends/statuses_helper.rb
@@ -12,9 +12,6 @@ module Admin::Trends::StatusesHelper
return '' if text.blank?
- html = Formatter.instance.send(:encode, text)
- html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
-
- html.html_safe # rubocop:disable Rails/OutputSafety
+ prerender_custom_emojis(h(text), status.emojis)
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index e997570b5..651a98a85 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -239,4 +239,8 @@ module ApplicationHelper
end
end.values
end
+
+ def prerender_custom_emojis(html, custom_emojis)
+ EmojiFormatter.new(html, custom_emojis, animate: prefers_autoplay?).to_s
+ end
end
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
new file mode 100644
index 000000000..66e9e1e91
--- /dev/null
+++ b/app/helpers/formatting_helper.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module FormattingHelper
+ def html_aware_format(text, local, options = {})
+ HtmlAwareFormatter.new(text, local, options).to_s
+ end
+
+ def linkify(text, options = {})
+ TextFormatter.new(text, options).to_s
+ end
+
+ def extract_plain_text(text, local)
+ PlainTextFormatter.new(text, local).to_s
+ end
+
+ def status_content_format(status)
+ html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []))
+ end
+end
diff --git a/app/helpers/routing_helper.rb b/app/helpers/routing_helper.rb
index fb24a1b28..f95f46a56 100644
--- a/app/helpers/routing_helper.rb
+++ b/app/helpers/routing_helper.rb
@@ -2,6 +2,7 @@
module RoutingHelper
extend ActiveSupport::Concern
+
include Rails.application.routes.url_helpers
include ActionView::Helpers::AssetTagHelper
include Webpacker::Helper
@@ -22,8 +23,6 @@ module RoutingHelper
full_asset_url(asset_pack_path(source, **options))
end
- private
-
def use_storage?
Rails.configuration.x.use_s3 || Rails.configuration.x.use_swift
end
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index d328f89b7..e92b4c839 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -113,20 +113,6 @@ module StatusesHelper
end
end
- private
-
- def simplified_text(text)
- text.dup.tap do |new_text|
- URI.extract(new_text).each do |url|
- new_text.gsub!(url, '')
- end
-
- new_text.gsub!(Account::MENTION_RE, '')
- new_text.gsub!(Tag::HASHTAG_RE, '')
- new_text.gsub!(/\s+/, '')
- end
- end
-
def embedded_view?
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
end
diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index ea8d146d4..f4f98e29c 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ActivityPub::Activity::Create < ActivityPub::Activity
+ include FormattingHelper
+
def perform
dereference_object!
@@ -367,7 +369,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end
def converted_text
- Formatter.instance.linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
+ linkify([@status_parser.title.presence, @status_parser.spoiler_text.presence, @status_parser.url || @status_parser.uri].compact.join("\n\n"))
end
def unsupported_media_type?(mime_type)
diff --git a/app/lib/emoji_formatter.rb b/app/lib/emoji_formatter.rb
new file mode 100644
index 000000000..f808f3a22
--- /dev/null
+++ b/app/lib/emoji_formatter.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+class EmojiFormatter
+ include RoutingHelper
+
+ DISALLOWED_BOUNDING_REGEX = /[[:alnum:]:]/.freeze
+
+ attr_reader :html, :custom_emojis, :options
+
+ # @param [ActiveSupport::SafeBuffer] html
+ # @param [Array] custom_emojis
+ # @param [Hash] options
+ # @option options [Boolean] :animate
+ def initialize(html, custom_emojis, options = {})
+ raise ArgumentError unless html.html_safe?
+
+ @html = html
+ @custom_emojis = custom_emojis
+ @options = options
+ end
+
+ def to_s
+ return html if custom_emojis.empty? || html.blank?
+
+ i = -1
+ tag_open_index = nil
+ inside_shortname = false
+ shortname_start_index = -1
+ invisible_depth = 0
+ last_index = 0
+ result = ''.dup
+
+ while i + 1 < html.size
+ i += 1
+
+ if invisible_depth.zero? && inside_shortname && html[i] == ':'
+ inside_shortname = false
+ shortcode = html[shortname_start_index + 1..i - 1]
+ char_after = html[i + 1]
+
+ next unless (char_after.nil? || !DISALLOWED_BOUNDING_REGEX.match?(char_after)) && (emoji = emoji_map[shortcode])
+
+ result << html[last_index..shortname_start_index - 1] if shortname_start_index.positive?
+ result << image_for_emoji(shortcode, emoji)
+ last_index = i + 1
+ elsif tag_open_index && html[i] == '>'
+ tag = html[tag_open_index..i]
+ tag_open_index = nil
+
+ if invisible_depth.positive?
+ invisible_depth += count_tag_nesting(tag)
+ elsif tag == ''
+ invisible_depth = 1
+ end
+ elsif html[i] == '<'
+ tag_open_index = i
+ inside_shortname = false
+ elsif !tag_open_index && html[i] == ':' && (i.zero? || !DISALLOWED_BOUNDING_REGEX.match?(html[i - 1]))
+ inside_shortname = true
+ shortname_start_index = i
+ end
+ end
+
+ result << html[last_index..-1]
+
+ result.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ private
+
+ def emoji_map
+ @emoji_map ||= custom_emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
+ end
+
+ def count_tag_nesting(tag)
+ if tag[1] == '/'
+ -1
+ elsif tag[-2] == '/'
+ 0
+ else
+ 1
+ end
+ end
+
+ def image_for_emoji(shortcode, emoji)
+ original_url, static_url = emoji
+
+ if animate?
+ image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
+ else
+ image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
+ end
+ end
+
+ def animate?
+ @options[:animate]
+ end
+end
diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb
index 8020aa916..ef9407864 100644
--- a/app/lib/extractor.rb
+++ b/app/lib/extractor.rb
@@ -5,18 +5,34 @@ module Extractor
module_function
- # :yields: username, list_slug, start, end
+ def extract_entities_with_indices(text, options = {}, &block)
+ entities = begin
+ extract_urls_with_indices(text, options) +
+ extract_hashtags_with_indices(text, check_url_overlap: false) +
+ extract_mentions_or_lists_with_indices(text) +
+ extract_extra_uris_with_indices(text)
+ end
+
+ return [] if entities.empty?
+
+ entities = remove_overlapping_entities(entities)
+ entities.each(&block) if block_given?
+ entities
+ end
+
def extract_mentions_or_lists_with_indices(text)
- return [] unless Twitter::TwitterText::Regex[:at_signs].match?(text)
+ return [] unless text && Twitter::TwitterText::Regex[:at_signs].match?(text)
possible_entries = []
- text.to_s.scan(Account::MENTION_RE) do |screen_name, _|
+ text.scan(Account::MENTION_RE) do |screen_name, _|
match_data = $LAST_MATCH_INFO
- after = $'
+ after = $'
+
unless Twitter::TwitterText::Regex[:end_mention_match].match?(after)
start_position = match_data.char_begin(1) - 1
- end_position = match_data.char_end(1)
+ end_position = match_data.char_end(1)
+
possible_entries << {
screen_name: screen_name,
indices: [start_position, end_position],
@@ -29,36 +45,70 @@ module Extractor
yield mention[:screen_name], mention[:indices].first, mention[:indices].last
end
end
+
possible_entries
end
- def extract_hashtags_with_indices(text, **)
- return [] unless /#/.match?(text)
+ def extract_hashtags_with_indices(text, _options = {})
+ return [] unless text&.index('#')
+
+ possible_entries = []
- tags = []
text.scan(Tag::HASHTAG_RE) do |hash_text, _|
- match_data = $LAST_MATCH_INFO
+ match_data = $LAST_MATCH_INFO
start_position = match_data.char_begin(1) - 1
- end_position = match_data.char_end(1)
- after = $'
+ end_position = match_data.char_end(1)
+ after = $'
+
if %r{\A://}.match?(after)
hash_text.match(/(.+)(https?\Z)/) do |matched|
- hash_text = matched[1]
+ hash_text = matched[1]
end_position -= matched[2].codepoint_length
end
end
- tags << {
+ possible_entries << {
hashtag: hash_text,
indices: [start_position, end_position],
}
end
- tags.each { |tag| yield tag[:hashtag], tag[:indices].first, tag[:indices].last } if block_given?
- tags
+ if block_given?
+ possible_entries.each do |tag|
+ yield tag[:hashtag], tag[:indices].first, tag[:indices].last
+ end
+ end
+
+ possible_entries
end
def extract_cashtags_with_indices(_text)
- [] # always returns empty array
+ []
+ end
+
+ def extract_extra_uris_with_indices(text)
+ return [] unless text&.index(':')
+
+ possible_entries = []
+
+ text.scan(Twitter::TwitterText::Regex[:valid_extended_uri]) do
+ valid_uri_match_data = $LAST_MATCH_INFO
+
+ start_position = valid_uri_match_data.char_begin(3)
+ end_position = valid_uri_match_data.char_end(3)
+
+ possible_entries << {
+ url: valid_uri_match_data[3],
+ indices: [start_position, end_position],
+ }
+ end
+
+ if block_given?
+ possible_entries.each do |url|
+ yield url[:url], url[:indices].first, url[:indices].last
+ end
+ end
+
+ possible_entries
end
end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index 46a55c7a4..53d1390d4 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -5,6 +5,7 @@ require 'singleton'
class FeedManager
include Singleton
include Redisable
+ include FormattingHelper
# Maximum number of items stored in a single feed
MAX_ITEMS = 400
@@ -445,7 +446,7 @@ class FeedManager
status = status.reblog if status.reblog?
combined_text = [
- Formatter.instance.plaintext(status),
+ extract_plain_text(status.text, status.local?),
status.spoiler_text,
status.preloadable_poll ? status.preloadable_poll.options.join("\n\n") : nil,
status.ordered_media_attachments.map(&:description).join("\n\n"),
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
deleted file mode 100644
index b6a13163d..000000000
--- a/app/lib/formatter.rb
+++ /dev/null
@@ -1,294 +0,0 @@
-# frozen_string_literal: true
-
-require 'singleton'
-
-class Formatter
- include Singleton
- include RoutingHelper
-
- include ActionView::Helpers::TextHelper
-
- def format(status, **options)
- if status.respond_to?(:reblog?) && status.reblog?
- prepend_reblog = status.reblog.account.acct
- status = status.proper
- else
- prepend_reblog = false
- end
-
- raw_content = status.text
-
- if options[:inline_poll_options] && status.preloadable_poll
- raw_content = raw_content + "\n\n" + status.preloadable_poll.options.map { |title| "[ ] #{title}" }.join("\n")
- end
-
- return '' if raw_content.blank?
-
- unless status.local?
- html = reformat(raw_content)
- html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
- return html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- linkable_accounts = status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []
- linkable_accounts << status.account
-
- html = raw_content
- html = "RT @#{prepend_reblog} #{html}" if prepend_reblog
- html = encode_and_link_urls(html, linkable_accounts)
- html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
- html = simple_format(html, {}, sanitize: false)
- html = html.delete("\n")
-
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- def reformat(html)
- sanitize(html, Sanitize::Config::MASTODON_STRICT)
- rescue ArgumentError
- ''
- end
-
- def plaintext(status)
- return status.text if status.local?
-
- text = status.text.gsub(/(
|
|<\/p>)+/) { |match| "#{match}\n" }
- strip_tags(text)
- end
-
- def simplified_format(account, **options)
- return '' if account.note.blank?
-
- html = account.local? ? linkify(account.note) : reformat(account.note)
- html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- def sanitize(html, config)
- Sanitize.fragment(html, config)
- end
-
- def format_spoiler(status, **options)
- html = encode(status.spoiler_text)
- html = encode_custom_emojis(html, status.emojis, options[:autoplay])
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- def format_poll_option(status, option, **options)
- html = encode(option.title)
- html = encode_custom_emojis(html, status.emojis, options[:autoplay])
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- def format_display_name(account, **options)
- html = encode(account.display_name.presence || account.username)
- html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- def format_field(account, str, **options)
- html = account.local? ? encode_and_link_urls(str, me: true, with_domain: true) : reformat(str)
- html = encode_custom_emojis(html, account.emojis, options[:autoplay]) if options[:custom_emojify]
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- def linkify(text)
- html = encode_and_link_urls(text)
- html = simple_format(html, {}, sanitize: false)
- html = html.delete("\n")
-
- html.html_safe # rubocop:disable Rails/OutputSafety
- end
-
- private
-
- def html_entities
- @html_entities ||= HTMLEntities.new
- end
-
- def encode(html)
- html_entities.encode(html)
- end
-
- def encode_and_link_urls(html, accounts = nil, options = {})
- entities = utf8_friendly_extractor(html, extract_url_without_protocol: false)
-
- if accounts.is_a?(Hash)
- options = accounts
- accounts = nil
- end
-
- rewrite(html.dup, entities) do |entity|
- if entity[:url]
- link_to_url(entity, options)
- elsif entity[:hashtag]
- link_to_hashtag(entity)
- elsif entity[:screen_name]
- link_to_mention(entity, accounts, options)
- end
- end
- end
-
- def count_tag_nesting(tag)
- if tag[1] == '/' then -1
- elsif tag[-2] == '/' then 0
- else 1
- end
- end
-
- # rubocop:disable Metrics/BlockNesting
- def encode_custom_emojis(html, emojis, animate = false)
- return html if emojis.empty?
-
- emoji_map = emojis.each_with_object({}) { |e, h| h[e.shortcode] = [full_asset_url(e.image.url), full_asset_url(e.image.url(:static))] }
-
- i = -1
- tag_open_index = nil
- inside_shortname = false
- shortname_start_index = -1
- invisible_depth = 0
-
- while i + 1 < html.size
- i += 1
-
- if invisible_depth.zero? && inside_shortname && html[i] == ':'
- shortcode = html[shortname_start_index + 1..i - 1]
- emoji = emoji_map[shortcode]
-
- if emoji
- original_url, static_url = emoji
- replacement = begin
- if animate
- image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
- else
- image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
- end
- end
- before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
- html = before_html + replacement + html[i + 1..-1]
- i += replacement.size - (shortcode.size + 2) - 1
- else
- i -= 1
- end
-
- inside_shortname = false
- elsif tag_open_index && html[i] == '>'
- tag = html[tag_open_index..i]
- tag_open_index = nil
- if invisible_depth.positive?
- invisible_depth += count_tag_nesting(tag)
- elsif tag == ''
- invisible_depth = 1
- end
- elsif html[i] == '<'
- tag_open_index = i
- inside_shortname = false
- elsif !tag_open_index && html[i] == ':'
- inside_shortname = true
- shortname_start_index = i
- end
- end
-
- html
- end
- # rubocop:enable Metrics/BlockNesting
-
- def rewrite(text, entities)
- text = text.to_s
-
- # Sort by start index
- entities = entities.sort_by do |entity|
- indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
- indices.first
- end
-
- result = []
-
- last_index = entities.reduce(0) do |index, entity|
- indices = entity.respond_to?(:indices) ? entity.indices : entity[:indices]
- result << encode(text[index...indices.first])
- result << yield(entity)
- indices.last
- end
-
- result << encode(text[last_index..-1])
-
- result.flatten.join
- end
-
- def utf8_friendly_extractor(text, options = {})
- # Note: I couldn't obtain list_slug with @user/list-name format
- # for mention so this requires additional check
- special = Extractor.extract_urls_with_indices(text, options)
- standard = Extractor.extract_entities_with_indices(text, options)
- extra = Extractor.extract_extra_uris_with_indices(text, options)
-
- Extractor.remove_overlapping_entities(special + standard + extra)
- end
-
- def link_to_url(entity, options = {})
- url = Addressable::URI.parse(entity[:url])
- html_attrs = { target: '_blank', rel: 'nofollow noopener noreferrer' }
-
- html_attrs[:rel] = "me #{html_attrs[:rel]}" if options[:me]
-
- Twitter::TwitterText::Autolink.send(:link_to_text, entity, link_html(entity[:url]), url, html_attrs)
- rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
- encode(entity[:url])
- end
-
- def link_to_mention(entity, linkable_accounts, options = {})
- acct = entity[:screen_name]
-
- return link_to_account(acct, options) unless linkable_accounts
-
- same_username_hits = 0
- account = nil
- username, domain = acct.split('@')
- domain = nil if TagManager.instance.local_domain?(domain)
-
- linkable_accounts.each do |item|
- same_username = item.username.casecmp(username).zero?
- same_domain = item.domain.nil? ? domain.nil? : item.domain.casecmp(domain)&.zero?
-
- if same_username && !same_domain
- same_username_hits += 1
- elsif same_username && same_domain
- account = item
- end
- end
-
- account ? mention_html(account, with_domain: same_username_hits.positive? || options[:with_domain]) : "@#{encode(acct)}"
- end
-
- def link_to_account(acct, options = {})
- username, domain = acct.split('@')
-
- domain = nil if TagManager.instance.local_domain?(domain)
- account = EntityCache.instance.mention(username, domain)
-
- account ? mention_html(account, with_domain: options[:with_domain]) : "@#{encode(acct)}"
- end
-
- def link_to_hashtag(entity)
- hashtag_html(entity[:hashtag])
- end
-
- def link_html(url)
- url = Addressable::URI.parse(url).to_s
- prefix = url.match(/\A(https?:\/\/(www\.)?|xmpp:)/).to_s
- text = url[prefix.length, 30]
- suffix = url[prefix.length + 30..-1]
- cutoff = url[prefix.length..-1].length > 30
-
- "#{encode(prefix)}#{encode(text)}#{encode(suffix)}"
- end
-
- def hashtag_html(tag)
- "##{encode(tag)}"
- end
-
- def mention_html(account, with_domain: false)
- "@#{encode(with_domain ? account.pretty_acct : account.username)}"
- end
-end
diff --git a/app/lib/html_aware_formatter.rb b/app/lib/html_aware_formatter.rb
new file mode 100644
index 000000000..64edba09b
--- /dev/null
+++ b/app/lib/html_aware_formatter.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+class HtmlAwareFormatter
+ attr_reader :text, :local, :options
+
+ alias local? local
+
+ # @param [String] text
+ # @param [Boolean] local
+ # @param [Hash] options
+ def initialize(text, local, options = {})
+ @text = text
+ @local = local
+ @options = options
+ end
+
+ def to_s
+ return ''.html_safe if text.blank?
+
+ if local?
+ linkify
+ else
+ reformat.html_safe # rubocop:disable Rails/OutputSafety
+ end
+ rescue ArgumentError
+ ''.html_safe
+ end
+
+ private
+
+ def reformat
+ Sanitize.fragment(text, Sanitize::Config::MASTODON_STRICT)
+ end
+
+ def linkify
+ TextFormatter.new(text, options).to_s
+ end
+end
diff --git a/app/lib/plain_text_formatter.rb b/app/lib/plain_text_formatter.rb
new file mode 100644
index 000000000..08aa29696
--- /dev/null
+++ b/app/lib/plain_text_formatter.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class PlainTextFormatter
+ include ActionView::Helpers::TextHelper
+
+ NEWLINE_TAGS_RE = /(
|
|<\/p>)+/.freeze
+
+ attr_reader :text, :local
+
+ alias local? local
+
+ def initialize(text, local)
+ @text = text
+ @local = local
+ end
+
+ def to_s
+ if local?
+ text
+ else
+ strip_tags(insert_newlines).chomp
+ end
+ end
+
+ private
+
+ def insert_newlines
+ text.gsub(NEWLINE_TAGS_RE) { |match| "#{match}\n" }
+ end
+end
diff --git a/app/lib/rss/serializer.rb b/app/lib/rss/serializer.rb
index 7e3ed1f17..d44e94221 100644
--- a/app/lib/rss/serializer.rb
+++ b/app/lib/rss/serializer.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class RSS::Serializer
+ include FormattingHelper
+
private
def render_statuses(builder, statuses)
@@ -9,7 +11,7 @@ class RSS::Serializer
item.title(status_title(status))
.link(ActivityPub::TagManager.instance.url_for(status))
.pub_date(status.created_at)
- .description(status.spoiler_text.presence || Formatter.instance.format(status, inline_poll_options: true).to_str)
+ .description(status_description(status))
status.ordered_media_attachments.each do |media|
item.enclosure(full_asset_url(media.file.url(:original, false)), media.file.content_type, media.file.size)
@@ -19,9 +21,8 @@ class RSS::Serializer
end
def status_title(status)
- return "#{status.account.acct} deleted status" if status.destroyed?
-
preview = status.proper.spoiler_text.presence || status.proper.text
+
if preview.length > 30 || preview[0, 30].include?("\n")
preview = preview[0, 30]
preview = preview[0, preview.index("\n").presence || 30] + '…'
@@ -35,4 +36,20 @@ class RSS::Serializer
"#{status.account.acct}: #{preview}"
end
end
+
+ def status_description(status)
+ if status.proper.spoiler_text?
+ status.proper.spoiler_text
+ else
+ html = status_content_format(status.proper).to_str
+ after_html = ''
+
+ if status.proper.preloadable_poll
+ poll_options_html = status.proper.preloadable_poll.options.map { |o| "[ ] #{o}" }.join('
')
+ after_html = "#{poll_options_html}
"
+ end
+
+ "#{html}#{after_html}"
+ end
+ end
end
diff --git a/app/lib/text_formatter.rb b/app/lib/text_formatter.rb
new file mode 100644
index 000000000..48e2fc233
--- /dev/null
+++ b/app/lib/text_formatter.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+class TextFormatter
+ include ActionView::Helpers::TextHelper
+ include ERB::Util
+ include RoutingHelper
+
+ URL_PREFIX_REGEX = /\A(https?:\/\/(www\.)?|xmpp:)/.freeze
+
+ DEFAULT_REL = %w(nofollow noopener noreferrer).freeze
+
+ DEFAULT_OPTIONS = {
+ multiline: true,
+ }.freeze
+
+ attr_reader :text, :options
+
+ # @param [String] text
+ # @param [Hash] options
+ # @option options [Boolean] :multiline
+ # @option options [Boolean] :with_domains
+ # @option options [Boolean] :with_rel_me
+ # @option options [Array] :preloaded_accounts
+ def initialize(text, options = {})
+ @text = text
+ @options = DEFAULT_OPTIONS.merge(options)
+ end
+
+ def entities
+ @entities ||= Extractor.extract_entities_with_indices(text, extract_url_without_protocol: false)
+ end
+
+ def to_s
+ return ''.html_safe if text.blank?
+
+ html = rewrite do |entity|
+ if entity[:url]
+ link_to_url(entity)
+ elsif entity[:hashtag]
+ link_to_hashtag(entity)
+ elsif entity[:screen_name]
+ link_to_mention(entity)
+ end
+ end
+
+ html = simple_format(html, {}, sanitize: false).delete("\n") if multiline?
+
+ html.html_safe # rubocop:disable Rails/OutputSafety
+ end
+
+ private
+
+ def rewrite
+ entities.sort_by! do |entity|
+ entity[:indices].first
+ end
+
+ result = ''.dup
+
+ last_index = entities.reduce(0) do |index, entity|
+ indices = entity[:indices]
+ result << h(text[index...indices.first])
+ result << yield(entity)
+ indices.last
+ end
+
+ result << h(text[last_index..-1])
+
+ result
+ end
+
+ def link_to_url(entity)
+ url = Addressable::URI.parse(entity[:url]).to_s
+ rel = with_rel_me? ? (DEFAULT_REL + %w(me)) : DEFAULT_REL
+
+ prefix = url.match(URL_PREFIX_REGEX).to_s
+ display_url = url[prefix.length, 30]
+ suffix = url[prefix.length + 30..-1]
+ cutoff = url[prefix.length..-1].length > 30
+
+ <<~HTML.squish
+ #{h(prefix)}#{h(display_url)}#{h(suffix)}
+ HTML
+ rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
+ h(entity[:url])
+ end
+
+ def link_to_hashtag(entity)
+ hashtag = entity[:hashtag]
+ url = tag_url(hashtag)
+
+ <<~HTML.squish
+ ##{h(hashtag)}
+ HTML
+ end
+
+ def link_to_mention(entity)
+ username, domain = entity[:screen_name].split('@')
+ domain = nil if local_domain?(domain)
+ account = nil
+
+ if preloaded_accounts?
+ same_username_hits = 0
+
+ preloaded_accounts.each do |other_account|
+ same_username = other_account.username.casecmp(username).zero?
+ same_domain = other_account.domain.nil? ? domain.nil? : other_account.domain.casecmp(domain)&.zero?
+
+ if same_username && !same_domain
+ same_username_hits += 1
+ elsif same_username && same_domain
+ account = other_account
+ end
+ end
+ else
+ account = entity_cache.mention(username, domain)
+ end
+
+ return "@#{h(entity[:screen_name])}" if account.nil?
+
+ url = ActivityPub::TagManager.instance.url_for(account)
+ display_username = same_username_hits&.positive? || with_domains? ? account.pretty_acct : account.username
+
+ <<~HTML.squish
+ @#{h(display_username)}
+ HTML
+ end
+
+ def entity_cache
+ @entity_cache ||= EntityCache.instance
+ end
+
+ def tag_manager
+ @tag_manager ||= TagManager.instance
+ end
+
+ delegate :local_domain?, to: :tag_manager
+
+ def multiline?
+ options[:multiline]
+ end
+
+ def with_domains?
+ options[:with_domains]
+ end
+
+ def with_rel_me?
+ options[:with_rel_me]
+ end
+
+ def preloaded_accounts
+ options[:preloaded_accounts]
+ end
+
+ def preloaded_accounts?
+ preloaded_accounts.present?
+ end
+end
diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb
index cc585c3b7..a37682eca 100644
--- a/app/mailers/application_mailer.rb
+++ b/app/mailers/application_mailer.rb
@@ -5,6 +5,7 @@ class ApplicationMailer < ActionMailer::Base
helper :application
helper :instance
+ helper :formatting
protected
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 48707aa16..bd1648348 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -2,6 +2,7 @@
class ActivityPub::ActorSerializer < ActivityPub::Serializer
include RoutingHelper
+ include FormattingHelper
context :security
@@ -102,7 +103,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def summary
- object.suspended? ? '' : Formatter.instance.simplified_format(object)
+ object.suspended? ? '' : html_aware_format(object.note, object.local?)
end
def icon
@@ -185,6 +186,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
class Account::FieldSerializer < ActivityPub::Serializer
+ include FormattingHelper
+
attributes :type, :name, :value
def type
@@ -192,7 +195,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
end
def value
- Formatter.instance.format_field(object.account, object.value)
+ html_aware_format(object.value, object.account.value?, with_rel_me: true, with_domains: true, multiline: false)
end
end
diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb
index 7be2e2647..27e058199 100644
--- a/app/serializers/activitypub/note_serializer.rb
+++ b/app/serializers/activitypub/note_serializer.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ActivityPub::NoteSerializer < ActivityPub::Serializer
+ include FormattingHelper
+
context_extensions :atom_uri, :conversation, :sensitive, :voters_count
attributes :id, :type, :summary,
@@ -39,11 +41,11 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
end
def content
- Formatter.instance.format(object)
+ status_content_format(object)
end
def content_map
- { object.language => Formatter.instance.format(object) }
+ { object.language => content }
end
def replies
diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb
index a78ec4507..2f67e06b2 100644
--- a/app/serializers/rest/account_serializer.rb
+++ b/app/serializers/rest/account_serializer.rb
@@ -2,6 +2,7 @@
class REST::AccountSerializer < ActiveModel::Serializer
include RoutingHelper
+ include FormattingHelper
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
:note, :url, :avatar, :avatar_static, :header, :header_static,
@@ -14,10 +15,12 @@ class REST::AccountSerializer < ActiveModel::Serializer
attribute :suspended, if: :suspended?
class FieldSerializer < ActiveModel::Serializer
+ include FormattingHelper
+
attributes :name, :value, :verified_at
def value
- Formatter.instance.format_field(object.account, object.value)
+ html_aware_format(object.value, object.account.local?, with_rel_me: true, with_domains: true, multiline: false)
end
end
@@ -32,7 +35,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
end
def note
- object.suspended? ? '' : Formatter.instance.simplified_format(object)
+ object.suspended? ? '' : html_aware_format(object.note, object.local?)
end
def url
diff --git a/app/serializers/rest/announcement_serializer.rb b/app/serializers/rest/announcement_serializer.rb
index 9343b97d2..23b2fa514 100644
--- a/app/serializers/rest/announcement_serializer.rb
+++ b/app/serializers/rest/announcement_serializer.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::AnnouncementSerializer < ActiveModel::Serializer
+ include FormattingHelper
+
attributes :id, :content, :starts_at, :ends_at, :all_day,
:published_at, :updated_at
@@ -25,7 +27,7 @@ class REST::AnnouncementSerializer < ActiveModel::Serializer
end
def content
- Formatter.instance.linkify(object.text)
+ linkify(object.text)
end
def reactions
diff --git a/app/serializers/rest/status_edit_serializer.rb b/app/serializers/rest/status_edit_serializer.rb
index 05ccd5e94..f7a48797d 100644
--- a/app/serializers/rest/status_edit_serializer.rb
+++ b/app/serializers/rest/status_edit_serializer.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::StatusEditSerializer < ActiveModel::Serializer
+ include FormattingHelper
+
has_one :account, serializer: REST::AccountSerializer
attributes :content, :spoiler_text, :sensitive, :created_at
@@ -11,7 +13,7 @@ class REST::StatusEditSerializer < ActiveModel::Serializer
attribute :poll, if: -> { object.poll_options.present? }
def content
- Formatter.instance.format(object)
+ status_content_format(object)
end
def poll
diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb
index 7c3dd673e..32c4e405e 100644
--- a/app/serializers/rest/status_serializer.rb
+++ b/app/serializers/rest/status_serializer.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class REST::StatusSerializer < ActiveModel::Serializer
+ include FormattingHelper
+
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:uri, :url, :replies_count, :reblogs_count,
@@ -71,7 +73,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def content
- Formatter.instance.format(object)
+ status_content_format(object)
end
def url
diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb
index 239ab9b93..9c8b5ea20 100644
--- a/app/services/fetch_link_card_service.rb
+++ b/app/services/fetch_link_card_service.rb
@@ -134,7 +134,7 @@ class FetchLinkCardService < BaseService
when 'video'
@card.width = embed[:width].presence || 0
@card.height = embed[:height].presence || 0
- @card.html = Formatter.instance.sanitize(embed[:html], Sanitize::Config::MASTODON_OEMBED)
+ @card.html = Sanitize.fragment(embed[:html], Sanitize::Config::MASTODON_OEMBED)
@card.image_remote_url = (url + embed[:thumbnail_url]).to_s if embed[:thumbnail_url].present?
when 'rich'
# Most providers rely on } }
-
- it 'does not include the HTML in the URL' do
- is_expected.to include '"http://example.com/blahblahblahblah/a"'
- end
-
- it 'escapes the HTML' do
- is_expected.to include '<script>alert("Hello")</script>'
- end
- end
-
- context 'given text containing HTML code (script tag)' do
- let(:text) { '' }
-
- it 'escapes the HTML' do
- is_expected.to include '<script>alert("Hello")</script>
'
- end
- end
-
- context 'given text containing HTML (XSS attack)' do
- let(:text) { %q{} }
-
- it 'escapes the HTML' do
- is_expected.to include '<img src="javascript:alert('XSS');">
'
- end
- end
-
- context 'given an invalid URL' do
- let(:text) { 'http://www\.google\.com' }
-
- it 'outputs the raw URL' do
- is_expected.to eq 'http://www\.google\.com
'
- end
- end
-
- context 'given text containing a hashtag' do
- let(:text) { '#hashtag' }
-
- it 'creates a hashtag link' do
- is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#hashtag'
- end
- end
-
- context 'given text containing a hashtag with Unicode chars' do
- let(:text) { '#hashtagタグ' }
-
- it 'creates a hashtag link' do
- is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#hashtagタグ'
- end
- end
-
- context 'given a stand-alone xmpp: URI' do
- let(:text) { 'xmpp:user@instance.com' }
-
- it 'matches the full URI' do
- is_expected.to include 'href="xmpp:user@instance.com"'
- end
- end
-
- context 'given a an xmpp: URI with a query-string' do
- let(:text) { 'please join xmpp:muc@instance.com?join right now' }
-
- it 'matches the full URI' do
- is_expected.to include 'href="xmpp:muc@instance.com?join"'
- end
- end
-
- context 'given text containing a magnet: URI' do
- let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
-
- it 'matches the full URI' do
- is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
- end
- end
- end
-
- describe '#format_spoiler' do
- subject { Formatter.instance.format_spoiler(status) }
-
- context 'given a post containing plain text' do
- let(:status) { Fabricate(:status, text: 'text', spoiler_text: 'Secret!', uri: nil) }
-
- it 'Returns the spoiler text' do
- is_expected.to eq 'Secret!'
- end
- end
-
- context 'given a post with an emoji shortcode at the start' do
- let!(:emoji) { Fabricate(:custom_emoji) }
- let(:status) { Fabricate(:status, text: 'text', spoiler_text: ':coolcat: Secret!', uri: nil) }
- let(:text) { ':coolcat: Beep boop' }
-
- it 'converts the shortcode to an image tag' do
- is_expected.to match(/@alice Hello world'
- end
- end
-
- context 'given a post containing plain text' do
- let(:status) { Fabricate(:status, text: 'text', uri: nil) }
-
- it 'paragraphizes the text' do
- is_expected.to eq 'text
'
- end
- end
-
- context 'given a post containing line feeds' do
- let(:status) { Fabricate(:status, text: "line\nfeed", uri: nil) }
-
- it 'removes line feeds' do
- is_expected.not_to include "\n"
- end
- end
-
- context 'given a post containing linkable mentions' do
- let(:status) { Fabricate(:status, mentions: [ Fabricate(:mention, account: local_account) ], text: '@alice') }
-
- it 'creates a mention link' do
- is_expected.to include '@alice'
- end
- end
-
- context 'given a post containing unlinkable mentions' do
- let(:status) { Fabricate(:status, text: '@alice', uri: nil) }
-
- it 'does not create a mention link' do
- is_expected.to include '@alice'
- end
- end
-
- context do
- subject do
- status = Fabricate(:status, text: text, uri: nil)
- Formatter.instance.format(status)
- end
-
- include_examples 'encode and link URLs'
- end
-
- context 'given a post with custom_emojify option' do
- let!(:emoji) { Fabricate(:custom_emoji) }
- let(:status) { Fabricate(:status, account: local_account, text: text) }
-
- subject { Formatter.instance.format(status, custom_emojify: true) }
-
- context 'given a post with an emoji shortcode at the start' do
- let(:text) { ':coolcat: Beep boop' }
-
- it 'converts the shortcode to an image tag' do
- is_expected.to match(/:coolcat: Beep boop
' }
-
- it 'converts the shortcode to an image tag' do
- is_expected.to match(/
Beep :coolcat: boop
' }
-
- it 'converts the shortcode to an image tag' do
- is_expected.to match(/Beep :coolcat::coolcat:
' }
-
- it 'does not touch the shortcodes' do
- is_expected.to match(/:coolcat::coolcat:<\/p>/)
- end
- end
-
- context 'given a post with an emoji shortcode at the end' do
- let(:text) { '
Beep boop
:coolcat:
' }
-
- it 'converts the shortcode to an image tag' do
- is_expected.to match(/
alert("Hello")' }
-
- it 'strips the scripts' do
- is_expected.to_not include ''
- end
- end
-
- context 'given a post containing malicious classes' do
- let(:text) { 'Show more' }
-
- it 'strips the malicious classes' do
- is_expected.to_not include 'status__content__spoiler-link'
- end
- end
- end
-
- describe '#plaintext' do
- subject { Formatter.instance.plaintext(status) }
-
- context 'given a post with local status' do
- let(:status) { Fabricate(:status, text: 'a text by a nerd who uses an HTML tag in text
', uri: nil) }
-
- it 'returns the raw text' do
- is_expected.to eq 'a text by a nerd who uses an HTML tag in text
'
- end
- end
-
- context 'given a post with remote status' do
- let(:status) { Fabricate(:status, account: remote_account, text: '') }
-
- it 'returns tag-stripped text' do
- is_expected.to eq ''
- end
- end
- end
-
- describe '#simplified_format' do
- subject { Formatter.instance.simplified_format(account) }
-
- context 'given a post with local status' do
- let(:account) { Fabricate(:account, domain: nil, note: text) }
-
- context 'given a post containing linkable mentions for local accounts' do
- let(:text) { '@alice' }
-
- before { local_account }
-
- it 'creates a mention link' do
- is_expected.to eq '@alice
'
- end
- end
-
- context 'given a post containing linkable mentions for remote accounts' do
- let(:text) { '@bob@remote.test' }
-
- before { remote_account }
-
- it 'creates a mention link' do
- is_expected.to eq '@bob
'
- end
- end
-
- context 'given a post containing unlinkable mentions' do
- let(:text) { '@alice' }
-
- it 'does not create a mention link' do
- is_expected.to eq '@alice
'
- end
- end
-
- context 'given a post with custom_emojify option' do
- let!(:emoji) { Fabricate(:custom_emoji) }
-
- before { account.note = text }
- subject { Formatter.instance.simplified_format(account, custom_emojify: true) }
-
- context 'given a post with an emoji shortcode at the start' do
- let(:text) { ':coolcat: Beep boop' }
-
- it 'converts the shortcode to an image tag' do
- is_expected.to match(/alert("Hello")' }
- let(:account) { Fabricate(:account, domain: 'remote', note: text) }
-
- it 'reformats' do
- is_expected.to_not include ''
- end
-
- context 'with custom_emojify option' do
- let!(:emoji) { Fabricate(:custom_emoji, domain: remote_account.domain) }
-
- before { remote_account.note = text }
-
- subject { Formatter.instance.simplified_format(remote_account, custom_emojify: true) }
-
- context 'given a post with an emoji shortcode at the start' do
- let(:text) { '
:coolcat: Beep boop
' }
-
- it 'converts shortcode to image tag' do
- is_expected.to match(/
Beep :coolcat: boop
' }
-
- it 'converts shortcode to image tag' do
- is_expected.to match(/Beep :coolcat::coolcat:' }
-
- it 'does not touch the shortcodes' do
- is_expected.to match(/:coolcat::coolcat:<\/p>/)
- end
- end
-
- context 'given a post with an emoji shortcode at the end' do
- let(:text) { '
Beep boop
:coolcat:
' }
-
- it 'converts shortcode to image tag' do
- is_expected.to match(/
alert("Hello")' }
-
- subject { Formatter.instance.sanitize(html, Sanitize::Config::MASTODON_STRICT) }
-
- it 'sanitizes' do
- is_expected.to eq ''
- end
- end
-end
diff --git a/spec/lib/html_aware_formatter.rb b/spec/lib/html_aware_formatter.rb
new file mode 100644
index 000000000..18d23abf5
--- /dev/null
+++ b/spec/lib/html_aware_formatter.rb
@@ -0,0 +1,44 @@
+require 'rails_helper'
+
+RSpec.describe HtmlAwareFormatter do
+ describe '#to_s' do
+ subject { described_class.new(text, local).to_s }
+
+ context 'when local' do
+ let(:local) { true }
+ let(:text) { 'Foo bar' }
+
+ it 'returns formatted text' do
+ is_expected.to eq 'Foo bar
'
+ end
+ end
+
+ context 'when remote' do
+ let(:local) { false }
+
+ context 'given plain text' do
+ let(:text) { 'Beep boop' }
+
+ it 'keeps the plain text' do
+ is_expected.to include 'Beep boop'
+ end
+ end
+
+ context 'given text containing script tags' do
+ let(:text) { '' }
+
+ it 'strips the scripts' do
+ is_expected.to_not include ''
+ end
+ end
+
+ context 'given text containing malicious classes' do
+ let(:text) { 'Show more' }
+
+ it 'strips the malicious classes' do
+ is_expected.to_not include 'status__content__spoiler-link'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/plain_text_formatter_spec.rb b/spec/lib/plain_text_formatter_spec.rb
new file mode 100644
index 000000000..c3d0ee630
--- /dev/null
+++ b/spec/lib/plain_text_formatter_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+RSpec.describe PlainTextFormatter do
+ describe '#to_s' do
+ subject { described_class.new(status.text, status.local?).to_s }
+
+ context 'given a post with local status' do
+ let(:status) { Fabricate(:status, text: 'a text by a nerd who uses an HTML tag in text
', uri: nil) }
+
+ it 'returns the raw text' do
+ is_expected.to eq 'a text by a nerd who uses an HTML tag in text
'
+ end
+ end
+
+ context 'given a post with remote status' do
+ let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
+ let(:status) { Fabricate(:status, account: remote_account, text: 'Hello
') }
+
+ it 'returns tag-stripped text' do
+ is_expected.to eq 'Hello'
+ end
+ end
+ end
+end
diff --git a/spec/lib/text_formatter_spec.rb b/spec/lib/text_formatter_spec.rb
new file mode 100644
index 000000000..52a9d2498
--- /dev/null
+++ b/spec/lib/text_formatter_spec.rb
@@ -0,0 +1,313 @@
+require 'rails_helper'
+
+RSpec.describe TextFormatter do
+ describe '#to_s' do
+ let(:preloaded_accounts) { nil }
+
+ subject { described_class.new(text, preloaded_accounts: preloaded_accounts).to_s }
+
+ context 'given text containing plain text' do
+ let(:text) { 'text' }
+
+ it 'paragraphizes the text' do
+ is_expected.to eq 'text
'
+ end
+ end
+
+ context 'given text containing line feeds' do
+ let(:text) { "line\nfeed" }
+
+ it 'removes line feeds' do
+ is_expected.not_to include "\n"
+ end
+ end
+
+ context 'given text containing linkable mentions' do
+ let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] }
+ let(:text) { '@alice' }
+
+ it 'creates a mention link' do
+ is_expected.to include '@alice'
+ end
+ end
+
+ context 'given text containing unlinkable mentions' do
+ let(:preloaded_accounts) { [] }
+ let(:text) { '@alice' }
+
+ it 'does not create a mention link' do
+ is_expected.to include '@alice'
+ end
+ end
+
+ context 'given a stand-alone medium URL' do
+ let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"'
+ end
+ end
+
+ context 'given a stand-alone google URL' do
+ let(:text) { 'http://google.com' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="http://google.com"'
+ end
+ end
+
+ context 'given a stand-alone URL with a newer TLD' do
+ let(:text) { 'http://example.gay' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="http://example.gay"'
+ end
+ end
+
+ context 'given a stand-alone IDN URL' do
+ let(:text) { 'https://nic.みんな/' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://nic.みんな/"'
+ end
+
+ it 'has display URL' do
+ is_expected.to include 'nic.みんな/'
+ end
+ end
+
+ context 'given a URL with a trailing period' do
+ let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' }
+
+ it 'matches the full URL but not the period' do
+ is_expected.to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"'
+ end
+ end
+
+ context 'given a URL enclosed with parentheses' do
+ let(:text) { '(http://google.com/)' }
+
+ it 'matches the full URL but not the parentheses' do
+ is_expected.to include 'href="http://google.com/"'
+ end
+ end
+
+ context 'given a URL with a trailing exclamation point' do
+ let(:text) { 'http://www.google.com!' }
+
+ it 'matches the full URL but not the exclamation point' do
+ is_expected.to include 'href="http://www.google.com"'
+ end
+ end
+
+ context 'given a URL with a trailing single quote' do
+ let(:text) { "http://www.google.com'" }
+
+ it 'matches the full URL but not the single quote' do
+ is_expected.to include 'href="http://www.google.com"'
+ end
+ end
+
+ context 'given a URL with a trailing angle bracket' do
+ let(:text) { 'http://www.google.com>' }
+
+ it 'matches the full URL but not the angle bracket' do
+ is_expected.to include 'href="http://www.google.com"'
+ end
+ end
+
+ context 'given a URL with a query string' do
+ context 'with escaped unicode character' do
+ let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"'
+ end
+ end
+
+ context 'with unicode character' do
+ let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&q=autolink"'
+ end
+ end
+
+ context 'with unicode character at the end' do
+ let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"'
+ end
+ end
+
+ context 'with escaped and not escaped unicode characters' do
+ let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' }
+
+ it 'preserves escaped unicode characters' do
+ is_expected.to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink"'
+ end
+ end
+ end
+
+ context 'given a URL with parentheses in it' do
+ let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"'
+ end
+ end
+
+ context 'given a URL in quotation marks' do
+ let(:text) { '"https://example.com/"' }
+
+ it 'does not match the quotation marks' do
+ is_expected.to include 'href="https://example.com/"'
+ end
+ end
+
+ context 'given a URL in angle brackets' do
+ let(:text) { '' }
+
+ it 'does not match the angle brackets' do
+ is_expected.to include 'href="https://example.com/"'
+ end
+ end
+
+ context 'given a URL with Japanese path string' do
+ let(:text) { 'https://ja.wikipedia.org/wiki/日本' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://ja.wikipedia.org/wiki/日本"'
+ end
+ end
+
+ context 'given a URL with Korean path string' do
+ let(:text) { 'https://ko.wikipedia.org/wiki/대한민국' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://ko.wikipedia.org/wiki/대한민국"'
+ end
+ end
+
+ context 'given a URL with a full-width space' do
+ let(:text) { 'https://example.com/ abc123' }
+
+ it 'does not match the full-width space' do
+ is_expected.to include 'href="https://example.com/"'
+ end
+ end
+
+ context 'given a URL in Japanese quotation marks' do
+ let(:text) { '「[https://example.org/」' }
+
+ it 'does not match the quotation marks' do
+ is_expected.to include 'href="https://example.org/"'
+ end
+ end
+
+ context 'given a URL with Simplified Chinese path string' do
+ let(:text) { 'https://baike.baidu.com/item/中华人民共和国' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://baike.baidu.com/item/中华人民共和国"'
+ end
+ end
+
+ context 'given a URL with Traditional Chinese path string' do
+ let(:text) { 'https://zh.wikipedia.org/wiki/臺灣' }
+
+ it 'matches the full URL' do
+ is_expected.to include 'href="https://zh.wikipedia.org/wiki/臺灣"'
+ end
+ end
+
+ context 'given a URL containing unsafe code (XSS attack, visible part)' do
+ let(:text) { %q{http://example.com/bb} }
+
+ it 'does not include the HTML in the URL' do
+ is_expected.to include '"http://example.com/b"'
+ end
+
+ it 'escapes the HTML' do
+ is_expected.to include '<del>b</del>'
+ end
+ end
+
+ context 'given a URL containing unsafe code (XSS attack, invisible part)' do
+ let(:text) { %q{http://example.com/blahblahblahblah/a} }
+
+ it 'does not include the HTML in the URL' do
+ is_expected.to include '"http://example.com/blahblahblahblah/a"'
+ end
+
+ it 'escapes the HTML' do
+ is_expected.to include '<script>alert("Hello")</script>'
+ end
+ end
+
+ context 'given text containing HTML code (script tag)' do
+ let(:text) { '' }
+
+ it 'escapes the HTML' do
+ is_expected.to include '<script>alert("Hello")</script>
'
+ end
+ end
+
+ context 'given text containing HTML (XSS attack)' do
+ let(:text) { %q{} }
+
+ it 'escapes the HTML' do
+ is_expected.to include '<img src="javascript:alert('XSS');">
'
+ end
+ end
+
+ context 'given an invalid URL' do
+ let(:text) { 'http://www\.google\.com' }
+
+ it 'outputs the raw URL' do
+ is_expected.to eq 'http://www\.google\.com
'
+ end
+ end
+
+ context 'given text containing a hashtag' do
+ let(:text) { '#hashtag' }
+
+ it 'creates a hashtag link' do
+ is_expected.to include '/tags/hashtag" class="mention hashtag" rel="tag">#hashtag'
+ end
+ end
+
+ context 'given text containing a hashtag with Unicode chars' do
+ let(:text) { '#hashtagタグ' }
+
+ it 'creates a hashtag link' do
+ is_expected.to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#hashtagタグ'
+ end
+ end
+
+ context 'given text with a stand-alone xmpp: URI' do
+ let(:text) { 'xmpp:user@instance.com' }
+
+ it 'matches the full URI' do
+ is_expected.to include 'href="xmpp:user@instance.com"'
+ end
+ end
+
+ context 'given text with an xmpp: URI with a query-string' do
+ let(:text) { 'please join xmpp:muc@instance.com?join right now' }
+
+ it 'matches the full URI' do
+ is_expected.to include 'href="xmpp:muc@instance.com?join"'
+ end
+ end
+
+ context 'given text containing a magnet: URI' do
+ let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' }
+
+ it 'matches the full URI' do
+ is_expected.to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"'
+ end
+ end
+ end
+end
--
cgit
From e6a159a64869927cca5535943cdf3a280aeb5394 Mon Sep 17 00:00:00 2001
From: Claire
Date: Mon, 28 Mar 2022 01:16:02 +0200
Subject: Fix extra “zero” key in some plural translation strings (#17883)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
config/locales/en.yml | 4 ----
1 file changed, 4 deletions(-)
(limited to 'config')
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 5fa3c012e..829cd61d0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -168,7 +168,6 @@ en:
previous_strikes_description_html:
one: This account has one strike.
other: This account has %{count} strikes.
- zero: This account is in good standing.
promote: Promote
protocol: Protocol
public: Public
@@ -530,7 +529,6 @@ en:
known_accounts:
one: "%{count} known account"
other: "%{count} known accounts"
- zero: No known account
moderation:
all: All
limited: Limited
@@ -802,7 +800,6 @@ en:
shared_by_over_week:
one: Shared by one person over the last week
other: Shared by %{count} people over the last week
- zero: Shared by noone over the last week
title: Trending links
usage_comparison: Shared %{today} times today, compared to %{yesterday} yesterday
pending_review: Pending review
@@ -845,7 +842,6 @@ en:
used_by_over_week:
one: Used by one person over the last week
other: Used by %{count} people over the last week
- zero: Used by noone over the last week
title: Trends
warning_presets:
add_new: Add new
--
cgit
From 56edc6552f71a1f58fd8ca5ea2f0603015be0c2c Mon Sep 17 00:00:00 2001
From: Eugen Rochko
Date: Mon, 28 Mar 2022 09:39:31 +0200
Subject: Add `SMTP_RETURN_PATH` environment variable to set bounce domain
(#17886)
---
config/environments/production.rb | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
(limited to 'config')
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 7fe381040..b003cce9e 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -91,11 +91,13 @@ Rails.application.configure do
# E-mails
outgoing_email_address = ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost')
- outgoing_mail_domain = Mail::Address.new(outgoing_email_address).domain
+ outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain
+
config.action_mailer.default_options = {
from: outgoing_email_address,
reply_to: ENV['SMTP_REPLY_TO'],
- 'Message-ID': -> { "<#{Mail.random_tag}@#{outgoing_mail_domain}>" },
+ return_path: ENV['SMTP_RETURN_PATH'],
+ message_id: -> { "<#{Mail.random_tag}@#{outgoing_email_domain}>" },
}
config.action_mailer.smtp_settings = {
--
cgit