+
{icon}
{type}
diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx
index 2bd1e101..fb253bbb 100644
--- a/app/assets/javascripts/components/features/ui/components/column_link.jsx
+++ b/app/assets/javascripts/components/features/ui/components/column_link.jsx
@@ -34,7 +34,8 @@ ColumnLink.propTypes = {
icon: React.PropTypes.string.isRequired,
text: React.PropTypes.string.isRequired,
to: React.PropTypes.string,
- href: React.PropTypes.string
+ href: React.PropTypes.string,
+ method: React.PropTypes.string
};
export default ColumnLink;
diff --git a/app/assets/javascripts/components/features/ui/components/media_modal.jsx b/app/assets/javascripts/components/features/ui/components/media_modal.jsx
index 130f48b4..786b0815 100644
--- a/app/assets/javascripts/components/features/ui/components/media_modal.jsx
+++ b/app/assets/javascripts/components/features/ui/components/media_modal.jsx
@@ -104,8 +104,8 @@ const MediaModal = React.createClass({
leftNav = rightNav = content = '';
if (media.size > 1) {
- leftNav =
;
- rightNav =
;
+ leftNav =
;
+ rightNav =
;
}
if (attachment.get('type') === 'image') {
diff --git a/app/assets/javascripts/components/features/ui/components/modal_root.jsx b/app/assets/javascripts/components/features/ui/components/modal_root.jsx
index 74eb5003..a1ed8fd8 100644
--- a/app/assets/javascripts/components/features/ui/components/modal_root.jsx
+++ b/app/assets/javascripts/components/features/ui/components/modal_root.jsx
@@ -66,7 +66,7 @@ const ModalRoot = React.createClass({
return (
-
+
diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx
index 5c7cc6ef..d3090ae9 100644
--- a/app/assets/javascripts/components/features/ui/index.jsx
+++ b/app/assets/javascripts/components/features/ui/index.jsx
@@ -141,7 +141,7 @@ const UI = React.createClass({
{mountedColumns}
-
+
diff --git a/app/assets/javascripts/components/link_header.jsx b/app/assets/javascripts/components/link_header.jsx
index 9a9ff7e7..b872dc24 100644
--- a/app/assets/javascripts/components/link_header.jsx
+++ b/app/assets/javascripts/components/link_header.jsx
@@ -14,7 +14,7 @@ Link.parseAttrs = (link, parts) => {
link = Link.parseParams(link, uriAttrs[1])
}
- while(match = Link.attrPattern.exec(attrs)) {
+ while(match = Link.attrPattern.exec(attrs)) { // eslint-disable-line no-cond-assign
attr = match[1].toLowerCase()
value = match[4] || match[3] || match[2]
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
index 47e23d98..5dccb807 100644
--- a/app/assets/javascripts/components/locales/en.jsx
+++ b/app/assets/javascripts/components/locales/en.jsx
@@ -31,6 +31,7 @@ const en = {
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
+ "column.mutes": "Muted users",
"column.notifications": "Notifications",
"column.public": "Federated timeline",
"compose_form.placeholder": "What is on your mind?",
@@ -68,6 +69,7 @@ const en = {
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.info": "Extended information",
"navigation_bar.logout": "Logout",
+ "navigation_bar.mutes": "Muted users",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"notification.favourite": "{name} favourited your status",
diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx
index 7e9c0dc2..b118def8 100644
--- a/app/assets/javascripts/components/locales/es.jsx
+++ b/app/assets/javascripts/components/locales/es.jsx
@@ -73,7 +73,7 @@ const es = {
"notifications.column_settings.mention": "Menciones:",
"notifications.column_settings.reblog": "Retoots:",
"emoji_button.label": "Insertar emoji",
- "privacy.public.short": "Público",
+ "privacy.public.short": "Público",
"privacy.public.long": "Mostrar en la historia federada",
"privacy.unlisted.short": "Sin federar",
"privacy.unlisted.long": "No mostrar en la historia federada",
diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx
index 565e2e6c..c64d7ecc 100644
--- a/app/assets/javascripts/components/locales/ja.jsx
+++ b/app/assets/javascripts/components/locales/ja.jsx
@@ -37,6 +37,7 @@ const ja = {
"getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。",
"getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。",
"getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
+ "getting_started.apps": "さまざまなアプリで利用できます。",
"column.home": "ホーム",
"column.community": "ローカルタイムライン",
"column.public": "連合タイムライン",
@@ -64,7 +65,7 @@ const ja = {
"privacy.private.long": "フォロワーだけに公開",
"privacy.direct.short": "ダイレクト",
"privacy.direct.long": "含んだユーザーだけに公開",
- "privacy.change": "投稿のプライバシーを変更2",
+ "privacy.change": "投稿のプライバシーを変更",
"report.heading": "新規通報",
"report.placeholder": "コメント",
"report.target": "問題のユーザー",
@@ -82,6 +83,7 @@ const ja = {
"search.account": "アカウント",
"search.hashtag": "ハッシュタグ",
"search.status_by": "{uuuname}からの投稿",
+ "search_results.total": "{count} 件",
"upload_area.title": "ファイルをこちらにドラッグしてください",
"upload_button.label": "メディアを追加",
"upload_form.undo": "やり直す",
@@ -111,7 +113,7 @@ const ja = {
"home.column_settings.show_replies": "返信表示",
"home.column_settings.filter_regex": "正規表現でフィルター",
"home.settings": "カラム設定",
- "notification.settings": "カラム設定",
+ "notifications.settings": "カラム設定",
"missing_indicator.label": "見つかりません",
"boost_modal.combo": "次は{combo}を押せば、これをスキップできます。"
};
diff --git a/app/assets/javascripts/components/locales/nl.jsx b/app/assets/javascripts/components/locales/nl.jsx
index 821b2866..8fc3a422 100644
--- a/app/assets/javascripts/components/locales/nl.jsx
+++ b/app/assets/javascripts/components/locales/nl.jsx
@@ -1,22 +1,22 @@
const nl = {
"column_back_button.label": "terug",
"lightbox.close": "Sluiten",
- "loading_indicator.label": "Laden...",
- "status.mention": "Vermeld @{name}",
- "status.delete": "Verwijder",
- "status.reply": "Reageer",
+ "loading_indicator.label": "Laden…",
+ "status.mention": "@{name} vermelden",
+ "status.delete": "Verwijderen",
+ "status.reply": "Reageren",
"status.reblog": "Boost",
"status.favourite": "Favoriet",
"status.reblogged_by": "{name} boostte",
"status.sensitive_warning": "Gevoelige inhoud",
"status.sensitive_toggle": "Klik om te zien",
- "video_player.toggle_sound": "Geluid omschakelen",
- "account.mention": "Vermeld @{name}",
- "account.edit_profile": "Bewerk profiel",
- "account.unblock": "Deblokkeer @{name}",
- "account.unfollow": "Ontvolg",
- "account.block": "Blokkeer @{name}",
- "account.follow": "Volg",
+ "video_player.toggle_sound": "Geluid in-/uitschakelen",
+ "account.mention": "@{name} vermelden",
+ "account.edit_profile": "Profiel bewerken",
+ "account.unblock": "@{name} deblokkeren",
+ "account.unfollow": "Ontvolgen",
+ "account.block": "@{name} blokkeren",
+ "account.follow": "Volgen",
"account.posts": "Berichten",
"account.follows": "Volgt",
"account.followers": "Volgers",
@@ -25,7 +25,7 @@ const nl = {
"getting_started.heading": "Beginnen",
"getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent, door het e-mailachtige adres in het zoekscherm in te voeren.",
"getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in de statussen wilt vermelden.",
- "getting_started.open_source_notice": "Mastodon is open source software. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
+ "getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
"column.home": "Thuis",
"column.community": "Lokale tijdlijn",
"column.public": "Federatietijdlijn",
@@ -37,30 +37,30 @@ const nl = {
"tabs_bar.notifications": "Meldingen",
"compose_form.placeholder": "Waar ben je mee bezig?",
"compose_form.publish": "Toot",
- "compose_form.sensitive": "Markeer media als gevoelig",
- "compose_form.spoiler": "Verberg tekst achter waarschuwing",
- "compose_form.private": "Mark als privé",
- "compose_form.privacy_disclaimer": "Je besloten status wordt afgeleverd aan vermelde gebruikers op {domains}. Vertrouw je {domainsCount, plural, one {that server} andere {those servers}}? Privé plaatsen werkt alleen op Mastodon servers. Als {domains} {domainsCount, plural, een {is not a Mastodon instance} andere {are not Mastodon instances}}, dan wordt er geen indicatie gegeven dat he bericht besloten is, waardoor het kan worden geboost of op andere manier zichtbaar worden voor niet bedoelde lezers.",
- "compose_form.unlisted": "Niet tonen op openbare tijdlijnen",
- "navigation_bar.edit_profile": "Bewerk profiel",
+ "compose_form.sensitive": "Media als gevoelig markeren",
+ "compose_form.spoiler": "Tekst achter waarschuwing verbergen",
+ "compose_form.private": "Als privé markeren",
+ "compose_form.privacy_disclaimer": "Je besloten status wordt afgeleverd aan vermelde gebruikers op {domains}. Vertrouw je {domainsCount, plural, one {that server} andere {those servers}}? Privé plaatsen werkt alleen op Mastodon servers. Als {domains} {domainsCount, plural, een {is not a Mastodon instance} andere {are not Mastodon instances}}, dan wordt er geen indicatie gegeven dat he bericht besloten is, waardoor het kan worden geboost of op andere manier zichtbaar worden voor niet bedoelde lezers.",
+ "compose_form.unlisted": "Niet op openbare tijdlijnen tonen",
+ "navigation_bar.edit_profile": "Profiel bewerken",
"navigation_bar.preferences": "Voorkeuren",
"navigation_bar.community_timeline": "Lokale tijdlijn",
"navigation_bar.public_timeline": "Federatietijdlijn",
- "navigation_bar.logout": "Uitloggen",
+ "navigation_bar.logout": "Afmelden",
"reply_indicator.cancel": "Annuleren",
"search.placeholder": "Zoeken",
"search.account": "Account",
"search.hashtag": "Hashtag",
- "upload_button.label": "Toevoegen media",
+ "upload_button.label": "Media toevoegen",
"upload_form.undo": "Ongedaan maken",
"notification.follow": "{name} volgde jou",
"notification.favourite": "{name} markeerde je status als favoriet",
"notification.reblog": "{name} boostte je status",
"notification.mention": "{name} vermeldde jou",
"notifications.column_settings.alert": "Desktopmeldingen",
- "notifications.column_settings.show": "Tonen in kolom",
+ "notifications.column_settings.show": "In kolom tonen",
"notifications.column_settings.follow": "Nieuwe volgers:",
- "notifications.column_settings.favourite": "Favoriten:",
+ "notifications.column_settings.favourite": "Favorieten:",
"notifications.column_settings.mention": "Vermeldingen:",
"notifications.column_settings.reblog": "Boosts:",
};
diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx
index 64b53fb1..43715fb5 100644
--- a/app/assets/javascripts/components/locales/no.jsx
+++ b/app/assets/javascripts/components/locales/no.jsx
@@ -1,77 +1,130 @@
const no = {
- "column_back_button.label": "Tilbake",
- "lightbox.close": "Lukk",
- "loading_indicator.label": "Laster...",
- "status.mention": "Nevn @{name}",
- "status.delete": "Slett",
- "status.reply": "Svar",
- "status.reblog": "Reblogg",
- "status.favourite": "Lik",
- "status.reblogged_by": "{name} reblogget",
- "status.sensitive_warning": "Sensitivt innhold",
- "status.sensitive_toggle": "Klikk for å vise",
- "status.show_more": "Vis mer",
- "status.show_less": "Vis mindre",
- "status.open": "Utvid denne statusen",
- "status.report": "Rapporter @{name}",
- "video_player.toggle_sound": "Veksle lyd",
- "account.mention": "Nevn @{name}",
+ "account.block": "Blokkér @{name}",
+ "account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.",
"account.edit_profile": "Rediger profil",
+ "account.follow": "Følg",
+ "account.followers": "Følgere",
+ "account.follows_you": "Følger deg",
+ "account.follows": "Følginger",
+ "account.mention": "Nevn @{name}",
+ "account.mute": "Demp @{name}",
+ "account.posts": "Poster",
+ "account.report": "Rapportér @{name}",
+ "account.requested": "Venter på godkjennelse",
"account.unblock": "Avblokker @{name}",
"account.unfollow": "Avfølg",
- "account.block": "Blokker @{name}",
- "account.follow": "Følg",
- "account.posts": "Poster",
- "account.follows": "Følginger",
- "account.followers": "Følgere",
- "account.follows_you": "Folger deg",
- "account.requested": "Venter på godkjennelse",
- "getting_started.heading": "Kom i gang",
- "getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.",
- "getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.",
- "getting_started.open_source_notice": "Mastodon er programvare med fri kildekode. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
- "column.home": "Hjem",
- "column.community": "Lokal tidslinje",
- "column.public": "Forent tidslinje",
- "column.notifications": "Varslinger",
+ "account.unmute": "Avdemp @{name}",
+ "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
+ "column_back_button.label": "Tilbake",
"column.blocks": "Blokkerte brukere",
+ "column.community": "Lokal tidslinje",
"column.favourites": "Likt",
- "tabs_bar.compose": "Komponer",
- "tabs_bar.home": "Hjem",
- "tabs_bar.mentions": "Nevninger",
- "tabs_bar.public": "Forent tidslinje",
- "tabs_bar.notifications": "Varslinger",
+ "column.follow_requests": "Følgeforespørsler",
+ "column.home": "Hjem",
+ "column.notifications": "Varslinger",
+ "column.public": "Felles tidslinje",
"compose_form.placeholder": "Hva har du på hjertet?",
+ "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli fremhevd eller på annen måte bli synlig for uventede mottakere.",
"compose_form.publish": "Tut",
"compose_form.sensitive": "Merk media som følsomt",
+ "compose_form.spoiler_placeholder": "Innholdsadvarsel",
"compose_form.spoiler": "Skjul tekst bak advarsel",
- "compose_form.private": "Merk som privat",
- "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli reblogget eller på annen måte bli synlig for uventede mottakere.",
- "compose_form.unlisted": "Ikke vis på offentlige tidslinjer",
- "navigation_bar.edit_profile": "Rediger profil",
- "navigation_bar.preferences": "Preferanser",
- "navigation_bar.community_timeline": "Lokal tidslinje",
- "navigation_bar.public_timeline": "Forent tidslinje",
- "navigation_bar.logout": "Logg ut",
+ "emoji_button.label": "Sett inn emoji",
+ "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
+ "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
+ "empty_column.home.public_timeline": "en offentlig tidslinje",
+ "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
+ "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
+ "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
+ "follow_request.authorize": "Autorisér",
+ "follow_request.reject": "Avvis",
+ "getting_started.apps": "Diverse apper er tilgjengelige",
+ "getting_started.heading": "Kom i gang",
+ "getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
+ "home.column_settings.advanced": "Advansert",
+ "home.column_settings.basic": "Enkel",
+ "home.column_settings.filter_regex": "Filtrér med regulære uttrykk",
+ "home.column_settings.show_reblogs": "Vis fremhevinger",
+ "home.column_settings.show_replies": "Vis svar",
+ "home.settings": "Kolonneinnstillinger",
+ "lightbox.close": "Lukk",
+ "loading_indicator.label": "Laster...",
+ "media_gallery.toggle_visible": "Veksle synlighet",
+ "missing_indicator.label": "Ikke funnet",
"navigation_bar.blocks": "Blokkerte brukere",
- "navigation_bar.info": "Utvidet informasjon",
+ "navigation_bar.community_timeline": "Lokal tidslinje",
+ "navigation_bar.edit_profile": "Rediger profil",
"navigation_bar.favourites": "Likt",
+ "navigation_bar.follow_requests": "Følgeforespørsler",
+ "navigation_bar.info": "Utvidet informasjon",
+ "navigation_bar.logout": "Logg ut",
+ "navigation_bar.preferences": "Preferanser",
+ "navigation_bar.public_timeline": "Felles tidslinje",
+ "notification.favourite": "{name} likte din status",
+ "notification.follow": "{name} fulgte deg",
+ "notification.reblog": "{name} fremhevde din status",
+ "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler?",
+ "notifications.clear": "Fjern varsler",
+ "notifications.column_settings.alert": "Skrivebordsvarslinger",
+ "notifications.column_settings.favourite": "Likt:",
+ "notifications.column_settings.follow": "Nye følgere:",
+ "notifications.column_settings.mention": "Nevninger:",
+ "notifications.column_settings.reblog": "Fremhevinger:",
+ "notifications.column_settings.show": "Vis i kolonne",
+ "notifications.column_settings.sound": "Spill lyd",
+ "notifications.settings": "Kolonneinstillinger",
+ "privacy.change": "Justér synlighet",
+ "privacy.direct.long": "Post kun til nevnte brukere",
+ "privacy.direct.short": "Direkte",
+ "privacy.private.long": "Post kun til følgere",
+ "privacy.private.short": "Privat",
+ "privacy.public.long": "Post kun til offentlige tidslinjer",
+ "privacy.public.short": "Offentlig",
+ "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
+ "privacy.unlisted.short": "Uoppført",
"reply_indicator.cancel": "Avbryt",
+ "report.heading": "Ny rapport",
+ "report.placeholder": "Tilleggskommentarer",
+ "report.submit": "Send inn",
+ "report.target": "Rapporterer",
+ "search_results.total": "{count} {count, plural, one {resultat} other {resultater}}",
"search.placeholder": "Søk",
- "search.account": "Konto",
- "search.hashtag": "Hashtag",
+ "search.status_by": "Status fra {name}",
+ "status.delete": "Slett",
+ "status.favourite": "Lik",
+ "status.load_more": "Last mer",
+ "status.media_hidden": "Media skjult",
+ "status.mention": "Nevn @{name}",
+ "status.open": "Utvid denne statusen",
+ "status.reblog": "Fremhev",
+ "status.reblogged_by": "Fremhevd av {name}",
+ "status.reply": "Svar",
+ "status.report": "Rapporter @{name}",
+ "status.sensitive_toggle": "Klikk for å vise",
+ "status.sensitive_warning": "Følsomt innhold",
+ "status.show_less": "Vis mindre",
+ "status.show_more": "Vis mer",
+ "tabs_bar.compose": "Komponer",
+ "tabs_bar.federated_timeline": "Felles",
+ "tabs_bar.home": "Hjem",
+ "tabs_bar.local_timeline": "Lokal",
+ "tabs_bar.notifications": "Varslinger",
+ "upload_area.title": "Dra og slipp for å laste opp",
"upload_button.label": "Legg til media",
"upload_form.undo": "Angre",
- "notification.follow": "{name} fulgte deg",
- "notification.favourite": "{name} likte din status",
- "notification.reblog": "{name} reblogget din status",
- "notification.mention": "{name} nevnte deg",
- "notifications.column_settings.alert": "Skrivebordsvarslinger",
- "notifications.column_settings.show": "Vis i kolonne",
- "notifications.column_settings.follow": "Nye følgere:",
- "notifications.column_settings.favourite": "Likt:",
- "notifications.column_settings.mention": "Nevninger:",
- "notifications.column_settings.reblog": "Reblogginger:",
+ "upload_progress.label": "Laster opp...",
+ "video_player.toggle_sound": "Veksle lyd",
+ "video_player.toggle_visible": "Veksle synlighet",
+ "video_player.expand": "Utvid video",
+ "getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.",
+ "getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.",
+ "tabs_bar.mentions": "Nevninger",
+ "tabs_bar.public": "Felles tidslinje",
+ "compose_form.private": "Merk som privat",
+ "compose_form.unlisted": "Ikke vis på offentlige tidslinjer",
+ "search.account": "Konto",
+ "search.hashtag": "Hashtag",
+ "notification.mention": "{name} nevnte deg"
};
export default no;
diff --git a/app/assets/javascripts/components/locales/ru.jsx b/app/assets/javascripts/components/locales/ru.jsx
index 30a92df8..5d84527b 100644
--- a/app/assets/javascripts/components/locales/ru.jsx
+++ b/app/assets/javascripts/components/locales/ru.jsx
@@ -2,7 +2,9 @@ const ru = {
"column_back_button.label": "Назад",
"lightbox.close": "Закрыть",
"loading_indicator.label": "Загрузка...",
+ "missing_indicator.label": "Не найдено",
"status.mention": "Упомянуть @{name}",
+ "status.media_hidden": "Медиаконтент скрыт",
"status.delete": "Удалить",
"status.reply": "Ответить",
"status.reblog": "Продвинуть",
@@ -14,20 +16,25 @@ const ru = {
"status.show_less": "Свернуть",
"status.open": "Развернуть статус",
"status.report": "Пожаловаться",
- "status.load_more": "Показать еще",
+ "status.load_more": "Показать еще",
"video_player.toggle_sound": "Вкл./выкл. звук",
+ "video_player.toggle_visible": "Показать/скрыть",
+ "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.",
"account.mention": "Упомянуть",
"account.edit_profile": "Изменить профиль",
"account.unblock": "Разблокировать",
"account.unfollow": "Отписаться",
"account.block": "Блокировать",
"account.mute": "Заглушить",
+ "account.report": "Пожаловаться",
+ "account.unmute": "Снять глушение",
"account.follow": "Подписаться",
"account.posts": "Посты",
"account.follows": "Подписки",
"account.followers": "Подписаны",
"account.follows_you": "Подписан(а) на Вас",
"account.requested": "Ожидает подтверждения",
+ "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
"getting_started.heading": "Добро пожаловать",
"getting_started.about_addressing": "Вы можете подписаться на человека, зная имя пользователя и домен, на котором он находится, введя e-mail-подобный адрес в форму поиска.",
"getting_started.about_shortcuts": "Если пользователь находится на одном с Вами домене, можно использовать только имя. То же правило применимо к упоминанию пользователей в статусах.",
@@ -37,11 +44,16 @@ const ru = {
"column.community": "Локальная лента",
"column.public": "Глобальная лента",
"column.notifications": "Уведомления",
+ "column.favourites": "Понравившееся",
+ "column.blocks": "Список блокировки",
+ "column.follow_requests": "Запросы на подписку",
"tabs_bar.compose": "Написать",
"tabs_bar.home": "Главная",
"tabs_bar.mentions": "Упоминания",
"tabs_bar.public": "Глобальная лента",
"tabs_bar.notifications": "Уведомления",
+ "tabs_bar.local_timeline": "Локальная",
+ "tabs_bar.federated_timeline": "Глобальная",
"compose_form.placeholder": "О чем Вы думаете?",
"compose_form.publish": "Трубить",
"compose_form.sensitive": "Отметить как чувствительный контент",
@@ -49,6 +61,7 @@ const ru = {
"compose_form.private": "Отметить как приватное",
"compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
"compose_form.unlisted": "Не отображать в публичных лентах",
+ "compose_form.spoiler_placeholder": "Не для всех",
"navigation_bar.edit_profile": "Изменить профиль",
"navigation_bar.preferences": "Опции",
"navigation_bar.community_timeline": "Локальная лента",
@@ -57,12 +70,20 @@ const ru = {
"navigation_bar.info": "Об узле",
"navigation_bar.favourites": "Понравившееся",
"navigation_bar.blocks": "Список блокировки",
+ "navigation_bar.follow_requests": "Запросы на подписку",
"reply_indicator.cancel": "Отмена",
+ "report.target": "Жалуемся на",
+ "report.heading": "Новая жалоба",
+ "report.placeholder": "Комментарий",
+ "report.submit": "Отправить",
"search.placeholder": "Поиск",
"search.account": "Аккаунт",
"search.hashtag": "Хэштег",
+ "search.status_by": "Статус от {name}",
+ "upload_area.title": "Перетащите сюда, чтобы загрузить",
"upload_button.label": "Добавить медиаконтент",
"upload_form.undo": "Отменить",
+ "upload_progress.label": "Загрузка...",
"notification.follow": "{name} подписался(-лась) на Вас",
"notification.favourite": "{name} понравился Ваш статус",
"notification.reblog": "{name} продвинул(а) Ваш статус",
@@ -71,9 +92,10 @@ const ru = {
"home.column_settings.basic": "Основные",
"home.column_settings.advanced": "Дополнительные",
"home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
- "home.column_settings.show_replies": "Показывать продвижения",
+ "home.column_settings.show_reblogs": "Показывать продвижения",
"home.column_settings.show_replies": "Показывать ответы",
"notifications.clear": "Очистить уведомления",
+ "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?",
"notifications.settings": "Настройки колонки",
"notifications.column_settings.alert": "Десктопные уведомления",
"notifications.column_settings.show": "Показывать в колонке",
@@ -96,6 +118,10 @@ const ru = {
"privacy.private.long": "Показать только подписчикам",
"privacy.direct.short": "Направленный",
"privacy.direct.long": "Показать только упомянутым",
+ "emoji_button.label": "Вставить эмодзи",
+ "follow_request.authorize": "Авторизовать",
+ "follow_request.reject": "Отказать",
+ "media_gallery.toggle_visible": "Показать/скрыть",
};
export default ru;
diff --git a/app/assets/javascripts/components/locales/zh-hk.jsx b/app/assets/javascripts/components/locales/zh-hk.jsx
index b26a8c3d..676be3db 100644
--- a/app/assets/javascripts/components/locales/zh-hk.jsx
+++ b/app/assets/javascripts/components/locales/zh-hk.jsx
@@ -19,19 +19,27 @@ export { localeData as localeData };
const zh_hk = {
"account.block": "封鎖 @{name}",
+ "account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。",
"account.edit_profile": "修改個人資料",
"account.follow": "關注",
"account.followers": "關注的人",
"account.follows_you": "關注你",
"account.follows": "正在關注",
"account.mention": "提及 @{name}",
+ "account.mute": "將 @{name} 靜音",
"account.posts": "文章",
+ "account.report": "舉報 @{name}",
"account.requested": "等候審批",
"account.unblock": "解除對 @{name} 的封鎖",
"account.unfollow": "取消關注",
- "column_back_button.label": "先前顯示",
+ "account.unmute": "取消 @{name} 的靜音",
+ "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
+ "column_back_button.label": "返回",
+ "column.blocks": "封鎖用戶",
"column.community": "本站時間軸",
- "column.home": "家",
+ "column.favourites": "喜歡的文章",
+ "column.follow_requests": "關注請求",
+ "column.home": "主頁",
"column.notifications": "通知",
"column.public": "跨站公共時間軸",
"compose_form.placeholder": "你在想甚麼?",
@@ -39,35 +47,49 @@ const zh_hk = {
"compose_form.private": "標示為「只有關注你的人能看」",
"compose_form.publish": "發文",
"compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
+ "compose_form.spoiler_placeholder": "敏感內容",
"compose_form.spoiler": "將部份文字藏於警告訊息之後",
"compose_form.unlisted": "請勿在公共時間軸顯示",
+ "emoji_button.label": "加入表情符號",
"empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
"empty_column.hashtag": "這個標籤暫時未有內容。",
"empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
"empty_column.home.public_timeline": "公共時間軸",
- "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
- "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up.",
+ "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
+ "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
+ "empty_column.public": "跨站公共時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
+ "follow_request.authorize": "批准",
+ "follow_request.reject": "拒絕",
"getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。",
"getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。",
"getting_started.apps": "手機或桌面應用程式",
"getting_started.heading": "開始使用",
"getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。",
+ "home.column_settings.advanced": "進階",
"home.column_settings.basic": "基本",
+ "home.column_settings.filter_regex": "使用正規表達式 (regular expression) 過濾",
"home.column_settings.show_reblogs": "顯示被轉推的文章",
"home.column_settings.show_replies": "顯示回應文章",
- "home.column_settings.advanced": "進階",
- "lightbox.close": "關閉",
+ "home.settings": "欄位設定",
+ "lightbox.close": "Close",
"loading_indicator.label": "載入中...",
+ "media_gallery.toggle_visible": "打開或關上",
"missing_indicator.label": "找不到內容",
+ "navigation_bar.blocks": "被封鎖的用戶",
"navigation_bar.community_timeline": "本站時間軸",
"navigation_bar.edit_profile": "修改個人資料",
+ "navigation_bar.favourites": "喜歡的內容",
+ "navigation_bar.follow_requests": "關注請求",
+ "navigation_bar.info": "關於本服務站",
"navigation_bar.logout": "登出",
- "navigation_bar.preferences": "個人設定",
+ "navigation_bar.preferences": "偏好設定",
"navigation_bar.public_timeline": "跨站公共時間軸",
"notification.favourite": "{name} 喜歡你的文章",
- "notification.follow": "{name} 開始開始你",
+ "notification.follow": "{name} 開始關注你",
"notification.mention": "{name} 提及你",
"notification.reblog": "{name} 轉推你的文章",
+ "notifications.clear_confirmation": "你確定要清空通知紀錄嗎?",
+ "notifications.clear": "清空通知紀錄",
"notifications.column_settings.alert": "顯示桌面通知",
"notifications.column_settings.favourite": "喜歡你的文章:",
"notifications.column_settings.follow": "關注你:",
@@ -75,13 +97,26 @@ const zh_hk = {
"notifications.column_settings.reblog": "轉推你的文章:",
"notifications.column_settings.show": "在通知欄顯示",
"notifications.column_settings.sound": "播放音效",
+ "notifications.settings": "欄位設定",
+ "privacy.change": "調整私隱設定",
+ "privacy.direct.long": "只有提及的用戶能看到",
+ "privacy.direct.short": "私人訊息",
+ "privacy.private.long": "只有關注你用戶能看到",
+ "privacy.private.short": "關注者",
+ "privacy.public.long": "在公共時間軸顯示",
+ "privacy.public.short": "公共",
+ "privacy.unlisted.long": "公開,但不在公共時間軸顯示",
+ "privacy.unlisted.short": "公開",
"reply_indicator.cancel": "取消",
+ "report.heading": "舉報",
+ "report.placeholder": "額外訊息",
+ "report.submit": "提交",
"report.target": "Reporting",
+ "search_results.total": "{count} 項結果",
"search.account": "用戶",
"search.hashtag": "標籤",
"search.placeholder": "搜尋",
- "search_results.total": "{count} 項結果",
- "search.status_by": "按用戶名稱搜尋文章",
+ "search.status_by": "按{name}搜尋文章",
"status.delete": "刪除",
"status.favourite": "喜歡",
"status.load_more": "載入更多",
@@ -97,17 +132,19 @@ const zh_hk = {
"status.show_less": "減少顯示",
"status.show_more": "顯示更多",
"tabs_bar.compose": "撰寫",
- "tabs_bar.home": "家",
+ "tabs_bar.federated_timeline": "跨站",
+ "tabs_bar.home": "主頁",
"tabs_bar.local_timeline": "本站",
"tabs_bar.mentions": "提及",
"tabs_bar.notifications": "通知",
"tabs_bar.public": "跨站公共時間軸",
- "tabs_bar.federated_timeline": "跨站",
"upload_area.title": "將檔案拖放至此上載",
"upload_button.label": "上載媒體檔案",
- "upload_progress.label": "上載中……",
"upload_form.undo": "還原",
+ "upload_progress.label": "上載中……",
+ "video_player.expand": "展開影片",
"video_player.toggle_sound": "開關音效",
+ "video_player.toggle_visible": "打開或關上",
};
export default zh_hk;
diff --git a/app/assets/javascripts/components/middleware/errors.jsx b/app/assets/javascripts/components/middleware/errors.jsx
index 4aca75f1..9a51257c 100644
--- a/app/assets/javascripts/components/middleware/errors.jsx
+++ b/app/assets/javascripts/components/middleware/errors.jsx
@@ -22,7 +22,7 @@ export default function errorsMiddleware() {
dispatch(showAlert(title, message));
} else {
- console.error(action.error);
+ console.error(action.error); // eslint-disable-line no-console
dispatch(showAlert('Oops!', 'An unexpected error occurred.'));
}
}
diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx
index df944009..60d283b0 100644
--- a/app/assets/javascripts/components/reducers/accounts.jsx
+++ b/app/assets/javascripts/components/reducers/accounts.jsx
@@ -15,6 +15,10 @@ import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks';
+import {
+ MUTES_FETCH_SUCCESS,
+ MUTES_EXPAND_SUCCESS
+} from '../actions/mutes';
import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
import {
REBLOG_SUCCESS,
@@ -94,6 +98,8 @@ export default function accounts(state = initialState, action) {
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
case BLOCKS_FETCH_SUCCESS:
case BLOCKS_EXPAND_SUCCESS:
+ case MUTES_FETCH_SUCCESS:
+ case MUTES_EXPAND_SUCCESS:
return normalizeAccounts(state, action.accounts);
case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS:
diff --git a/app/assets/javascripts/components/reducers/alerts.jsx b/app/assets/javascripts/components/reducers/alerts.jsx
index 42987f64..dc014582 100644
--- a/app/assets/javascripts/components/reducers/alerts.jsx
+++ b/app/assets/javascripts/components/reducers/alerts.jsx
@@ -9,17 +9,17 @@ const initialState = Immutable.List([]);
export default function alerts(state = initialState, action) {
switch(action.type) {
- case ALERT_SHOW:
- return state.push(Immutable.Map({
- key: state.size > 0 ? state.last().get('key') + 1 : 0,
- title: action.title,
- message: action.message
- }));
- case ALERT_DISMISS:
- return state.filterNot(item => item.get('key') === action.alert.key);
- case ALERT_CLEAR:
- return state.clear();
- default:
- return state;
+ case ALERT_SHOW:
+ return state.push(Immutable.Map({
+ key: state.size > 0 ? state.last().get('key') + 1 : 0,
+ title: action.title,
+ message: action.message
+ }));
+ case ALERT_DISMISS:
+ return state.filterNot(item => item.get('key') === action.alert.key);
+ case ALERT_CLEAR:
+ return state.clear();
+ default:
+ return state;
}
};
diff --git a/app/assets/javascripts/components/reducers/meta.jsx b/app/assets/javascripts/components/reducers/meta.jsx
index cd4b313d..acf6d4be 100644
--- a/app/assets/javascripts/components/reducers/meta.jsx
+++ b/app/assets/javascripts/components/reducers/meta.jsx
@@ -2,6 +2,7 @@ import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
const initialState = Immutable.Map({
+ streaming_api_base_url: null,
access_token: null,
me: null
});
diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx
index 8c9a3d3a..7f55c364 100644
--- a/app/assets/javascripts/components/reducers/user_lists.jsx
+++ b/app/assets/javascripts/components/reducers/user_lists.jsx
@@ -16,6 +16,10 @@ import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks';
+import {
+ MUTES_FETCH_SUCCESS,
+ MUTES_EXPAND_SUCCESS
+} from '../actions/mutes';
import Immutable from 'immutable';
const initialState = Immutable.Map({
@@ -24,7 +28,8 @@ const initialState = Immutable.Map({
reblogged_by: Immutable.Map(),
favourited_by: Immutable.Map(),
follow_requests: Immutable.Map(),
- blocks: Immutable.Map()
+ blocks: Immutable.Map(),
+ mutes: Immutable.Map()
});
const normalizeList = (state, type, id, accounts, next) => {
@@ -65,6 +70,10 @@ export default function userLists(state = initialState, action) {
return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
case BLOCKS_EXPAND_SUCCESS:
return state.updateIn(['blocks', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
+ case MUTES_FETCH_SUCCESS:
+ return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
+ case MUTES_EXPAND_SUCCESS:
+ return state.updateIn(['mutes', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
default:
return state;
}
diff --git a/app/assets/javascripts/components/stream.jsx b/app/assets/javascripts/components/stream.jsx
index 392268b2..08da7160 100644
--- a/app/assets/javascripts/components/stream.jsx
+++ b/app/assets/javascripts/components/stream.jsx
@@ -10,8 +10,8 @@ const createWebSocketURL = (url) => {
return a.href;
};
-export default function getStream(accessToken, stream, { connected, received, disconnected, reconnected }) {
- const ws = new WebSocketClient(`${createWebSocketURL(STREAMING_API_BASE_URL)}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
+export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
+ const ws = new WebSocketClient(`${createWebSocketURL(streamingAPIBaseURL)}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
ws.onopen = connected;
ws.onmessage = e => received(JSON.parse(e.data));
diff --git a/app/assets/stylesheets/about.scss b/app/assets/stylesheets/about.scss
index 8bf950d4..4bd4c26d 100644
--- a/app/assets/stylesheets/about.scss
+++ b/app/assets/stylesheets/about.scss
@@ -64,7 +64,7 @@
p, li {
font: 16px/28px 'Montserrat', sans-serif;
font-weight: 400;
- margin-bottom: 26px;
+ margin-bottom: 12px;
a {
color: $color4;
@@ -352,7 +352,7 @@
}
}
}
-
+
@media screen and (max-width: 625px) {
.mascot {
display: none;
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 1c363ec1..90fada67 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -2056,3 +2056,11 @@ button.icon-button.active i.fa-retweet {
flex: 0 0 auto;
}
}
+
+.loading-bar {
+ background-color: $color4;
+ height: 3px;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
diff --git a/app/assets/stylesheets/lists.scss b/app/assets/stylesheets/lists.scss
index eac9f5a2..13243aae 100644
--- a/app/assets/stylesheets/lists.scss
+++ b/app/assets/stylesheets/lists.scss
@@ -6,3 +6,12 @@
margin: 0 5px;
}
}
+
+.recovery-codes {
+ column-count: 2;
+ height: 100px;
+ li {
+ list-style: decimal;
+ margin-left: 20px;
+ }
+}
diff --git a/app/controllers/admin/resets_controller.rb b/app/controllers/admin/resets_controller.rb
new file mode 100644
index 00000000..6db64840
--- /dev/null
+++ b/app/controllers/admin/resets_controller.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Admin
+ class ResetsController < BaseController
+ before_action :set_account
+
+ def create
+ @account.user.send_reset_password_instructions
+ redirect_to admin_accounts_path
+ end
+
+ private
+
+ def set_account
+ @account = Account.find(params[:account_id])
+ end
+ end
+end
diff --git a/app/controllers/api/oembed_controller.rb b/app/controllers/api/oembed_controller.rb
index 379e910e..2ea48229 100644
--- a/app/controllers/api/oembed_controller.rb
+++ b/app/controllers/api/oembed_controller.rb
@@ -14,7 +14,7 @@ class Api::OEmbedController < ApiController
def stream_entry_from_url(url)
params = Rails.application.routes.recognize_path(url)
- raise ActiveRecord::NotFound unless params[:controller] == 'stream_entries' && params[:action] == 'show'
+ raise ActiveRecord::RecordNotFound unless params[:controller] == 'stream_entries' && params[:action] == 'show'
StreamEntry.find(params[:id])
end
diff --git a/app/controllers/api/push_controller.rb b/app/controllers/api/push_controller.rb
index 78d4e36e..f2ddfd96 100644
--- a/app/controllers/api/push_controller.rb
+++ b/app/controllers/api/push_controller.rb
@@ -30,7 +30,7 @@ class Api::PushController < ApiController
params = Rails.application.routes.recognize_path(uri.path)
domain = uri.host + (uri.port ? ":#{uri.port}" : '')
- return unless TagManager.instance.local_domain?(domain) && params[:controller] == 'accounts' && params[:action] == 'show' && params[:format] == 'atom'
+ return unless TagManager.instance.web_domain?(domain) && params[:controller] == 'accounts' && params[:action] == 'show' && params[:format] == 'atom'
Account.find_local(params[:username])
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 61ca7112..0c320177 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
force_ssl if: "Rails.env.production? && ENV['LOCAL_HTTPS'] == 'true'"
include Localized
- helper_method :current_account
+ helper_method :current_account, :single_user_mode?
rescue_from ActionController::RoutingError, with: :not_found
rescue_from ActiveRecord::RecordNotFound, with: :not_found
@@ -69,6 +69,10 @@ class ApplicationController < ActionController::Base
end
end
+ def single_user_mode?
+ @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.first
+ end
+
def current_account
@current_account ||= current_user.try(:account)
end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 4881c074..f8050afb 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -28,7 +28,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def check_enabled_registrations
- redirect_to root_path if Rails.configuration.x.single_user_mode || !Setting.open_registrations
+ redirect_to root_path if single_user_mode? || !Setting.open_registrations
end
private
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index 4184750f..a187ae6d 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -49,7 +49,8 @@ class Auth::SessionsController < Devise::SessionsController
end
def valid_otp_attempt?(user)
- user.validate_and_consume_otp!(user_params[:otp_attempt])
+ user.validate_and_consume_otp!(user_params[:otp_attempt]) ||
+ user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
def authenticate_with_two_factor
diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb
index 22bb5b21..d9a7a722 100644
--- a/app/controllers/concerns/localized.rb
+++ b/app/controllers/concerns/localized.rb
@@ -27,7 +27,11 @@ module Localized
def default_locale
ENV.fetch('DEFAULT_LOCALE') {
- http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
+ user_supplied_locale || I18n.default_locale
}
end
+
+ def user_supplied_locale
+ http_accept_language.language_region_compatible_from(I18n.available_locales)
+ end
end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 814b1f75..2d1cf74f 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -4,15 +4,16 @@ class HomeController < ApplicationController
before_action :authenticate_user!
def index
- @body_classes = 'app-body'
- @token = find_or_create_access_token.token
- @web_settings = Web::Setting.find_by(user: current_user)&.data || {}
+ @body_classes = 'app-body'
+ @token = find_or_create_access_token.token
+ @web_settings = Web::Setting.find_by(user: current_user)&.data || {}
+ @streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
end
private
def authenticate_user!
- redirect_to(Rails.configuration.x.single_user_mode ? account_path(Account.first) : about_path) unless user_signed_in?
+ redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in?
end
def find_or_create_access_token
diff --git a/app/controllers/settings/two_factor_auths_controller.rb b/app/controllers/settings/two_factor_auths_controller.rb
index 203d1fc4..bfe3868f 100644
--- a/app/controllers/settings/two_factor_auths_controller.rb
+++ b/app/controllers/settings/two_factor_auths_controller.rb
@@ -19,9 +19,9 @@ class Settings::TwoFactorAuthsController < ApplicationController
def create
if current_user.validate_and_consume_otp!(confirmation_params[:code])
current_user.otp_required_for_login = true
+ @codes = current_user.generate_otp_backup_codes!
current_user.save!
-
- redirect_to settings_two_factor_auth_path, notice: I18n.t('two_factor_auth.enabled_success')
+ flash[:notice] = I18n.t('two_factor_auth.enabled_success')
else
@confirmation = Form::TwoFactorConfirmation.new
set_qr_code
@@ -30,6 +30,12 @@ class Settings::TwoFactorAuthsController < ApplicationController
end
end
+ def recovery_codes
+ @codes = current_user.generate_otp_backup_codes!
+ current_user.save!
+ flash[:notice] = I18n.t('two_factor_auth.recovery_codes_regenerated')
+ end
+
def disable
current_user.otp_required_for_login = false
current_user.save!
diff --git a/app/helpers/admin/accounts_helper.rb b/app/helpers/admin/filter_helper.rb
similarity index 70%
rename from app/helpers/admin/accounts_helper.rb
rename to app/helpers/admin/filter_helper.rb
index 497abf80..591056dd 100644
--- a/app/helpers/admin/accounts_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
-module Admin::AccountsHelper
- def filter_params(more_params)
- params.permit(:local, :remote, :by_domain, :silenced, :suspended, :recent, :resolved).merge(more_params)
- end
+module Admin::FilterHelper
+ ACCOUNT_FILTERS = %i[local remote by_domain silenced suspended recent].freeze
+ REPORT_FILTERS = %i[resolved].freeze
+
+ FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS
def filter_link_to(text, more_params)
new_url = filtered_url_for(more_params)
@@ -16,6 +17,10 @@ module Admin::AccountsHelper
private
+ def filter_params(more_params)
+ params.permit(FILTERS).merge(more_params)
+ end
+
def filter_link_class(new_url)
filtered_url_for(params) == new_url ? 'selected' : ''
end
diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb
index 07b2fb91..f26c943d 100644
--- a/app/lib/tag_manager.rb
+++ b/app/lib/tag_manager.rb
@@ -56,6 +56,10 @@ class TagManager
id.start_with?("tag:#{Rails.configuration.x.local_domain}")
end
+ def web_domain?(domain)
+ domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.web_domain).zero?
+ end
+
def local_domain?(domain)
domain.nil? || domain.gsub(/[\/]/, '').casecmp(Rails.configuration.x.local_domain).zero?
end
diff --git a/app/models/user.rb b/app/models/user.rb
index d2aa5d80..27a38674 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -5,7 +5,9 @@ class User < ApplicationRecord
devise :registerable, :recoverable,
:rememberable, :trackable, :validatable, :confirmable,
- :two_factor_authenticatable, otp_secret_encryption_key: ENV['OTP_SECRET']
+ :two_factor_authenticatable, :two_factor_backupable,
+ otp_secret_encryption_key: ENV['OTP_SECRET'],
+ otp_number_of_backup_codes: 10
belongs_to :account, inverse_of: :user
accepts_nested_attributes_for :account
diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb
index cd809566..13c5ece4 100644
--- a/app/presenters/instance_presenter.rb
+++ b/app/presenters/instance_presenter.rb
@@ -3,7 +3,7 @@
class InstancePresenter
delegate(
:closed_registrations_message,
- :contact_email,
+ :site_contact_email,
:open_registrations,
:site_description,
:site_extended_description,
diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb
index f55439dc..a9cb8550 100644
--- a/app/services/account_search_service.rb
+++ b/app/services/account_search_service.rb
@@ -1,26 +1,86 @@
# frozen_string_literal: true
class AccountSearchService < BaseService
+ attr_reader :query, :limit, :resolve, :account
+
def call(query, limit, resolve = false, account = nil)
- return [] if query.blank? || query.start_with?('#')
+ @query = query
+ @limit = limit
+ @resolve = resolve
+ @account = account
- username, domain = query.gsub(/\A@/, '').split('@')
- domain = nil if TagManager.instance.local_domain?(domain)
+ search_service_results
+ end
- if domain.nil?
- exact_match = Account.find_local(username)
- results = account.nil? ? Account.search_for(username, limit) : Account.advanced_search_for(username, account, limit)
+ private
+
+ def search_service_results
+ return [] if query_blank_or_hashtag?
+
+ if resolving_non_matching_remote_account?
+ [FollowRemoteAccountService.new.call("#{query_username}@#{query_domain}")]
else
- exact_match = Account.find_remote(username, domain)
- results = account.nil? ? Account.search_for("#{username} #{domain}", limit) : Account.advanced_search_for("#{username} #{domain}", account, limit)
+ search_results_and_exact_match.compact.uniq
end
+ end
- results = [exact_match] + results.reject { |a| a.id == exact_match.id } if exact_match
+ def resolving_non_matching_remote_account?
+ resolve && !exact_match && !domain_is_local?
+ end
- if resolve && !exact_match && !domain.nil?
- results = [FollowRemoteAccountService.new.call("#{username}@#{domain}")]
+ def search_results_and_exact_match
+ [exact_match] + search_results.to_a
+ end
+
+ def query_blank_or_hashtag?
+ query.blank? || query.start_with?('#')
+ end
+
+ def split_query_string
+ @_split_query_string ||= query.gsub(/\A@/, '').split('@')
+ end
+
+ def query_username
+ @_query_username ||= split_query_string.first
+ end
+
+ def query_domain
+ @_query_domain ||= query_without_split? ? nil : split_query_string.last
+ end
+
+ def query_without_split?
+ split_query_string.size == 1
+ end
+
+ def domain_is_local?
+ @_domain_is_local ||= TagManager.instance.local_domain?(query_domain)
+ end
+
+ def exact_match
+ @_exact_match ||= Account.find_remote(query_username, query_domain)
+ end
+
+ def search_results
+ @_search_results ||= if account
+ advanced_search_results
+ else
+ simple_search_results
end
+ end
- results
+ def advanced_search_results
+ Account.advanced_search_for(terms_for_query, account, limit)
+ end
+
+ def simple_search_results
+ Account.search_for(terms_for_query, limit)
+ end
+
+ def terms_for_query
+ if domain_is_local?
+ query_username
+ else
+ "#{query_username} #{query_domain}"
+ end
end
end
diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb
index 443c9c70..dce712b4 100644
--- a/app/services/follow_remote_account_service.rb
+++ b/app/services/follow_remote_account_service.rb
@@ -16,7 +16,7 @@ class FollowRemoteAccountService < BaseService
return Account.find_local(username) if TagManager.instance.local_domain?(domain)
account = Account.find_remote(username, domain)
- return account unless account.nil?
+ return account unless account&.last_webfingered_at.nil? || 1.day.from_now(account.last_webfingered_at) < Time.now.utc
Rails.logger.debug "Looking up webfinger for #{uri}"
@@ -29,20 +29,24 @@ class FollowRemoteAccountService < BaseService
return Account.find_local(confirmed_username) if TagManager.instance.local_domain?(confirmed_domain)
confirmed_account = Account.find_remote(confirmed_username, confirmed_domain)
- return confirmed_account unless confirmed_account.nil?
+ if confirmed_account.nil?
+ Rails.logger.debug "Creating new remote account for #{uri}"
- Rails.logger.debug "Creating new remote account for #{uri}"
+ domain_block = DomainBlock.find_by(domain: domain)
+ account = Account.new(username: confirmed_username, domain: confirmed_domain)
+ account.suspended = true if domain_block && domain_block.suspend?
+ account.silenced = true if domain_block && domain_block.silence?
+ account.private_key = nil
+ else
+ account = confirmed_account
+ end
- domain_block = DomainBlock.find_by(domain: domain)
+ account.last_webfingered_at = Time.now.utc
- account = Account.new(username: confirmed_username, domain: confirmed_domain)
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
account.salmon_url = data.link('salmon').href
account.url = data.link('http://webfinger.net/rel/profile-page').href
account.public_key = magic_key_to_pem(data.link('magic-public-key').href)
- account.private_key = nil
- account.suspended = true if domain_block && domain_block.suspend?
- account.silenced = true if domain_block && domain_block.silence?
body, xml = get_feed(account.remote_url)
hubs = get_hubs(xml)
diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb
index 2eaa7522..1b36b058 100644
--- a/app/services/process_feed_service.rb
+++ b/app/services/process_feed_service.rb
@@ -164,7 +164,7 @@ class ProcessFeedService < BaseService
url = Addressable::URI.parse(link['href'])
- mentioned_account = if TagManager.instance.local_domain?(url.host)
+ mentioned_account = if TagManager.instance.web_domain?(url.host)
Account.find_local(url.path.gsub('/users/', ''))
else
Account.find_by(url: link['href']) || FetchRemoteAccountService.new.call(link['href'])
diff --git a/app/services/pubsubhubbub/unsubscribe_service.rb b/app/services/pubsubhubbub/unsubscribe_service.rb
index 62459a0a..7adadf8e 100644
--- a/app/services/pubsubhubbub/unsubscribe_service.rb
+++ b/app/services/pubsubhubbub/unsubscribe_service.rb
@@ -4,7 +4,7 @@ class Pubsubhubbub::UnsubscribeService < BaseService
def call(account, callback)
return ['Invalid topic URL', 422] if account.nil?
- subscription = Subscription.where(account: account, callback_url: callback)
+ subscription = Subscription.find_by(account: account, callback_url: callback)
unless subscription.nil?
Pubsubhubbub::ConfirmationWorker.perform_async(subscription.id, 'unsubscribe')
diff --git a/app/views/about/_contact.html.haml b/app/views/about/_contact.html.haml
new file mode 100644
index 00000000..d8c54c18
--- /dev/null
+++ b/app/views/about/_contact.html.haml
@@ -0,0 +1,15 @@
+.panel
+ .panel-header= t 'about.contact'
+ .panel-body
+ - if contact.contact_account
+ .owner
+ .avatar= image_tag contact.contact_account.avatar.url
+ .name
+ = link_to TagManager.instance.url_for(contact.contact_account) do
+ %span.display_name.emojify= display_name(contact.contact_account)
+ %span.username= "@#{contact.contact_account.acct}"
+
+ - if contact.site_contact_email
+ .contact-email
+ = t 'about.business_email'
+ %strong= contact.site_contact_email
diff --git a/app/views/about/_links.html.haml b/app/views/about/_links.html.haml
new file mode 100644
index 00000000..492c7132
--- /dev/null
+++ b/app/views/about/_links.html.haml
@@ -0,0 +1,11 @@
+.panel
+ .panel-header= t 'about.links'
+ .panel-list
+ %ul
+ - if user_signed_in?
+ %li= link_to t('about.get_started'), root_path
+ - else
+ %li= link_to t('about.get_started'), new_user_registration_path
+ %li= link_to t('auth.login'), new_user_session_path
+ %li= link_to t('about.terms'), terms_path
+ %li= link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon'
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 8c12f57c..418c9824 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -28,29 +28,5 @@
.panel= @instance_presenter.site_extended_description.html_safe
.sidebar
- .panel
- .panel-header= t 'about.contact'
- .panel-body
- - if @instance_presenter.contact_account
- .owner
- .avatar= image_tag @instance_presenter.contact_account.avatar.url
- .name
- = link_to TagManager.instance.url_for(@instance_presenter.contact_account) do
- %span.display_name.emojify= display_name(@instance_presenter.contact_account)
- %span.username= "@#{@instance_presenter.contact_account.acct}"
-
- - unless @instance_presenter.contact_email.blank?
- .contact-email
- = t 'about.business_email'
- %strong= @instance_presenter.contact_email
- .panel
- .panel-header= t 'about.links'
- .panel-list
- %ul
- - if user_signed_in?
- %li= link_to t('about.get_started'), root_path
- - else
- %li= link_to t('about.get_started'), new_user_registration_path
- %li= link_to t('auth.login'), new_user_session_path
- %li= link_to t('about.terms'), terms_path
- %li= link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon'
+ = render partial: 'contact', object: @instance_presenter
+ = render 'links'
diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml
index 922e4257..39686b53 100644
--- a/app/views/about/show.html.haml
+++ b/app/views/about/show.html.haml
@@ -17,7 +17,7 @@
.wrapper
%h1
= image_tag 'logo.png'
- Mastodon
+ = Setting.site_title
%p= t('about.about_mastodon').html_safe
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 3b0d69dc..9c4e32e0 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -14,7 +14,7 @@
%meta{ property: 'og:image:height', content: '120' }/
%meta{ property: 'twitter:card', content: 'summary' }/
-- if !user_signed_in? && !Rails.configuration.x.single_user_mode
+- if !user_signed_in? && !single_user_mode?
= render partial: 'shared/landing_strip', locals: { account: @account }
.h-feed
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 6d2a4d12..07dcc7f4 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -61,12 +61,16 @@
= surround '(', ')' do
= number_to_human_size @account.media_attachments.sum('file_file_size')
-- if @account.silenced?
- = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button'
-- else
- = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button'
+%div{ style: 'float: right' }
+ = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button'
-- if @account.suspended?
- = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button'
-- else
- = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
+%div{ style: 'float: left' }
+ - if @account.silenced?
+ = link_to t('admin.accounts.undo_silenced'), admin_account_silence_path(@account.id), method: :delete, class: 'button'
+ - else
+ = link_to t('admin.accounts.silence'), admin_account_silence_path(@account.id), method: :post, class: 'button'
+
+ - if @account.suspended?
+ = link_to t('admin.accounts.undo_suspension'), admin_account_suspension_path(@account.id), method: :delete, class: 'button'
+ - else
+ = link_to t('admin.accounts.perform_full_suspension'), admin_account_suspension_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml
index 1deff82b..3dec40c4 100644
--- a/app/views/auth/sessions/two_factor.html.haml
+++ b/app/views/auth/sessions/two_factor.html.haml
@@ -2,7 +2,9 @@
= t('auth.login')
= simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
- = f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt') }, required: true, autofocus: true, autocomplete: 'off'
+ = f.input :otp_attempt, type: :number, placeholder: t('simple_form.labels.defaults.otp_attempt'),
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt') }, required: true, autofocus: true, autocomplete: 'off',
+ hint: t('simple_form.hints.sessions.otp')
.actions
= f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
index 6cb71557..5b6f5b85 100644
--- a/app/views/home/index.html.haml
+++ b/app/views/home/index.html.haml
@@ -1,7 +1,5 @@
- content_for :header_tags do
- :javascript
- window.STREAMING_API_BASE_URL = '#{Rails.configuration.x.streaming_api_base_url}';
- window.INITIAL_STATE = #{json_escape(render(file: 'home/initial_state', formats: :json))}
+ %script#initial-state{:type => 'application/json'}!= json_escape(render(file: 'home/initial_state', formats: :json))
= javascript_include_tag 'application', integrity: true
diff --git a/app/views/home/initial_state.json.rabl b/app/views/home/initial_state.json.rabl
index caee2bfc..9f94e614 100644
--- a/app/views/home/initial_state.json.rabl
+++ b/app/views/home/initial_state.json.rabl
@@ -2,6 +2,7 @@ object false
node(:meta) do
{
+ streaming_api_base_url: @streaming_api_base_url,
access_token: @token,
locale: I18n.locale,
me: current_account.id,
diff --git a/app/views/settings/two_factor_auths/_recovery_codes.html.haml b/app/views/settings/two_factor_auths/_recovery_codes.html.haml
new file mode 100644
index 00000000..c23311e2
--- /dev/null
+++ b/app/views/settings/two_factor_auths/_recovery_codes.html.haml
@@ -0,0 +1,7 @@
+%p.hint= t('two_factor_auth.recovery_instructions')
+
+%h3= t('two_factor_auth.recovery_codes')
+%ol.recovery-codes
+ - @codes.each do |code|
+ %li
+ %samp= code
diff --git a/app/views/settings/two_factor_auths/create.html.haml b/app/views/settings/two_factor_auths/create.html.haml
new file mode 100644
index 00000000..8710b6e0
--- /dev/null
+++ b/app/views/settings/two_factor_auths/create.html.haml
@@ -0,0 +1,4 @@
+- content_for :page_title do
+ = t('settings.two_factor_auth')
+
+= render 'recovery_codes'
diff --git a/app/views/settings/two_factor_auths/recovery_codes.html.haml b/app/views/settings/two_factor_auths/recovery_codes.html.haml
new file mode 100644
index 00000000..8710b6e0
--- /dev/null
+++ b/app/views/settings/two_factor_auths/recovery_codes.html.haml
@@ -0,0 +1,4 @@
+- content_for :page_title do
+ = t('settings.two_factor_auth')
+
+= render 'recovery_codes'
diff --git a/app/views/settings/two_factor_auths/show.html.haml b/app/views/settings/two_factor_auths/show.html.haml
index 047fe0c5..bf19d24f 100644
--- a/app/views/settings/two_factor_auths/show.html.haml
+++ b/app/views/settings/two_factor_auths/show.html.haml
@@ -8,3 +8,8 @@
= link_to t('two_factor_auth.disable'), disable_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button'
- else
= link_to t('two_factor_auth.setup'), new_settings_two_factor_auth_path, class: 'block-button'
+
+- if current_user.otp_required_for_login
+ .simple_form
+ %p.hint= t('two_factor_auth.lost_recovery_codes')
+ = link_to t('two_factor_auth.generate_recovery_codes'), recovery_codes_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button'
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index 86294675..73a2365f 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -20,7 +20,7 @@
%meta{ property: 'twitter:card', content: 'summary' }/
-- if !user_signed_in? && !Rails.configuration.x.single_user_mode
+- if !user_signed_in? && !single_user_mode?
= render partial: 'shared/landing_strip', locals: { account: @stream_entry.account }
.activity-stream.activity-stream-headless.h-entry
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index c894cdb2..829f003a 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -3,7 +3,7 @@
.compact-header
%h1<
- = link_to 'Mastodon', root_path
+ = link_to site_title, root_path
%small= "##{@tag.name}"
- if @statuses.empty?
diff --git a/config/application.rb b/config/application.rb
index f5c27686..fa7098b3 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -42,6 +42,7 @@ module Mastodon
:uk,
'zh-CN',
:'zh-HK',
+ :'zh-TW',
]
config.i18n.default_locale = :en
diff --git a/config/environments/production.rb b/config/environments/production.rb
index 0f107654..80021287 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -55,6 +55,8 @@ Rails.application.configure do
ENV['REDIS_HOST'] = redis_url.host
ENV['REDIS_PORT'] = redis_url.port.to_s
ENV['REDIS_PASSWORD'] = redis_url.password
+ db_num = redis_url.path[1..-1]
+ ENV['REDIS_DB'] = db_num if db_num.present?
end
# Use a different cache store in production.
@@ -62,7 +64,7 @@ Rails.application.configure do
host: ENV.fetch('REDIS_HOST') { 'localhost' },
port: ENV.fetch('REDIS_PORT') { 6379 },
password: ENV.fetch('REDIS_PASSWORD') { false },
- db: 0,
+ db: ENV.fetch('REDIS_DB') { 0 },
namespace: 'cache',
expires_in: 10.minutes,
}
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 3c23e7b2..4754c2c8 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -1,6 +1,7 @@
Devise.setup do |config|
config.warden do |manager|
manager.default_strategies(scope: :user).unshift :two_factor_authenticatable
+ manager.default_strategies(scope: :user).unshift :two_factor_backupable
end
# The secret key used by Devise. Devise uses this key to generate
diff --git a/config/initializers/ostatus.rb b/config/initializers/ostatus.rb
index fb0b8b7f..155d0a9f 100644
--- a/config/initializers/ostatus.rb
+++ b/config/initializers/ostatus.rb
@@ -2,18 +2,20 @@
port = ENV.fetch('PORT') { 3000 }
host = ENV.fetch('LOCAL_DOMAIN') { "localhost:#{port}" }
+web_host = ENV.fetch('WEB_DOMAIN') { host }
https = ENV['LOCAL_HTTPS'] == 'true'
Rails.application.configure do
config.x.local_domain = host
+ config.x.web_domain = web_host
config.x.use_https = https
config.x.use_s3 = ENV['S3_ENABLED'] == 'true'
- config.action_mailer.default_url_options = { host: host, protocol: https ? 'https://' : 'http://', trailing_slash: false }
+ config.action_mailer.default_url_options = { host: web_host, protocol: https ? 'https://' : 'http://', trailing_slash: false }
config.x.streaming_api_base_url = 'http://localhost:4000'
if Rails.env.production?
- config.action_cable.allowed_request_origins = ["http#{https ? 's' : ''}://#{host}"]
- config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') { "http#{https ? 's' : ''}://#{host}" }
+ config.action_cable.allowed_request_origins = ["http#{https ? 's' : ''}://#{web_host}"]
+ config.x.streaming_api_base_url = ENV.fetch('STREAMING_API_BASE_URL') { "http#{https ? 's' : ''}://#{web_host}" }
end
end
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index c58f1b06..202c102c 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -38,4 +38,7 @@ if ENV['S3_ENABLED'] == 'true'
Paperclip::Attachment.default_options[:url] = ':s3_alias_url'
Paperclip::Attachment.default_options[:s3_host_alias] = ENV['S3_CLOUDFRONT_HOST']
end
+else
+ Paperclip::Attachment.default_options[:path] = (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename'
+ Paperclip::Attachment.default_options[:url] = (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename'
end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index ecdd07b0..8ae3bd5a 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,11 +1,12 @@
host = ENV.fetch('REDIS_HOST') { 'localhost' }
port = ENV.fetch('REDIS_PORT') { 6379 }
password = ENV.fetch('REDIS_PASSWORD') { false }
+db = ENV.fetch('REDIS_DB') { 0 }
Sidekiq.configure_server do |config|
- config.redis = { host: host, port: port, password: password}
+ config.redis = { host: host, port: port, db: db, password: password }
end
Sidekiq.configure_client do |config|
- config.redis = { host: host, port: port, password: password }
+ config.redis = { host: host, port: port, db: db, password: password }
end
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
index bceb66ca..e0e60adf 100644
--- a/config/locales/bg.yml
+++ b/config/locales/bg.yml
@@ -165,5 +165,3 @@ bg:
users:
invalid_email: E-mail адресът е невалиден
invalid_otp_token: Невалиден код
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index d1dae7c3..f2bc6a97 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -6,12 +6,12 @@ fr:
send_instructions: Vous allez recevoir les instructions nécessaires à la confirmation de votre compte dans quelques minutes.
send_paranoid_instructions: Si votre e-mail existe dans notre base de données, vous allez bientôt recevoir un e-mail contenant les instructions de confirmation de votre compte.
failure:
- already_authenticated: Vous êtes déjà connecté
- inactive: Votre compte n'est pas encore activé.
- invalid: Email ou mot de passe incorrect.
+ already_authenticated: Vous êtes déjà connecté⋅e
+ inactive: Votre compte n’est pas encore activé.
+ invalid: Courriel ou mot de passe incorrect.
last_attempt: Vous avez droit à une tentative avant que votre compte ne soit verrouillé.
locked: Votre compte est verrouillé.
- not_found_in_database: Email ou mot de passe invalide.
+ not_found_in_database: Courriel ou mot de passe invalide.
timeout: Votre session a expiré. Veuillez vous reconnecter pour continuer.
unauthenticated: Vous devez vous connecter ou vous inscrire pour continuer.
unconfirmed: Vous devez valider votre compte pour continuer.
@@ -25,7 +25,7 @@ fr:
unlock_instructions:
subject: Instructions pour déverrouiller votre compte
omniauth_callbacks:
- failure: 'Nous n''avons pas pu vous authentifier via %{kind} : ''%{reason}''.'
+ failure: 'Nous n’avons pas pu vous authentifier via %{kind} : ''%{reason}''.'
success: Authentifié avec succès via %{kind}.
passwords:
no_token: Vous ne pouvez accéder à cette page sans passer par un e-mail de réinitialisation de mot de passe. Si vous êtes passé⋅e par un e-mail de ce type, assurez-vous d'utiliser l'URL complète.
@@ -36,10 +36,10 @@ fr:
registrations:
destroyed: Votre compte a été supprimé avec succès. Nous espérons vous revoir bientôt.
signed_up: Bienvenue, vous êtes connecté⋅e.
- signed_up_but_inactive: Vous êtes bien enregistré⋅e. Vous ne pouvez cependant pas vous connecter car votre compte n'est pas encore activé.
+ signed_up_but_inactive: Vous êtes bien enregistré⋅e. Vous ne pouvez cependant pas vous connecter car votre compte n’est pas encore activé.
signed_up_but_locked: Vous êtes bien enregistré⋅e. Vous ne pouvez cependant pas vous connecter car votre compte est verrouillé.
- signed_up_but_unconfirmed: Un message contenant un lien de confirmation a été envoyé à votre adresse email. Ouvrez ce lien pour activer votre compte.
- update_needs_confirmation: Votre compte a bien été mis à jour mais nous devons vérifier votre nouvelle adresse email. Merci de vérifier vos emails et de cliquer sur le lien de confirmation pour finaliser la validation de votre nouvelle adresse.
+ signed_up_but_unconfirmed: Un message contenant un lien de confirmation a été envoyé à votre adresse courriel. Ouvrez ce lien pour activer votre compte.
+ update_needs_confirmation: Votre compte a bien été mis à jour mais nous devons vérifier votre nouvelle adresse courriel. Merci de vérifier vos courriels et de cliquer sur le lien de confirmation pour finaliser la validation de votre nouvelle adresse.
updated: Votre compte a été modifié avec succès.
sessions:
already_signed_out: Déconnecté.
@@ -47,15 +47,15 @@ fr:
signed_out: Déconnecté.
unlocks:
send_instructions: Vous allez recevoir les instructions nécessaires au déverrouillage de votre compte dans quelques instants
- send_paranoid_instructions: Si votre compte existe, vous allez bientôt recevoir un email contenant les instructions pour le déverrouiller.
+ send_paranoid_instructions: Si votre compte existe, vous allez bientôt recevoir un courriel contenant les instructions pour le déverrouiller.
unlocked: Votre compte a été déverrouillé avec succès, vous êtes maintenant connecté⋅e.
errors:
messages:
already_confirmed: a déjà été validé⋅e, veuillez essayer de vous connecter
confirmation_period_expired: à confirmer dans les %{period}, merci de faire une nouvelle demande
- expired: a expiré, merci d'en faire une nouvelle demande
- not_found: n'a pas été trouvé⋅e
- not_locked: n'était pas verrouillé⋅e
+ expired: a expiré, merci d’en faire une nouvelle demande
+ not_found: n’a pas été trouvé⋅e
+ not_locked: n’était pas verrouillé⋅e
not_saved:
one: '1 erreur a empêché ce(tte) %{resource} d’être sauvegardé⋅e :'
other: '%{count} erreurs ont empêché %{resource} d’être sauvegardé⋅e :'
diff --git a/config/locales/devise.no.yml b/config/locales/devise.no.yml
index 8b650e54..1bb14d26 100644
--- a/config/locales/devise.no.yml
+++ b/config/locales/devise.no.yml
@@ -2,60 +2,60 @@
'no':
devise:
confirmations:
- confirmed: Epostaddressen din er blitt bekreftet.
- send_instructions: Du vil motta en epost med instruksjoner for hvordan bekrefte din epostaddresse om noen få minutter.
- send_paranoid_instructions: Hvis din epostaddresse finnes i vår database vil du motta en epost med instruksjoner for hvordan bekrefte din epost om noen få minutter.
+ confirmed: E-postaddressen din er blitt bekreftet.
+ send_instructions: Du vil motta en e-post med instruksjoner for bekreftelse om noen få minutter.
+ send_paranoid_instructions: Hvis din e-postaddresse finnes i vår database vil du motta en e-post med instruksjoner for bekreftelse om noen få minutter.
failure:
already_authenticated: Du er allerede innlogget.
inactive: Din konto er ikke blitt aktivert ennå.
invalid: Ugyldig %{authentication_keys} eller passord.
- last_attempt: Du har ett forsøk igjen før kontoen din bli låst.
+ last_attempt: Du har ett forsøk igjen før kontoen din låses.
locked: Din konto er låst.
not_found_in_database: Ugyldig %{authentication_keys} eller passord.
- timeout: Sesjonen din løp ut på tid. Logg inn på nytt for å fortsette.
+ timeout: Økten din løp ut på tid. Logg inn på nytt for å fortsette.
unauthenticated: Du må logge inn eller registrere deg før du kan fortsette.
- unconfirmed: Du må bekrefte epostadressen din før du kan fortsette.
+ unconfirmed: Du må bekrefte e-postadressen din før du kan fortsette.
mailer:
confirmation_instructions:
- subject: 'Mastodon: Instruksjoner for å bekrefte epostadresse'
+ subject: 'Mastodon: Instruksjoner for å bekrefte e-postadresse'
password_change:
subject: 'Mastodon: Passord endret'
reset_password_instructions:
- subject: 'Mastodon: Hvordan nullstille passord?'
+ subject: 'Mastodon: Hvordan nullstille passord'
unlock_instructions:
subject: 'Mastodon: Instruksjoner for å gjenåpne konto'
omniauth_callbacks:
failure: Kunne ikke autentisere deg fra %{kind} fordi "%{reason}".
success: Vellykket autentisering fra %{kind}.
passwords:
- no_token: Du har ingen tilgang til denne siden så lenge du ikke kommer fra en epost om nullstilling av passord. Hvis du kommer fra en passordnullstilling epost, dobbelsjekk at du brukte hele URLen.
- send_instructions: Du vil motta en epost med instruksjoner for å nullstille passordet ditt om noen få minutter.
- send_paranoid_instructions: Hvis epostadressen din finnes i databasen vår vil du motta en instruksjonsmail om passord nullstilling om noen få minutter.
- updated: Passordet ditt har blitt endret. Du er nå logget inn.
- updated_not_active: Passordet ditt har blitt endret.
+ no_token: Du har ingen tilgang til denne siden hvis ikke klikket på en e-post om nullstilling av passord. Hvis du kommer fra en sådan bør du dobbelsjekke at du limte inn hele URLen.
+ send_instructions: Du vil motta en e-post med instruksjoner om nullstilling av passord om noen få minutter.
+ send_paranoid_instructions: Hvis e-postadressen din finnes i databasen vår vil du motta en e-post med instruksjoner om nullstilling av passord om noen få minutter.
+ updated: Passordet ditt er endret. Du er nå logget inn.
+ updated_not_active: Passordet ditt er endret.
registrations:
- destroyed: Adjø! Kontoen din har blitt avsluttet. Vi håper at vi ser deg igjen snart.
- signed_up: Velkommen! Registrasjonen var vellykket.
- signed_up_but_inactive: Registrasjonen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din ennå ikke har blitt aktivert.
- signed_up_but_locked: Registrasjonen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din har blitt låst.
- signed_up_but_unconfirmed: En epostmelding med en bekreftelseslink har blitt sendt til din adresse. Klikk på linken i eposten for å aktivere kontoen din.
- update_needs_confirmation: Du har oppdatert kontoen din, men vi må bekrefte din nye epostadresse. Sjekk eposten din og følg bekreftelseslinken for å bekrefte din nye epostadresse.
+ destroyed: Adjø! Kontoen din er slettet. På gjensyn!
+ signed_up: Velkommen! Registreringen var vellykket.
+ signed_up_but_inactive: Registreringen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din ennå ikke har blitt aktivert.
+ signed_up_but_locked: Registreringen var vellykket. Vi kunne dessverre ikke logge deg inn fordi kontoen din har blitt låst.
+ signed_up_but_unconfirmed: En e-post med en bekreftelseslenke har blitt sendt til din innboks. Klikk på lenken i e-posten for å aktivere kontoen din.
+ update_needs_confirmation: Du har oppdatert kontoen din, men vi må bekrefte din nye e-postadresse. Sjekk e-posten din og følg bekreftelseslenken for å bekrefte din nye e-postadresse.
updated: Kontoen din ble oppdatert.
sessions:
already_signed_out: Logget ut.
signed_in: Logget inn.
signed_out: Logget ut.
unlocks:
- send_instructions: Du vil motta en epost med instruksjoner for å åpne kontoen din om noen få minutter.
- send_paranoid_instructions: Hvis kontoen din eksisterer vil du motta en epost med instruksjoner for å åpne kontoen din om noen få minutter.
+ send_instructions: Du vil motta en e-post med instruksjoner for å åpne kontoen din om noen få minutter.
+ send_paranoid_instructions: Hvis kontoen din eksisterer vil du motta en e-post med instruksjoner for å åpne kontoen din om noen få minutter.
unlocked: Kontoen din ble åpnet uten problemer. Logg på for å fortsette.
errors:
messages:
- already_confirmed: har allerede blitt bekreftet, prøv å logg på istedet.
- confirmation_period_expired: må bekreftes innen %{period}. Spør om en ny bekreftelsesmail istedet.
+ already_confirmed: har allerede blitt bekreftet, prøv å logge på istedet.
+ confirmation_period_expired: må bekreftes innen %{period}. Spør om en ny e-mail for bekreftelse istedet.
expired: har utløpt, spør om en ny en istedet
not_found: ikke funnet
not_locked: var ikke låst
not_saved:
- one: '1 feil hindret denne %{resource} fra å bli lagret:'
- other: "%{count} feil hindret denne %{resource} fra å bli lagret:"
+ one: '1 feil hindret denne %{resource} i å bli lagret:'
+ other: "%{count} feil hindret denne %{resource} i å bli lagret:"
diff --git a/config/locales/devise.pl.yml b/config/locales/devise.pl.yml
new file mode 100644
index 00000000..d4ee1b6a
--- /dev/null
+++ b/config/locales/devise.pl.yml
@@ -0,0 +1,61 @@
+---
+pl:
+ devise:
+ confirmations:
+ confirmed: Twój adres e-mail został poprawnie zweryfikowany.
+ send_instructions: W ciągu kilku minut otrzymasz wiadomosć e-mail z instrukcją jak potwierdzić Twój adres e-mail.
+ send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją jak potwierdzić Twój adres e-mail.
+ failure:
+ already_authenticated: Jesteś już zalogowany/zalogowana.
+ inactive: Twoje konto nie zostało jeszcze aktywowane.
+ invalid: Błędne %{authentication_keys} lub hasło.
+ last_attempt: Masz jeszcze jedną próbę; Twoje konto zostanie zablokowane jeśli się nie powiedzie.
+ locked: Twoje konto zostało zablokowane.
+ not_found_in_database: Błędne %{authentication_keys} lub hasło.
+ timeout: Twoja sesja wygasła. Zaloguj się ponownie aby kontynuować..
+ unauthenticated: Zapisz się lub zaloguj aby kontynuować.
+ unconfirmed: Zweryfikuj adres e-mail aby kontynuować.
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: Instrukcje weryfikacji adresu e-mail'
+ password_change:
+ subject: 'Mastodon: Hasło zmienione'
+ reset_password_instructions:
+ subject: 'Mastodon: Instrukcje ustawienia nowego hasła'
+ unlock_instructions:
+ subject: 'Mastodon: Instrukcje odblokowania konta'
+ omniauth_callbacks:
+ failure: 'Uwierzytelnienie przez %{kind} nie powiodło się, ponieważ: "%{reason}".'
+ success: Uwierzytelnienie przez %{kind} powiodło się.
+ passwords:
+ no_token: Dostęp do tej strony możliwy jest wyłącznie za pomocą odnośnika z e-maila z instrukcjami ustawienia nowego hasła. Jeśli skorzystałeś/aś z takiego odnośnika, upewnij się, że został wykorzystany/skopiowany cały odnośnik.
+ send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcją ustawienia nowego hasła.
+ send_paranoid_instructions: Jeśli Twój adres e-mail już istnieje w naszej bazie danych, w ciągu kilku minut otrzymasz wiadomość e-mail zawierającą odnośnik pozwalający na ustawienie nowego hasła.
+ updated: Twoje hasło zostało zmienione. Jesteś zalogowany/a.
+ updated_not_active: Twoje hasło zostało zmienione.
+ registrations:
+ destroyed: Twoje konto zostało anulowane. Mamy jednak nadzieję, że do nas wrócisz. Do zobaczenia!
+ signed_up: Twoje konto zostało utworzone. Witamy!
+ signed_up_but_inactive: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto nie zostało jeszcze aktywowane.
+ signed_up_but_locked: Twoje konto zostało utworzone. Nie mogliśmy Cię jednak zalogować, ponieważ konto jest zablokowane.
+ signed_up_but_unconfirmed: Na Twój adres e-mail została wysłana wiadomosć z odnośnikiem potwierdzającym. Kliknij w odnośnik aby aktywować konto.
+ update_needs_confirmation: Konto zostało zaktualizowane, musimy jednak zweryfikować Twój nowy adres e-mail. Została na niego wysłana wiadomość z odnośnikiem potwierdzającym.
+ updated: Konto zostało zaktualizowane.
+ sessions:
+ already_signed_out: Zostałeś/aś wylogowany/a.
+ signed_in: Zostałeś/aś zalogowany/a.
+ signed_out: Zostałeś/aś wylogowany/a.
+ unlocks:
+ send_instructions: W ciągu kilku minut otrzymasz wiadomość e-mail z instrukcjami odblokowania konta.
+ send_paranoid_instructions: Jeśli Twoje konto istnieje, instrukcje odblokowania go otrzymasz w wiadomości e-mail w ciągu kilku minut.
+ unlocked: Twoje konto zostało odblokowane. Zaloguj się aby kontynuować.
+ errors:
+ messages:
+ already_confirmed: był już potwierdzony, spróbuj się zalogować
+ confirmation_period_expired: musi być potwierdzony w ciągu %{period}, poproś o nowe potwierdzenie
+ expired: wygasło, poproś o nowe
+ not_found: nie znaleziono
+ not_locked: było zablokowane
+ not_saved:
+ one: '1 błąd uniemożliwił zapisanie zasobu %{resource}:'
+ other: "Błędy (%{count}) uniemożliwiły zapisanie zasobu %{resource}:"
diff --git a/config/locales/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml
index cecd4007..a3e5980d 100644
--- a/config/locales/devise.zh-HK.yml
+++ b/config/locales/devise.zh-HK.yml
@@ -57,5 +57,5 @@ zh-HK:
not_found: 找不到
not_locked: 並未被鎖定
not_saved:
- one: '1 個錯誤令 %{resource} 被法被儲存︰'
+ one: 1 個錯誤令 %{resource} 被法被儲存︰
other: "%{count} 個錯誤令 %{resource} 被法被儲存︰"
diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml
new file mode 100644
index 00000000..c38839b9
--- /dev/null
+++ b/config/locales/devise.zh-TW.yml
@@ -0,0 +1,61 @@
+---
+zh-TW:
+ devise:
+ confirmations:
+ confirmed: 信箱驗證成功
+ send_instructions: 您將會在幾分鐘內收到驗證信。
+ send_paranoid_instructions: 如果您的電子信箱已經存在於我們的資料庫,您將會在幾分鐘內收到信,確認您電子信箱的指示。
+ failure:
+ already_authenticated: 您已經登入了。
+ inactive: 您的帳號尚未啟用。
+ invalid: 不正確的 %{authentication_keys} 或密碼。
+ last_attempt: 若您再次嘗試失敗,我們將鎖定您的帳號,以策安全。
+ locked: 您的帳號已被鎖定
+ not_found_in_database: 不正確的 %{authentication_keys} 或密碼。
+ timeout: 您的登入階段已經逾期,請重新登入以繼續使用。
+ unauthenticated: 您必須先登入或註冊,以繼續使用。
+ unconfirmed: 您必須先完成信箱驗證,以繼續使用。
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: 信箱驗證'
+ password_change:
+ subject: 'Mastodon: 更改密碼'
+ reset_password_instructions:
+ subject: 'Mastodon: 重設密碼'
+ unlock_instructions:
+ subject: 'Mastodon: 帳號解鎖'
+ omniauth_callbacks:
+ failure: 無法以 %{kind} 登入您的帳號,原因是︰「%{reason}」。
+ success: 成功以 %{kind} 登入您的帳號。
+ passwords:
+ no_token: 您請使用重設密碼信中的網址,並確認您用了完整的網址。
+ send_instructions: 您將在幾分鐘內收到重設密碼信。
+ send_paranoid_instructions: 如果您的電子信箱已經存在於我們的資料庫,您將會在幾分鐘內收到重設密碼信。
+ updated: 您的密碼已經更新,您現在正登入本站。
+ updated_not_active: 您的密碼已經更新。
+ registrations:
+ destroyed: 再見了!您的帳號已被取消,期待再相逢。
+ signed_up: 歡迎您!您的已成功註冊。
+ signed_up_but_inactive: 您已成功註冊,但由於您的帳號尚未啟用,我們暫時無法讓您登入。
+ signed_up_but_locked: 您已成功註冊,但由於您的帳號已被鎖定,我們無法讓您登入。
+ signed_up_but_unconfirmed: 驗證信已寄出。請使用該連結啟用您的帳號。
+ update_needs_confirmation: 已更新您的帳號,但我們需要進行信箱驗證。請開啟您的信箱,使用確認信中的連結進行驗證。
+ updated: 您的帳號已成功更新。
+ sessions:
+ already_signed_out: 成功登出。
+ signed_in: 成功登入。
+ signed_out: 成功登出。
+ unlocks:
+ send_instructions: 您將在幾分鐘內收到帳號解鎖信。
+ send_paranoid_instructions: 如果您的電子信箱已經存在於我們的資料庫,您將在幾分鐘內收到帳號解鎖信。
+ unlocked: 已解鎖您的帳號,請登入以繼續。
+ errors:
+ messages:
+ already_confirmed: 已經確認,請嘗試登入
+ confirmation_period_expired: 需要在 %{period} 內完成驗證。請重新申請
+ expired: 已經過期,請重新申請
+ not_found: 找不到
+ not_locked: 並未被鎖定
+ not_saved:
+ one: '1 個錯誤使 %{resource} 無法被儲存︰'
+ other: "%{count} 個錯誤使 %{resource} 無法被儲存︰"
diff --git a/config/locales/doorkeeper.es.yml b/config/locales/doorkeeper.es.yml
index 74360371..f3efb230 100644
--- a/config/locales/doorkeeper.es.yml
+++ b/config/locales/doorkeeper.es.yml
@@ -41,7 +41,7 @@ es:
title: Nueva aplicación
show:
actions: Acciones
- application_id: Id de aplicación
+ application_id: Id de la aplicación
callback_urls: Callback urls
scopes: Ámbitos
secret: Secreto
@@ -49,12 +49,12 @@ es:
authorizations:
buttons:
authorize: Autorizar
- deny: Denegar
+ deny: Desautorizar
error:
title: Ha ocurrido un error
new:
able_to: Será capaz de
- prompt: La aplicación %{client_name} solicita acceder a su cuenta
+ prompt: La aplicación %{client_name} solicita tener acceso a su cuenta
title: Se requiere autorización
show:
title: Código de autorización
@@ -72,16 +72,16 @@ es:
messages:
access_denied: El propietario del recurso o servidor de autorización denegó la petición.
credential_flow_not_configured: Las credenciales de contraseña del propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_from_credentials está sin configurar.
- invalid_client: La autentificación del cliente falló debido a un cliente desconocido, no hay incluido autentificación del cliente, o método de autentificación no confirmado.
- invalid_grant: La concesión de autorización ofrecida es inválida, expiró, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente.
+ invalid_client: La autentificación del cliente falló debido o a que es un cliente desconocido o no está incluída la autentificación del cliente o el método de autentificación no está confirmado.
+ invalid_grant: La concesión de autorización ofrecida es inválida, venció, se revocó, no coincide con la URI de redirección utilizada en la petición de autorización, o fue emitida para otro cliente.
invalid_redirect_uri: La URI de redirección incluida no es válida.
- invalid_request: En la petición falta un parámetro necesario, incluye un valor de parámetro no soportado o tiene otro tipo de formato incorrecto.
- invalid_resource_owner: Las credeciales del propietario del recurso proporcionado no son válidas, o el propietario del recurso no puede ser encontrado
+ invalid_request: En la petición falta un parámetro necesario o incluye un valor de parámetro no soportado o tiene otro tipo de formato incorrecto.
+ invalid_resource_owner: Las credenciales del propietario del recurso proporcionado no son válidas, o el propietario del recurso no puede ser encontrado.
invalid_scope: El ámbito pedido es inválido, desconocido o erróneo.
invalid_token:
- expired: El identificador de acceso expiró
- revoked: El identificador de acceso fue revocado
- unknown: El identificador de acceso es inválido
+ expired: El identificador de acceso finalizó.
+ revoked: El identificador de acceso fue revocado.
+ unknown: El identificador de acceso es inválido.
resource_owner_authenticator_not_configured: El propietario del recurso falló debido a que Doorkeeper.configure.resource_owner_authenticator está sin configurar.
server_error: El servidor de la autorización entontró una condición inesperada que le impidió cumplir con la solicitud.
temporarily_unavailable: El servidor de la autorización es actualmente incapaz de manejar la petición debido a una sobrecarga temporal o un trabajo de mantenimiento del servidor.
diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml
index 842a96ac..a7d4a966 100644
--- a/config/locales/doorkeeper.fr.yml
+++ b/config/locales/doorkeeper.fr.yml
@@ -33,9 +33,9 @@ fr:
user:
attributes:
email:
- blank: Email vide
- invalid: Email invalide
- taken: Email pris
+ blank: Courriel vide
+ invalid: Courriel invalide
+ taken: Courriel pris
password:
blank: Mot de passe vide
too_short: Mot de passe trop court
diff --git a/config/locales/doorkeeper.no.yml b/config/locales/doorkeeper.no.yml
index f149f53e..23ca61d4 100644
--- a/config/locales/doorkeeper.no.yml
+++ b/config/locales/doorkeeper.no.yml
@@ -20,38 +20,38 @@
authorize: Autoriser
cancel: Avbryt
destroy: Ødelegg
- edit: Endre
+ edit: Rediger
submit: Send inn
confirmations:
destroy: Er du sikker?
edit:
title: Endre applikasjon
form:
- error: Whoops! Sjekk skjemaet ditt for mulige feil
+ error: Oops! Sjekk skjemaet ditt for mulige feil
help:
native_redirect_uri: Bruk %{native_redirect_uri} for lokale tester
- redirect_uri: Bruk en linje per URI
+ redirect_uri: Bruk én linje per URI
scopes: Adskill omfang med mellomrom. La det være blankt for å bruke standard omfang.
index:
- callback_url: Callback URL
+ callback_url: Callback-URL
name: Navn
- new: Ny Applikasjon
+ new: Ny applikasjon
title: Dine applikasjoner
new:
- title: Ny Applikasjoner
+ title: Nye applikasjoner
show:
actions: Operasjoner
- application_id: Applikasjon Id
- callback_urls: Callback urls
+ application_id: Applikasjons-ID
+ callback_urls: Callback-URLer
scopes: Omfang
secret: Hemmelighet
title: 'Applikasjon: %{name}'
authorizations:
buttons:
- authorize: Autoriser
+ authorize: Autorisér
deny: Avvis
error:
- title: En feil oppsto
+ title: En feil oppstod
new:
able_to: Den vil ha mulighet til
prompt: Applikasjon %{client_name} spør om tilgang til din konto
@@ -71,24 +71,24 @@
title: Dine autoriserte applikasjoner
errors:
messages:
- access_denied: Ressurseieren eller autoriserings tjeneren avviste forespørslen.
- credential_flow_not_configured: Ressurseiers passord flyt feilet på grunn av at Doorkeeper.configure.resource_owner_from_credentials ikke var konfigurert.
- invalid_client: Klient autentisering feilet på grunn av ukjent klient, ingen autentisering inkludert eller autentiserings metode som ikke er støttet.
+ access_denied: Ressurseieren eller autoriseringstjeneren avviste forespørslen.
+ credential_flow_not_configured: Ressurseiers passordflyt feilet fordi Doorkeeper.configure.resource_owner_from_credentials ikke var konfigurert.
+ invalid_client: Klientautentisering feilet på grunn av ukjent klient, ingen autentisering inkludert, eller autentiseringsmetode er ikke støttet.
invalid_grant: Autoriseringen er ugyldig, utløpt, opphevet, stemmer ikke overens med omdirigerings-URIen eller var utstedt til en annen klient.
- invalid_redirect_uri: redirect urien som var inkludert er ikke gyldig.
- invalid_request: Forespørslen mangler ett eller flere parametere, inkluderte ett parameter som ikke støttes eller har feil struktur.
- invalid_resource_owner: Ressurseierens detaljer er ikke gyldig, eller så kan ikke eieren finnes.
+ invalid_redirect_uri: Den inkluderte omdirigerings-URLen er ikke gyldig.
+ invalid_request: Forespørslen mangler en eller flere parametere, inkluderte en parameter som ikke støttes eller har feil struktur.
+ invalid_resource_owner: Ressurseierens detaljer er ikke gyldige, eller så kan ikke eieren finnes.
invalid_scope: Det etterspurte omfanget er ugyldig, ukjent eller har feil struktur.
invalid_token:
expired: Tilgangsbeviset har utløpt
revoked: Tilgangsbeviset har blitt opphevet
unknown: Tilgangsbeviset er ugyldig
resource_owner_authenticator_not_configured: Ressurseier kunne ikke finnes fordi Doorkeeper.configure.resource_owner_authenticator ikke er konfigurert.
- server_error: Autoriserings tjeneren støtte på en uventet hendelse som hindret den i å svare på forespørslen.
- temporarily_unavailable: Autoriserings tjeneren kan ikke håndtere forespørslen grunnet en midlertidig overbelastning eller tjenervedlikehold.
+ server_error: Autoriseringstjeneren støtte på en uventet hendelse som hindret den i å svare på forespørslen.
+ temporarily_unavailable: Autoriseringstjeneren kan ikke håndtere forespørslen grunnet en midlertidig overbelastning eller tjenervedlikehold.
unauthorized_client: Klienten har ikke autorisasjon for å utføre denne forespørslen med denne metoden.
- unsupported_grant_type: Autorisasjons tildelings typen er ikke støttet av denne autoriserings tjeneren.
- unsupported_response_type: Autorisasjons serveren støtter ikke denne typen av forespørsler.
+ unsupported_grant_type: Autorisasjonstildelingstypen er ikke støttet av denne autoriseringstjeneren.
+ unsupported_response_type: Autorisasjonsserveren støtter ikke denne typen av forespørsler.
flash:
applications:
create:
@@ -104,10 +104,10 @@
admin:
nav:
applications: Applikasjoner
- oauth2_provider: OAuth2 tilbyder
+ oauth2_provider: OAuth2-tilbyder
application:
- title: OAuth autorisering påkrevet
+ title: OAuth-autorisering påkrevet
scopes:
- follow: følg, blokker, avblokker, avfølg kontoer
+ follow: følg, blokkér, avblokkér, avfølg brukere
read: lese dine data
write: poste på dine vegne
diff --git a/config/locales/doorkeeper.pl.yml b/config/locales/doorkeeper.pl.yml
new file mode 100644
index 00000000..8103c456
--- /dev/null
+++ b/config/locales/doorkeeper.pl.yml
@@ -0,0 +1,113 @@
+---
+pl:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Nazwa
+ redirect_uri: URI przekierowania
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: nie może zawierać fragmentu.
+ invalid_uri: musi być poprawnym adresem URI.
+ relative_uri: musi być bezwzględnym adresem URI.
+ secured_uri: musi być bezpiecznym (HTTPS/TLS) adresem URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Autoryzuj
+ cancel: Anuluj
+ destroy: Usuń
+ edit: Edytuj
+ submit: Wyślij
+ confirmations:
+ destroy: Czy na pewno?
+ edit:
+ title: Edytuj aplikację
+ form:
+ error: Ups! Sprawdź, czy formularz nie zawiera błędów
+ help:
+ native_redirect_uri: Użyj %{native_redirect_uri} do lokalnych testów
+ redirect_uri: Jeden adres na linię tekstu
+ scopes: Rozdziel zakresy (scopes) spacjami. Zostaw puste aby użyć domyślnych zakresów.
+ index:
+ callback_url: URL wywołania zwrotnego (callback)
+ name: Nazwa
+ new: Nowa aplikacja
+ title: Twoje aplikacje
+ new:
+ title: Nowa aplikacja
+ show:
+ actions: Akcje
+ application_id: ID Aplikacji
+ callback_urls: Adresy wywołań zwrotnych
+ scopes: Zakresy (scopes)
+ secret: Sekret
+ title: 'Aplikacja: %{name}'
+ authorizations:
+ buttons:
+ authorize: Autoryzuj
+ deny: Odmów
+ error:
+ title: Wystapił błąd
+ new:
+ able_to: Będzie w stanie
+ prompt: Aplikacja %{client_name} prosi o dostęp do Twojego konta
+ title: Wymagana jest autoryzacja
+ show:
+ title: Kod autoryzacji
+ authorized_applications:
+ buttons:
+ revoke: Unieważnij
+ confirmations:
+ revoke: Czy na pewno?
+ index:
+ application: Aplikacja
+ created_at: Autoryzowana
+ date_format: "%d.%m.%Y %H:%M:%S"
+ scopes: Zakresy
+ title: Twoje autoryzowane aplikacje
+ errors:
+ messages:
+ access_denied: Właściciel zasobu lub serwer autoryzujący odrzuciły żądanie.
+ credential_flow_not_configured: Ścieżka "Resource Owner Password Credentials" zakończyła się błędem, ponieważ Doorkeeper.configure.resource_owner_from_credentials nie jest skonfigurowany.
+ invalid_client: Autoryzacja klienta nie powiodła się z powodu nieznanego klienta, braku uwierzytelnienia klienta, lub niewspieranej metody uwierzytelniania.
+ invalid_grant: Grant uwierzytelnienia jest niepoprawny, przeterminowany, unieważniony, nie pasuje do URI przekierowwania użytego w żądaniu uwierzytelnienia, lub został wystawiony przez innego klienta.
+ invalid_redirect_uri: URI przekierowania jest nieprawidłowy.
+ invalid_request: 'Żądanie jest nieprawidłowe: brakujący parametr, niewspierana wartość parametru, lub inny błąd.'
+ invalid_resource_owner: Dostarczone dane uwierzytelniające właściciela zasobu są niepoprawne, lub właściciel zasobu nie może zostać znaleziony.
+ invalid_scope: Zakres żądania jest niepoprawny, nieznany, lub błędnie zbudowany.
+ invalid_token:
+ expired: Token dostępowy wygasł
+ revoked: Token dostępowy został unieważniony
+ unknown: Token dostępowy jest błędny
+ resource_owner_authenticator_not_configured: Wyszukiwanie właściciela zasobu nie powiodło się, ponieważ Doorkeeper.configure.resource_owner_authenticator jest nieskonfigurowany.
+ server_error: Serwer uwierzytelniający napotkał niespodziewane warunki, które uniemożliwiły obsłużenie żądania.
+ temporarily_unavailable: Serwer uwierzytelniający nie jest obecnie w stanie obsłużyć żądania z powodu tymczasowego przeciążenia lub prac konserwacyjnych.
+ unauthorized_client: Klient nie jest uprawniony do wykonania tego żądania przy pomocy tej metody.
+ unsupported_grant_type: Ten typ grantu uwierzytelniającego nie jest wspierany przez serwer uwierzytelniający.
+ unsupported_response_type: Serwer uwierzytelniający nie wspiera tego typu odpowiedzi.
+ flash:
+ applications:
+ create:
+ notice: Aplikacja utworzona.
+ destroy:
+ notice: Aplikacja usunięta.
+ update:
+ notice: Aplikacja zaktualizowana.
+ authorized_applications:
+ destroy:
+ notice: Aplikacja unieważniona.
+ layouts:
+ admin:
+ nav:
+ applications: Aplikacje
+ oauth2_provider: Dostawca OAuth2
+ application:
+ title: Uwierzytelnienie OAuth jest wymagane
+ scopes:
+ follow: śledzenie, blokowanie, usuwanie blokady, anulowanie śledzenia kont
+ read: dostęp do odczytu danych konta
+ write: publikowanie postów w Twoim imieniu
diff --git a/config/locales/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml
index 90224c73..f674098a 100644
--- a/config/locales/doorkeeper.zh-HK.yml
+++ b/config/locales/doorkeeper.zh-HK.yml
@@ -10,7 +10,7 @@ zh-HK:
doorkeeper/application:
attributes:
redirect_uri:
- fragment_present: 'URI 不可包含 "#fragment" 部份'
+ fragment_present: URI 不可包含 "#fragment" 部份
invalid_uri: 必需有正確的 URI.
relative_uri: 必需為絕對 URI.
secured_uri: 必需使用有 HTTPS/SSL 加密的 URI.
@@ -45,7 +45,7 @@ zh-HK:
callback_urls: 回傳網址
scopes: 權限範圍
secret: 密碼
- title: '應用程式︰ %{name}'
+ title: 應用程式︰ %{name}
authorizations:
buttons:
authorize: 批准
@@ -72,9 +72,12 @@ zh-HK:
errors:
messages:
access_denied: 資源擁有者或授權伺服器不接受請求。
- credential_flow_not_configured: 資源擁有者密碼認證程序 (Resource Owner Password Credentials flow) 失敗,原因是 Doorkeeper.configure.resource_owner_from_credentials 沒有設定。
- invalid_client: 用戶程式認證 (Client authentication) 失敗,原因是用戶程式未有登記、沒有指定用戶程式 (client)、或者使用了不支援的認證方法 (method)。
- invalid_grant: 授權申請 (authorization grant) 不正確、過期、已被取消,或者無法對應授權請求 (authorization request) 內的轉接 URI,或者屬於別的用戶程式。
+ credential_flow_not_configured: 資源擁有者密碼認證程序 (Resource Owner Password Credentials
+ flow) 失敗,原因是 Doorkeeper.configure.resource_owner_from_credentials 沒有設定。
+ invalid_client: 用戶程式認證 (Client authentication) 失敗,原因是用戶程式未有登記、沒有指定用戶程式 (client)、或者使用了不支援的認證方法
+ (method)。
+ invalid_grant: 授權申請 (authorization grant) 不正確、過期、已被取消,或者無法對應授權請求 (authorization
+ request) 內的轉接 URI,或者屬於別的用戶程式。
invalid_redirect_uri: 不正確的轉接網址。
invalid_request: 請求缺少了必要的參數、包含了不支援的參數、或者其他輸入錯誤。
invalid_resource_owner: 資源擁有者的登入資訊錯誤、或者無法找到該資源擁有者。
@@ -83,7 +86,8 @@ zh-HK:
expired: access token 已經過期
revoked: access token 已被取消
unknown: access token 不正確
- resource_owner_authenticator_not_configured: 無法找到資源擁有者,原因是 Doorkeeper.configure.resource_owner_authenticator 沒有設定。
+ resource_owner_authenticator_not_configured: 無法找到資源擁有者,原因是 Doorkeeper.configure.resource_owner_authenticator
+ 沒有設定。
server_error: 認證伺服器遇上未知狀況,令請求無法通過。
temporarily_unavailable: 認證伺服器由於臨時負荷過重或者維護,目前未能處理請求。
unauthorized_client: 用戶程式無權用此方法 (method) 請行這個請求。
diff --git a/config/locales/doorkeeper.zh-TW.yml b/config/locales/doorkeeper.zh-TW.yml
new file mode 100644
index 00000000..7e8c78cd
--- /dev/null
+++ b/config/locales/doorkeeper.zh-TW.yml
@@ -0,0 +1,113 @@
+---
+zh-TW:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: 名稱
+ redirect_uri: 重新導向 URI
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: 'URI 不可包含 "#fragment" 部份'
+ invalid_uri: 必需有正確的 URI.
+ relative_uri: 必需為絕對 URI.
+ secured_uri: 必需使用有 HTTPS/SSL 加密的 URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: 授權
+ cancel: 取消
+ destroy: 移除
+ edit: 編輯
+ submit: 送出
+ confirmations:
+ destroy: 您確定嗎?
+ edit:
+ title: 編輯應用程式
+ form:
+ error: 噢!請檢查表單錯誤訊息
+ help:
+ native_redirect_uri: 使用 %{native_redirect_uri} 作局部測試
+ redirect_uri: 每行輸入一個 URI
+ scopes: 請用半形空格分開權限範圍 (scope)。留空表示使用預設的權限範圍
+ index:
+ callback_url: 回傳網址
+ name: 名稱
+ new: 新增應用程式
+ title: 您的應用程式
+ new:
+ title: 新增應用程式
+ show:
+ actions: 動作
+ application_id: 應用程式 ID
+ callback_urls: 回傳網址
+ scopes: 權限範圍
+ secret: 密碼
+ title: '應用程式︰ %{name}'
+ authorizations:
+ buttons:
+ authorize: 允許
+ deny: 拒絕
+ error:
+ title: 發生錯誤
+ new:
+ able_to: 要求取得權限
+ prompt: 應用程式 %{client_name} 要求取得您帳號的部份權限
+ title: 需要授權
+ show:
+ title: 授權代碼
+ authorized_applications:
+ buttons:
+ revoke: 取消授權
+ confirmations:
+ revoke: 您確定要取消授權?
+ index:
+ application: 應用程式
+ created_at: 授權於
+ date_format: "%Y-%m-%d %H:%M:%S"
+ scopes: 權限範圍
+ title: 已獲您授權的應用程式
+ errors:
+ messages:
+ access_denied: 資源擁有者或授權伺服器不接受請求。
+ credential_flow_not_configured: 資源擁有者密碼認證程序失敗,由於 Doorkeeper.configure.resource_owner_from_credentials 沒有設定。
+ invalid_client: 客戶端驗證失敗,可能是未知的客戶端程式、未包含客戶端驗證、或使用了不支援的認證方法。
+ invalid_grant: 授權申請不正確、逾期、已被取消、與授權請求內的重新導向 URI 不符、或屬於別的客戶端程式。
+ invalid_redirect_uri: 不正確的重新導向網址。
+ invalid_request: 請求缺少必要的參數、包含不支援的參數、或其他輸入錯誤。
+ invalid_resource_owner: 資源擁有者的登入資訊錯誤、或無法找到該資源擁有者。
+ invalid_scope: 請求的權限範圍不正確、未有定義、或輸入錯誤。
+ invalid_token:
+ expired: access token 已過期
+ revoked: access token 已被取消
+ unknown: access token 不正確
+ resource_owner_authenticator_not_configured: 無法找到資源擁有者,由於 Doorkeeper.configure.resource_owner_authenticator 沒有設定。
+ server_error: 認證伺服器發生未知錯誤。
+ temporarily_unavailable: 認證伺服器暫時無法使用。
+ unauthorized_client: 客戶端程式無權使用此方法進行請求。
+ unsupported_grant_type: 授權伺服器不支援這個授權類型。
+ unsupported_response_type: 授權伺服器不支援這個回應類型。
+ flash:
+ applications:
+ create:
+ notice: 已新增應用程式。
+ destroy:
+ notice: 已刪除應用程式。
+ update:
+ notice: 已更新應用程式。
+ authorized_applications:
+ destroy:
+ notice: 已取消應用程式授權。
+ layouts:
+ admin:
+ nav:
+ applications: 應用程式
+ oauth2_provider: OAuth2 供應者
+ application:
+ title: 需要 OAuth 授權
+ scopes:
+ follow: 關注、封鎖、解除封鎖及取消關注帳號
+ read: 讀取您的帳號資料
+ write: 以您的名義發佈文章
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 9c270892..474de398 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -290,13 +290,16 @@ en:
disable: Disable
enable: Enable
enabled_success: Two-factor authentication successfully enabled
+ generate_recovery_codes: Generate Recovery Codes
instructions_html: "
Scan this QR code into Google Authenticator or a similiar TOTP app on your phone. From now on, that app will generate tokens that you will have to enter when logging in."
+ lost_recovery_codes: Recovery codes allow you to regain access to your account if you lose your phone. If you've lost your recovery codes, you can regenerate them here. Your old recovery codes will be invalidated.
manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
+ recovery_codes: Recovery Codes
+ recovery_codes_regenerated: Recovery codes successfully regenerated
+ recovery_instructions: If you ever lose access to your phone, you can use one of the recovery codes below to regain access to your account. Keep the recovery codes safe, for example by printing them and storing them with other important documents.
setup: Set up
warning: If you cannot configure an authenticator app right now, you should click "disable" or you won't be able to login.
wrong_code: The entered code was invalid! Are server time and device time correct?
users:
invalid_email: The e-mail address is invalid
invalid_otp_token: Invalid two-factor code
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 3b0181f6..a29fe17f 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -139,7 +139,7 @@ es:
import: Importar
preferences: Preferencias
settings: Ajustes
- two_factor_auth: Aute/ción. de dos factores
+ two_factor_auth: Autenticación de dos factores
statuses:
open_in_web: Abrir en web
over_character_limit: Límite de caracteres de %{max} superado
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 9e590f10..754eabb9 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -293,5 +293,3 @@ fr:
users:
invalid_email: L'adresse courriel est invalide
invalid_otp_token: Le code d'authentification à deux facteurs est invalide
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/hr.yml b/config/locales/hr.yml
index 64b1ae2c..f6e6ed44 100644
--- a/config/locales/hr.yml
+++ b/config/locales/hr.yml
@@ -161,5 +161,3 @@ hr:
users:
invalid_email: E-mail adresa nije valjana
invalid_otp_token: Nevaljani dvo-faktorski kod
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index c1df73b7..3c7b342e 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -34,12 +34,89 @@ ja:
followers: フォロワー
following: フォロー中
nothing_here: 何もありません
- people_followed_by: "%{name} さんをフォロー中のアカウント"
- people_who_follow: "%{name} さんがフォロー中のアカウント"
+ people_followed_by: "%{name} さんがフォロー中のアカウント"
+ people_who_follow: "%{name} さんをフォロー中のアカウント"
posts: 投稿
remote_follow: リモートフォロー
unfollow: フォロー解除
admin:
+ accounts:
+ are_you_sure: 本当に実行しますか?
+ display_name: 表示名
+ domain: ドメイン
+ edit: 編集
+ email: E-mail
+ feed_url: Feed URL
+ followers: フォロワー数
+ follows: フォロー数
+ location:
+ all: すべて
+ local: ローカル
+ remote: リモート
+ title: ロケーション
+ media_attachments: 添付されたメディア
+ moderation:
+ all: すべて
+ silenced: サイレンス中
+ suspended: 停止中
+ title: モデレーション
+ most_recent_activity: 直近の活動
+ most_recent_ip: 直近のIP
+ not_subscribed: 購読していない
+ order:
+ alphabetic: アルファベット順
+ most_recent: 直近の活動順
+ title: 順序
+ perform_full_suspension: 完全に活動停止させる
+ profile_url: プロフィールURL
+ public: パブリック
+ push_subscription_expires: PuSH購読期限切れ
+ salmon_url: Salmon URL
+ silence: サイレンス
+ statuses: トゥート数
+ title: アカウント
+ undo_silenced: サイレンスから戻す
+ undo_suspension: 停止から戻す
+ username: ユーザー名
+ web: Web
+ domain_block:
+ add_new: 新規追加
+ domain: ドメイン
+ new:
+ create: ブロックを作成
+ hint: ドメインブロックはデータベース中のアカウント項目の作成を妨げませんが、遡って自動的に指定されたモデレーションをそれらのアカウントに適用します。
+ severity:
+ desc_html: "
サイレンスはアカウントのトゥートをフォローしていない人から隠します。
停止はそのアカウントのコンテンツ、メディア、プロフィールデータをすべて削除します。"
+ silence: サイレンス
+ suspend: 停止
+ title: 新規ドメインブロック
+ severity: 深刻度
+ title: ドメインブロック
+ pubsubhubbub:
+ callback_url: コールバックURL
+ confirmed: 確認済み
+ expires_in: 期限
+ last_delivery: 最終配送
+ title: PubSubHubbub
+ topic: トピック
+ reports:
+ comment:
+ label: コメント
+ none: なし
+ delete: 削除
+ id: ID
+ mark_as_resolved: 解決済みとしてマーク
+ report: 'レポート#%{id}'
+ reported_account: 報告対象アカウント
+ reported_by: 報告者
+ resolved: 解決済み
+ silence_account: アカウントをサイレンス
+ status: ステータス
+ suspend_account: アカウントを停止
+ target: ターゲット
+ title: レポート
+ unresolved: 未解決
+ view: 表示
settings:
click_to_edit: クリックして編集
contact_information:
@@ -47,19 +124,23 @@ ja:
label: 連絡先情報
username: ユーザー名を入力
registrations:
+ closed_message:
+ desc_html: 新規登録を停止しているときにフロントページに表示されます。
HTMLタグが利用可能です。
+ title: 新規登録停止時のメッセージ
open:
disabled: 無効
enabled: 有効
title: 新規登録を受け付ける
setting: 設定
site_description:
- desc_html: トップページへの表示と meta タグに使用されます。
HTMLタグ、特に
<a>
and
<em>
が利用可能です。
+ desc_html: トップページへの表示と meta タグに使用されます。
HTMLタグ、特に
<a>
と
<em>
が利用可能です。
title: サイトの説明文
site_description_extended:
desc_html: インスタンスについてのページに表示されます。
HTMLタグが利用可能です。
title: サイトの詳細な説明
site_title: サイトのタイトル
title: サイト設定
+ title: 管理
application_mailer:
settings: 'メール設定の変更: %{link}'
signature: Mastodon %{instance} インスタンスからの通知
@@ -157,7 +238,7 @@ ja:
prev: 前
truncate: "…"
remote_follow:
- acct: フォローしたい人の ユーザー名@ドメイン を入力してください
+ acct: あなたの ユーザー名@ドメイン を入力してください
missing_resource: リダイレクト先が見つかりませんでした
proceed: フォローする
prompt: 'フォローしようとしています:'
@@ -217,5 +298,3 @@ ja:
users:
invalid_email: メールアドレスが無効です
invalid_otp_token: 二段階認証コードが間違っています
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 22fed228..0af0a99e 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -1,11 +1,11 @@
---
nl:
about:
- about_mastodon: Mastodon is een
vrije, gratis, open-source sociaal netwerk. E
gedecentraliseerd alternatief voor commerciële platforms, het voorkomt de risico's van een enkel bedrijf dat jouw communicatie monopoliseert. Kies een server die je vertrouwt — welke je ook kiest, je kunt met iedere ander communiceren. Iedereen kan een eigen Mastodon server draaien en naadloos deelnemen in het
sociale netwerk.
+ about_mastodon: Mastodon is een
vrij, gratis en open-source sociaal netwerk. Een
gedecentraliseerd alternatief voor commerciële platforms, het voorkomt de risico's van een enkel bedrijf dat jouw communicatie monopoliseert. Kies een server die je vertrouwt — welke je ook kiest, je kunt met iedere ander communiceren. Iedereen kan een eigen Mastodon-server draaien en naadloos deelnemen in het
sociale netwerk.
about_this: Over deze server
apps: Apps
- business_email: 'Zakelijke e-mailadres:'
- closed_registrations: Registrateren op deze server is momenteel uitgeschakeld.
+ business_email: 'Zakelijk e-mailadres:'
+ closed_registrations: Registreren op deze server is momenteel uitgeschakeld.
contact: Contact
description_headline: Wat is %{domain}?
domain_count_after: andere servers
@@ -16,22 +16,22 @@ nl:
characters: 500 tekens per bericht
chronology: Tijdlijnen zijn chronologisch
ethics: 'Ethisch design: geen ads, geen spionage'
- gifv: GIFV sets en korte video's
- privacy: Granulaire, privacy instellingen per bericht
+ gifv: GIFV-sets en korte video's
+ privacy: Granulaire privacyinstellingen per bericht
public: Openbare tijdlijnen
- features_headline: Wat maak Mastodon anders
+ features_headline: Wat maakt Mastodon anders
get_started: Beginnen
links: Links
other_instances: Andere servers
source_code: Source code
status_count_after: statussen
status_count_before: Wie schreef
- terms: Voorw
+ terms: Voorwaarden
user_count_after: gebruikers
user_count_before: Thuis naar
accounts:
- follow: Volg
- followers: Volgens
+ follow: Volgen
+ followers: Volgers
following: Volgend
nothing_here: Hier is niets!
people_followed_by: Mensen die %{name} volgt
@@ -40,23 +40,23 @@ nl:
remote_follow: Externe volg
unfollow: Ontvolgen
application_mailer:
- settings: 'Wijzigen e-mailvoorkeuren: %{link}'
- signature: Mastodon meldingen van %{instance}
+ settings: 'E-mailvoorkeuren wijzigen: %{link}'
+ signature: Mastodon-meldingen van %{instance}
view: 'Bekijk:'
applications:
invalid_url: De opgegevens URL is ongeldig
auth:
change_password: Inloggegevens
- didnt_get_confirmation: Ontving je geen bevestigingsinstructies?
+ didnt_get_confirmation: Geen bevestigingsinstructies ontvangen?
forgot_password: Wachtwoord vergeten?
- login: Inloggen
- logout: Uitloggen
+ login: Aanmelden
+ logout: Afmelden
register: Registreren
- resend_confirmation: Herstuur de bevestigingsinstructies
- reset_password: Herstel wachtwoord
- set_new_password: Instellen nieuw wachtwoord
+ resend_confirmation: Verstuur de bevestigingsinstructies nogmaals
+ reset_password: Wachtwoord opnieuw instellen
+ set_new_password: Nieuw wachtwoord instellen
authorize_follow:
- error: Helaas, er was een fout bij het opzoeken van het externe account
+ error: Helaas, er is een fout opgetreden bij het opzoeken van de externe account
follow: Volgen
prompt_html: 'Je (
%{self}) hebt volgen aangevraagd:'
title: Volg %{acct}
@@ -78,22 +78,22 @@ nl:
blocks: Je blokkeert
csv: CSV
follows: Je volgt
- storage: Media-opslag
+ storage: Mediaopslag
generic:
changes_saved_msg: Wijzigingen succesvol opgeslagen!
- powered_by: powered by %{link}
+ powered_by: mogelijk gemaakt door %{link}
save_changes: Wijziginen opslaan
validation_errors:
one: Er is iets niet helemaal goed! Bekijk onderstaande fout
other: Er is iets niet helemaal goed! Bekijk onderstaande %{count} fouten
imports:
preface: Je kunt bepaalde gegevens, zoals de mensen die je volgt of blokkeert, importeren voor je account op deze server, als ze zijn geëxporteerd op een andere server.
- success: Je gegevens zijn succesvol ge-upload en wordt binnenkort verwerkt
+ success: Je gegevens zijn succesvol geüpload en worden binnenkort verwerkt
types:
- blocking: Blokkadelijst
+ blocking: Blokkeerlijst
following: Volglijst
upload: Uploaden
- landing_strip_html:
%{name} is een gebruiker op
%{domain}. Je kunt deze volgen of ermee interacteren als je ergens in deze fediverse een account hebt. Als he dat niet hebt, kun je je
hier aanmelden.
+ landing_strip_html:
%{name} is een gebruiker op
%{domain}. Je kunt deze volgen of ermee interacteren als je ergens in deze fediverse een account hebt. Als je dat niet hebt, kun je je
hier aanmelden.
notification_mailer:
digest:
body: 'Hier is een korte samenvatting van wat je hebt gemist op %{instance} sinds je laatste bezoek op %{since}:'
@@ -105,17 +105,17 @@ nl:
one: "1 nieuwe melding sinds je laatste bezoek \U0001F418"
other: "%{count} nieuwe meldingen sinds je laatste bezoek \U0001F418"
favourite:
- body: 'Je status werd als favoriet gemarkeerd door %{name}:'
- subject: "%{name} markeerde je status als favouriet"
+ body: 'Je status werd door %{name} als favoriet gemarkeerd:'
+ subject: "%{name} markeerde je status als favoriet"
follow:
body: "%{name} volgt je nu!"
subject: "%{name} volgt je nu"
follow_request:
- body: "%{name} wil je graag volgend"
+ body: "%{name} wil je graag volgen"
subject: 'Volgen in afwachting: %{name}'
mention:
- body: 'Je werd door %{name} vermeld in:'
- subject: Je werd vermeld door %{name}
+ body: 'Je bent door %{name} vermeld in:'
+ subject: Je bent vermeld door %{name}
reblog:
body: 'Je status werd geboost door %{name}:'
subject: "%{name} booste je status"
@@ -124,24 +124,24 @@ nl:
prev: Vorige
remote_follow:
acct: Geef je gebruikersnaam@domein op waarvandaan je wilt volgen
- missing_resource: Kon geen de vereiste doorverwijszings-URL voor je account niet vinden
+ missing_resource: Kon vereiste doorverwijzings-URL voor je account niet vinden
proceed: Ga door om te volgen
prompt: 'Je gaat volgen:'
settings:
authorized_apps: Geautoriseerde
back: Terug naar Mastodon
- edit_profile: Bewerk profiel
+ edit_profile: Profiel bewerken
export: Gegevensexport
import: Import
preferences: Voorkeuren
settings: Instellingen
- two_factor_auth: Twe-factor authenticatie
+ two_factor_auth: Twee-factorauthenticatie
statuses:
open_in_web: Openen in web
over_character_limit: Tekenlimiet van %{max} overschreden
show_more: Toon meer
visibilities:
- private: Toon alleen aan volgers
+ private: Alleen aan volgers tonen
public: Openbaar
unlisted: Openbaar, maar niet tonen op openbare tijdlijn
stream_entries:
@@ -152,14 +152,12 @@ nl:
formats:
default: "%b %d, %J, %U:%M"
two_factor_auth:
- description_html: Als je
twee-factor authenticatie instelt, kun je alleen inloggen als je je mobiele telefoon bij je hebt, waarmee je de in te voeren tokens genereert.
+ description_html: Als je
twee-factorauthenticatie instelt, kun je alleen aanmelden als je je mobiele telefoon bij je hebt, waarmee je de in te voeren tokens genereert.
disable: Uitschakelen
enable: Inschakelen
- instructions_html: "
Scan deze QR-code in Google Authenticator of een soortgelijke app op je mobiele telefoon. Van nu af aan creëert deze app tokens die je bij inloggen moet invoeren."
+ instructions_html: "
Scan deze QR-code in Google Authenticator of een soortgelijke app op je mobiele telefoon. Van nu af aan creëert deze app tokens die je bij aanmelden moet invoeren."
plaintext_secret_html: 'Gewone-tekst geheim:
%{secret}'
- warning: Als je nu geen authenticator app kunt installeren, moet je "Uitschakelen" kiezen of je kunt niet meer inloggen.
+ warning: Als je nu geen authenticator-app kunt installeren, moet je "Uitschakelen" kiezen of je kunt niet meer aanmelden.
users:
invalid_email: Het e-mailadres is ongeldig
- invalid_otp_token: Ongeldige twe-factor code
- will_paginate:
- page_gap: "…"
+ invalid_otp_token: Ongeldige twee-factorcode
diff --git a/config/locales/no.yml b/config/locales/no.yml
index d13d90c2..d382db92 100644
--- a/config/locales/no.yml
+++ b/config/locales/no.yml
@@ -17,7 +17,7 @@
ethics: 'Etisk design: Ingen reklame, ingen sporing'
gifv: Støtte for GIFV og korte videoer
privacy: Finmaskede personvernsinnstillinger
- public: Forente tidslinjer
+ public: Felles tidslinjer
features_headline: Hva skiller Mastodon fra andre sosiale nettverk
get_started: Kom i gang
links: Lenker
@@ -40,7 +40,7 @@
unfollow: Avfølg
application_mailer:
settings: 'Endre foretrukne epost innstillinger: %{link}'
- signature: Mastodon notiser fra %{instance}
+ signature: Mastodon-notiser fra %{instance}
view: 'Se:'
applications:
invalid_url: Den oppgitte URLen er ugyldig
@@ -55,44 +55,44 @@
reset_password: Nullstill passord
set_new_password: Sett nytt passord
authorize_follow:
- error: Uheldigvis så skjedde det en feil når vi prøvde å få tak i en konto fra en annen instans.
+ error: Uheldigvis så skjedde det en feil da vi prøvde å få tak i en bruker fra en annen instans.
follow: Følg
prompt_html: 'Du (
%{self}) har spurt om å følge:'
title: Følg %{acct}
datetime:
distance_in_words:
- about_x_hours: "%{count}t"
- about_x_months: "%{count}m"
- about_x_years: "%{count}å"
- almost_x_years: "%{count}å"
- half_a_minute: Nylig
- less_than_x_minutes: "%{count}min"
- less_than_x_seconds: Nylig
- over_x_years: "%{count}å"
- x_days: "%{count}d"
- x_minutes: "%{count}min"
- x_months: "%{count}mo"
- x_seconds: "%{count}s"
+ about_x_hours: "%{count} timer"
+ about_x_months: "%{count} mnd"
+ about_x_years: "%{count} år"
+ almost_x_years: "%{count} år"
+ half_a_minute: Nettopp
+ less_than_x_minutes: "%{count} min"
+ less_than_x_seconds: Nettopp
+ over_x_years: "%{count} år"
+ x_days: "%{count} dager"
+ x_minutes: "%{count} min"
+ x_months: "%{count} mnd"
+ x_seconds: "%{count} sek"
exports:
blocks: Du blokkerer
csv: CSV
follows: Du følger
- storage: Media lagring
+ storage: Medialagring
generic:
changes_saved_msg: Vellykket lagring av endringer!
powered_by: drevet av %{link}
save_changes: Lagre endringer
validation_errors:
- one: Noe er ikke helt riktig ennå. Vær snill å se etter en gang til
+ one: Noe er ikke helt riktig ennå. Vennligst se etter en gang til
other: Noe er ikke helt riktig ennå. Det er ennå %{count} feil å rette på
imports:
- preface: Du kan importere data om mennesker du følger eller blokkerer inn til kontoen din på denne instansen, fra filer opprettet av eksporter fra andre instanser.
- success: Din data ble mottatt og vil bli prosessert så fort som mulig.
+ preface: Du kan importere data om brukere du følger eller blokkerer til kontoen din på denne instansen med eksportfiler fra andre instanser.
+ success: Din data ble mottatt og vil bli behandlet så fort som mulig.
types:
blocking: Blokkeringsliste
following: Følgeliste
upload: Opplastning
- landing_strip_html:
%{name} er en bruker på
%{domain}. Du kan følge dem eller interagere med dem hvis du har en konto hvor som helst i fediverset. Hvis du ikke har en konto så kan du
registrere deg her.
+ landing_strip_html:
%{name} er en bruker på
%{domain}. Du kan følge dem eller kommunisere med dem hvis du har en konto hvor som helst i fediverset. Hvis du ikke har en konto så kan du
registrere deg her.
notification_mailer:
digest:
body: 'Her er en kort oppsummering av hva du har gått glipp av på %{instance} siden du logget deg inn sist den %{since}:'
@@ -110,14 +110,14 @@
body: "%{name} følger deg!"
subject: "%{name} følger deg"
follow_request:
- body: "%{name} har spurt om å få lov til å følge deg"
- subject: 'Ventende følger: %{name}'
+ body: "%{name} har bedt om lov til å følge deg"
+ subject: 'Ventende følginger: %{name}'
mention:
body: 'Du ble nevnt av %{name} i:'
subject: Du ble nevnt av %{name}
reblog:
- body: 'Din status ble reblogget av %{name}:'
- subject: "%{name} reblogget din status"
+ body: 'Din status ble fremhevd av %{name}:'
+ subject: "%{name} fremhevde din status"
pagination:
next: Neste
prev: Forrige
@@ -125,38 +125,38 @@
acct: Tast inn brukernavn@domene som du vil følge fra
missing_resource: Kunne ikke finne URLen for din konto
proceed: Fortsett med følging
- prompt: 'Du kommer til å følge:'
+ prompt: 'Du vil følge:'
settings:
authorized_apps: Autoriserte applikasjoner
back: Tilbake til Mastodon
edit_profile: Endre profil
- export: Data eksport
- import: Importer
- preferences: Foretrukne valg
+ export: Dataeksport
+ import: Importér
+ preferences: Preferanser
settings: Innstillinger
- two_factor_auth: To-faktor autentisering
+ two_factor_auth: Tofaktorautentisering
statuses:
open_in_web: Åpne i nettleser
- over_character_limit: tegngrense på %{max} overskredet
+ over_character_limit: grense på %{max} tegn overskredet
show_more: Vis mer
visibilities:
private: Vis kun til følgere
public: Offentlig
- unlisted: Offentlig, men vis ikke på forent tidslinje
+ unlisted: Offentlig, men vis ikke på felles tidslinje
stream_entries:
click_to_show: Klikk for å vise
- reblogged: reblogget
- sensitive_content: Sensitivt innhold
+ reblogged: fremhevde
+ sensitive_content: Følsomt innhold
time:
formats:
default: "%d, %b %Y, %H:%M"
two_factor_auth:
- description_html: Hvis du skru på
tofaktor autentisering vil innlogging kreve at du har telefonen din, som vil generere koder som du må taste inn.
+ description_html: Hvis du skrur på
tofaktorautentisering må du ha din telefon for å logge inn. Denne vil generere koder som du må taste inn.
disable: Skru av
enable: Skru på
- instructions_html: "
Scan denne QR-koden i Google Authenticator eller en lignende app på telefonen din. Fra nå av så vil denne applikasjonen generere koder for deg som skal brukes under innlogging"
+ instructions_html: "
Scan denne QR-koden i Google Authenticator eller en lignende app på telefonen din. Fra nå av vil denne applikasjonen generere koder for deg som skal brukes under innlogging"
plaintext_secret_html: 'Plain-text secret:
%{secret}'
- warning: Hvis du ikke kan konfigurere en autentikatorapp nå, så bør du trykke "Skru av"; ellers vil du ikke kunne logge inn.
+ warning: Hvis du ikke kan konfigurere en autentiseringsapp nå bør du trykke "Skru av"; ellers vil du ikke kunne logge inn.
users:
- invalid_email: E-post addressen er ugyldig
- invalid_otp_token: Ugyldig two-faktor kode
+ invalid_email: E-postaddressen er ugyldig
+ invalid_otp_token: Ugyldig tofaktorkode
diff --git a/config/locales/pl.yml b/config/locales/pl.yml
new file mode 100644
index 00000000..3c4c98bc
--- /dev/null
+++ b/config/locales/pl.yml
@@ -0,0 +1,164 @@
+---
+pl:
+ about:
+ about_mastodon: Mastodon jest
wolną i otwartą siecią społecznościową,
zdecentralizowaną alternatywą dla zamkniętych, komercyjnych platform. Pozwala uniknąć ryzyka monopolizacji Twojej komunikacji przez jedną korporację. Wybierz serwer, któremu ufasz — nie ograniczy to Twoich możliwości komunikacji z innymi osobami w sieci. Każdy może też uruchomić własną instancję Mastodona i dołączyć do reszty tej
sieci społecznościowej.
+ about_this: O tej instancji
+ apps: Aplikacje
+ business_email: 'Służbowy adres e-mail:'
+ contact: Kontakt
+ description_headline: Czym jest %{domain}?
+ domain_count_after: inne instancje
+ domain_count_before: Połączone z
+ features:
+ api: Otwarte API dla aplikacji i usług
+ blocks: Rozbudowane narzędzia blokowania i wyciszania
+ characters: 500 znaków na wpis
+ chronology: Chronologiczny porządek wyświetlania
+ ethics: 'Etyczne założenia: bez reklam, bez śledzenia'
+ gifv: obsługa GIFV i krótkich wideo
+ privacy: Precyzyjne ustawienia widoczności poszczególnych postów
+ public: Publiczne osie czasu
+ features_headline: Co wyróżnia Mastodona
+ get_started: Rozpocznijmy!
+ links: Odnośniki
+ other_instances: Inne instancje
+ source_code: Kod źródłowy
+ status_count_after: wpisów
+ status_count_before: Kto jest autorem
+ terms: Regulamin
+ user_count_after: tootujących
+ user_count_before: Dom dla
+ accounts:
+ follow: Śledź
+ followers: Śledzących
+ following: Śledzi
+ nothing_here: Niczego tu nie ma!
+ people_followed_by: Konta śledzone przez %{name}
+ people_who_follow: Osoby, które śledzą konto %{name}
+ posts: Wpisy
+ remote_follow: Zdalne śledzenie
+ unfollow: Przestań śledzić
+ application_mailer:
+ settings: 'Zmień ustawienia powiadamiania: %{link}'
+ signature: Powiadomienie Mastodona, wysłane przez %{instance}
+ view: 'Zobacz:'
+ applications:
+ invalid_url: Ten URL jest nieprawidłowy
+ auth:
+ change_password: Uwierzytelnienie
+ didnt_get_confirmation: Nie otrzymałeś instrukcji weryfikacji?
+ forgot_password: Zapomniane hasło
+ login: Zaloguj się
+ logout: Wyloguj się
+ register: Rejestracja
+ resend_confirmation: Ponownie prześlij instrukcje weryfikacji
+ reset_password: Zresetuj hasło
+ set_new_password: Ustaw nowe hasło
+ authorize_follow:
+ error: Niestety, podczas sprawdzania zdalnego konta wystąpił błąd
+ follow: Śledź
+ prompt_html: 'Ty (
%{self}) chcesz śledzić:'
+ title: Śledź %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}h"
+ about_x_months: "%{count} miesięcy"
+ about_x_years: "%{count} lat"
+ almost_x_years: "%{count} lat"
+ half_a_minute: Przed chwilą
+ less_than_x_minutes: "%{count}min"
+ less_than_x_seconds: Przed chwilą
+ over_x_years: "%{count} lat"
+ x_days: "%{count} dni"
+ x_minutes: "%{count}min"
+ x_months: "%{count} miesięcy"
+ x_seconds: "%{count}s"
+ exports:
+ blocks: Blokujesz
+ csv: CSV
+ follows: Śledzisz
+ storage: Media storage
+ generic:
+ changes_saved_msg: Ustawienia zapisane!
+ powered_by: uruchomione na %{link}
+ save_changes: Zapisz zmiany
+ validation_errors:
+ one: Coś jest wciąż nie tak! Przyjrzyj się błędowi poniżej
+ other: Coś jest wciąż nie tak! Przejrzyj błędy (%{count}) poniżej
+ imports:
+ preface: Możesz zaimportować pewne dane (jak dane kont, które śledzisz lub blokujesz) do swojego konta na tym serwerze, korzystająć z danych wyeksportowanych z innego serwera.
+ success: Twoje dane zostały załadowane i zostaną niebawem przetworzone
+ types:
+ blocking: Lista blokowanych
+ following: Lista śledzonych
+ upload: Załaduj
+ landing_strip_html:
%{name} ma konto na
%{domain}. Możesz je śledzić i wejść z nim w interakcję jeśli masz konto gdziekolwiek w Fediwersie. Jeśli jeszcze go nie masz, możesz
stworzyć konto.
+ notification_mailer:
+ digest:
+ body: 'Oto krótkie podsumowanie co Cię ominęło na %{instance} od Twojej ostatniej wizyty (%{since}):'
+ mention: "%{name} wspomniał o Tobie w:"
+ new_followers_summary:
+ one: Śledzi Cię nowa osoba! Gratulacje!
+ other: Kilka (%{count}) nowych osób Cię śledzi! Wspaniale!
+ subject:
+ one: "1 nowe powiadomienie od Twojej ostatniej wizyty \U0001F418"
+ other: "%{count} nowych powiadomień od Twojej ostatniej wizyty \U0001F418"
+ favourite:
+ body: 'Twój wpis został polubiony przez %{name}:'
+ subject: "%{name} lubi Twój wpis"
+ follow:
+ body: "%{name} Cię śledzi!"
+ subject: "%{name} Cię śledzi"
+ follow_request:
+ body: "%{name} poprosił o możliwość śledzenia Cię"
+ subject: 'Prośba o możliwość śledzenia: %{name}'
+ mention:
+ body: '%{name} wspomniał Cię w:'
+ subject: '%{name} Cię wspomniał'
+ reblog:
+ body: 'Twój wpis został podbity przez %{name}:'
+ subject: "Twój wpis został podbity przez %{name}"
+ pagination:
+ next: Następna
+ prev: Poprzednia
+ remote_follow:
+ acct: Podaj swój adres (nazwa@domena), z którego chcesz śledzić
+ missing_resource: Nie udało się znaleźć adresu przekierowania z Twojej domeny
+ proceed: Śledź
+ prompt: 'Śledzony będzie:'
+ settings:
+ authorized_apps: Uwierzytelnione aplikacje
+ back: Powrót do Mastodona
+ edit_profile: Edytuj profil
+ export: Exportuj dane
+ import: Importuj dane
+ preferences: Preferencje
+ settings: Ustawienia
+ two_factor_auth: Uwierzytelnianie dwuetapowe
+ statuses:
+ open_in_web: Otwórz w przeglądarce
+ over_character_limit: limit %{max} znaków przekroczony
+ show_more: Pokaż więcej
+ visibilities:
+ private: Tylko dla śledzących
+ public: Publiczny
+ unlisted: Publiczny, ale nie wyświetlaj na publicznych osiach czasu
+ stream_entries:
+ click_to_show: Kliknij aby pokazać
+ reblogged: podbity
+ sensitive_content: Wrażliwa treść
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ description_html: Jeśli włączysz
uwierzytelnianie dwustopniowe, logowanie się będzie wymagało podania tokenu wyświetlonego na Twoim telefone.
+ disable: Wyłącz
+ enable: Włącz
+ instructions_html: "
Zeskanuj ten kod QR na swoim urządzeniu za pomocą Google Authenticator, FreeOTP lub podobnej aplikacji. Od teraz będzie ona generowała kody wymagane przy logowaniu."
+ plaintext_secret_html: 'Sekret:
%{secret}'
+ warning: Jeśli nie jesteś w stanie skonfigurować aplikacji uwierzytelniania dwustopniowego w tej chwili, wyłącz uwierzytelnianie dwustopniowe. W przeciwnym wypadku nie będziesz się w stanie zalogować!
+ users:
+ invalid_email: Adres e-mail jest niepoprawny
+ invalid_otp_token: Kod uwierzytelniający jest niepoprawny
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
index ca73dd45..4c8cb6a4 100644
--- a/config/locales/ru.yml
+++ b/config/locales/ru.yml
@@ -164,5 +164,3 @@ ru:
users:
invalid_email: Введенный e-mail неверен
invalid_otp_token: Введен неверный код
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 74649da5..c25407f2 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -10,6 +10,8 @@ en:
note: At most 160 characters
imports:
data: CSV file exported from another Mastodon instance
+ sessions:
+ otp: Enter the Two-factor code from your phone or use one of your recovery codes.
labels:
defaults:
avatar: Avatar
diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml
index 8c89a56e..7c501bb9 100644
--- a/config/locales/simple_form.eo.yml
+++ b/config/locales/simple_form.eo.yml
@@ -31,7 +31,7 @@ eo:
username: Uzantnomo
interactions:
must_be_follower: Kaŝi la sciigojn de homoj, kiuj ne sekvas vin
- must_be_following: Kaŝi la sciigojn de homoj, kiujn vi ne sekas
+ must_be_following: Kaŝi la sciigojn de homoj, kiujn vi ne sekvas
notification_emails:
digest: Sendi resumajn retpoŝt-mesaĝojn
favourite: Sendi retpoŝt-mesaĝon, kiam iu favoras mesaĝon de vi
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index 1c1afcdd..f260e600 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -21,19 +21,24 @@ es:
email: Dirección de correo electrónico
header: Img. cabecera
locale: Idioma
+ locked: Hacer privada esta cuenta
new_password: Nueva contraseña
note: Biografía
otp_attempt: Código de dos factores
password: Contraseña
+ setting_boost_modal: Mostrar ventana de confirmación antes de un Retoot
setting_default_privacy: Privacidad de publicaciones
+ severity: Severidad
type: Importar tipo
username: Nombre de usuario
interactions:
must_be_follower: Bloquear notificaciones de personas que no te siguen
must_be_following: Bloquear notificaciones de personas que no sigues
notification_emails:
+ digest: Enviar resumen de correos electrónicos
favourite: Enviar correo electrónico cuando alguien de a favorito en su publicación
follow: Enviar correo electrónico cuando alguien te siga
+ follow_request: Enviar correo electrónico cuando alguien solicita seguirte
mention: Enviar correo electrónico cuando alguien te mencione
reblog: Enviar correo electrónico cuando alguien comparta su publicación
'no': 'No'
diff --git a/config/locales/simple_form.no.yml b/config/locales/simple_form.no.yml
index 22df43e7..5a348b9e 100644
--- a/config/locales/simple_form.no.yml
+++ b/config/locales/simple_form.no.yml
@@ -6,41 +6,42 @@
avatar: PNG, GIF eller JPG. Maksimalt 2MB. Vil bli nedskalert til 120x120px
display_name: Maksimalt 30 tegn
header: PNG, GIF eller JPG. Maksimalt 2MB. Vil bli nedskalert til 700x335px
- locked: Krever at du manuelt godkjenner følgere og setter standard beskyttelse av poster til kun-følgere
+ locked: Krever at du manuelt godkjenner følgere og setter standardbeskyttelse av poster til kun-følgere
note: Maksimalt 160 tegn
imports:
- data: CSV fil eksportert fra en annen Mastodon instans
+ data: CSV-fil eksportert fra en annen Mastodon instans
labels:
defaults:
- avatar: Avatar
+ avatar: Profilbilde
confirm_new_password: Bekreft nytt passord
confirm_password: Bekreft passord
current_password: Nåværende passord
data: Data
display_name: Visningsnavn
- email: E-post adresse
- header: Header
+ email: E-postadresse
+ header: Overskrift
locale: Språk
locked: Endre konto til privat
new_password: Nytt passord
note: Biografi
- otp_attempt: To-faktor kode
+ otp_attempt: Tofaktorkode
password: Passord
setting_boost_modal: Vis bekreftelsesdialog før reblogging
setting_default_privacy: Leserettigheter for poster
type: Importeringstype
username: Brukernavn
+ setting_boost_modal: Vis bekreftelsesdialog før fremheving
interactions:
- must_be_follower: Blokker varslinger fra ikke-følgere
- must_be_following: Blokker varslinger fra folk du ikke følger
+ must_be_follower: Blokkér varslinger fra ikke-følgere
+ must_be_following: Blokkér varslinger fra brukere du ikke følger
notification_emails:
digest: Send oppsummeringseposter
favourite: Send e-post når noen liker din status
follow: Send e-post når noen følger deg
follow_request: Send e-post når noen ber om å få følge deg
mention: Send e-post når noen nevner deg
- reblog: Send e-post når noen reblogger din status
- 'no': Nei
+ reblog: Send e-post når noen fremhever din status
+ 'no': 'Nei'
required:
mark: "*"
text: påkrevd
diff --git a/config/locales/simple_form.pl.yml b/config/locales/simple_form.pl.yml
new file mode 100644
index 00000000..19121c4d
--- /dev/null
+++ b/config/locales/simple_form.pl.yml
@@ -0,0 +1,46 @@
+---
+pl:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF lub JPG. Najwyżej 2MB. Zostanie zmniejszone do 120x120px
+ display_name: Najwyżej 30 znaków
+ header: PNG, GIF lub JPG. Najwyżej 2MB. Zostanie zmniejszone do 700x335px
+ locked: Śledzenie Cię wymaga Twojego potwierdzenia; Twoje wpisy są domyślnie widoczne tylko dla śledzących Cię.
+ note: Najwyżej 160 znaków
+ imports:
+ data: Plik CSV wyeksportowany z innej instancji Mastodona
+ labels:
+ defaults:
+ avatar: Awatar
+ confirm_new_password: Potwierdź nowe hasło
+ confirm_password: Potwierdź hasło
+ current_password: Obecne hasło
+ data: Dane
+ display_name: Widoczna nazwa
+ email: Adres e-mail
+ header: Nagłówek
+ locale: Język
+ locked: Ustaw konto jako prywatne
+ new_password: Nowe hasło
+ note: Biogram
+ otp_attempt: Kod uwierzytelnienia dwustopniowego
+ password: Hasło
+ setting_default_privacy: Widoczność posta
+ type: Typ importu
+ username: Nazwa użytkownika
+ interactions:
+ must_be_follower: Zablokuj powiadomienia od osób, które Cię nie śledzą
+ must_be_following: Zablokuj powiadomienia od osób, których nie śledzisz
+ notification_emails:
+ digest: Wysyłaj podsumowania e-mailem
+ favourite: Powiadom mnie e-mailem gdy ktoś polubi mój status.
+ follow: Powiadom mnie e-mailem gdy ktoś zacznie mnie śledzić.
+ follow_request: Powiadom mnie e-mailem gdy ktoś poprosi o pozwolenie śledzenia mnie.
+ mention: Powiadom mnie e-mailem gdy ktoś mnie wspomni.
+ reblog: Powiadom mnie e-mailem gdy ktoś podbije mój status.
+ 'no': 'Nie'
+ required:
+ mark: "*"
+ text: pole wymagane
+ 'yes': 'Tak'
diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml
index 07d14522..71982f8f 100644
--- a/config/locales/simple_form.zh-HK.yml
+++ b/config/locales/simple_form.zh-HK.yml
@@ -26,7 +26,9 @@ zh-HK:
note: 簡介
otp_attempt: 雙重認證碼
password: 密碼
+ setting_boost_modal: 在轉推前詢問我
setting_default_privacy: 文章預設私隱度
+ severity: 等級
type: 匯入資料類型
username: 用戶名稱
interactions:
@@ -39,8 +41,8 @@ zh-HK:
follow_request: 當有用戶要求關注你時,發電郵通知
mention: 當有用戶在文章提及你時,發電郵通知
reblog: 當有用戶轉推你的文章時,發電郵通知
- 'no': '否'
+ 'no': 否
required:
mark: "*"
text: 必須填寫
- 'yes': '是'
+ 'yes': 是
diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml
new file mode 100644
index 00000000..0786c9cd
--- /dev/null
+++ b/config/locales/simple_form.zh-TW.yml
@@ -0,0 +1,46 @@
+---
+zh-TW:
+ simple_form:
+ hints:
+ defaults:
+ avatar: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB,會被縮小為 120x120px
+ display_name: 最多 30 個字元
+ header: 支援 PNG, GIF 或 JPG 圖片,檔案大小上限為 2MB,會被縮小為 700x335px
+ locked: 您必須手動核准每個使用者對您的關注請求,而您的文章隱私預設為「只有關注您的人能看」
+ note: 最多 160 個字元
+ imports:
+ data: 自其他服務站匯出的 CSV 檔案
+ labels:
+ defaults:
+ avatar: 大頭貼
+ confirm_new_password: 確認新密碼
+ confirm_password: 確認密碼
+ current_password: 目前密碼
+ data: 資料
+ display_name: 顯示名稱
+ email: 電子信箱
+ header: 個人頁面頂部
+ locale: 語言
+ locked: 將帳號轉為「私密」
+ new_password: 新密碼
+ note: 簡介
+ otp_attempt: 雙因子驗證碼
+ password: 密碼
+ setting_default_privacy: 文章預設隱私度
+ type: 匯入資料類型
+ username: 使用者名稱
+ interactions:
+ must_be_follower: 隱藏沒有關注您的使用者的通知
+ must_be_following: 隱藏您不關注的使用者的通知
+ notification_emails:
+ digest: 定期摘要信
+ favourite: 當有使用者喜歡您的文章時,發信通知
+ follow: 當有使用者關注您時,發信通知
+ follow_request: 當有使用者要求關注您時,發信通知
+ mention: 當有使用者在文章提及您時,發信通知
+ reblog: 當有使用者轉推您的文章時,發信通知
+ 'no': '否'
+ required:
+ mark: "*"
+ text: 必填
+ 'yes': '是'
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
index a1c87a7e..595e8ffc 100644
--- a/config/locales/zh-HK.yml
+++ b/config/locales/zh-HK.yml
@@ -34,15 +34,120 @@ zh-HK:
followers: 關注者
following: 正在關注
nothing_here: 暫時未有內容可以顯示
- people_followed_by: '%{name} 關注的人'
+ people_followed_by: "%{name} 關注的人"
people_who_follow: 關注 %{name} 的人
posts: 文章
remote_follow: 跨站關注
unfollow: 取消關注
+ admin:
+ accounts:
+ are_you_sure: 你確定嗎?
+ display_name: 顯示名稱
+ domain: 域名
+ edit: 編輯
+ email: 電郵地址
+ feed_url: Feed URL
+ followers: 關注者
+ follows: 正在關注
+ location:
+ all: 全部
+ local: 本地
+ remote: 遠端
+ title: 地點
+ media_attachments: 媒體檔案
+ moderation:
+ all: 全部
+ silenced: 被靜音的
+ suspended: 被停權的
+ title: 管理操作
+ most_recent_activity: 最新活動
+ most_recent_ip: 最新 IP 位域
+ not_subscribed: 未訂閱
+ order:
+ alphabetic: 按字母
+ most_recent: 按時間
+ title: 排序
+ perform_full_suspension: 實行完全暫停
+ profile_url: 個人檔案 URL
+ public: 公共
+ push_subscription_expires: PuSH subscription expires
+ salmon_url: Salmon 反饋 URL
+ silence: 靜音
+ statuses: 文章
+ title: 用戶
+ undo_silenced: 解除靜音
+ undo_suspension: 解除停權
+ username: 用戶名稱
+ web: 用戶頁面
+ domain_block:
+ add_new: 新增
+ domain: 域名阻隔
+ new:
+ create: 新增域名阻隔
+ hint: 「域名阻隔」不會隔絕該域名用戶的文章進入本站資料庫,但會文章抵達後,自動套用特定的審批操作。
+ severity:
+ desc_html: 「
自動靜音」令該域名用戶的文章,設為只對關注者顯示,沒有關注的人會看不到。
+ 「
自動刪除」會自動將該域名用戶的文章、媒體檔案、個人資料自本服務站刪除。
+ silence: 自動靜音
+ suspend: 自動刪除
+ title: 新增域名阻隔
+ severity: 阻隔分級
+ title: 域名阻隔
+ pubsubhubbub:
+ callback_url: 回傳 URL
+ confirmed: 確定
+ expires_in: 期限
+ last_delivery: 資料最後送抵時間
+ title: PubSubHubbub 訂閱
+ topic: 所訂閱資源
+ reports:
+ comment:
+ label: 詳細解釋
+ none: 沒有
+ delete: 刪除
+ id: ID
+ mark_as_resolved: 標示為「已處理」
+ report: '舉報 #%{id}'
+ reported_account: 舉報用戶
+ reported_by: 舉報者
+ resolved: 已處理
+ silence_account: 將用戶靜音
+ status: 狀態
+ suspend_account: 將用戶停權
+ target: 對象
+ title: 舉報
+ unresolved: 未處理
+ view: 檢視
+ settings:
+ click_to_edit: 點擊編輯
+ contact_information:
+ email: 輸入一個公開的電郵地址
+ label: 聯絡資料
+ username: 輸入用戶名稱
+ registrations:
+ closed_message:
+ desc_html: 當本站暫停接受註冊時,會顯示這個訊息。
+ 可使用 HTML
+ title: 暫停註冊訊息
+ open:
+ disabled: 停用
+ enabled: 啟用
+ title: 開放註冊
+ setting: 設定
+ site_description:
+ desc_html: 在首頁顯示,及在 meta tag 使用作網站介紹。
+ 你可以在此使用
<a>
和
<em>
。
+ title: 本站介紹
+ site_description_extended:
+ desc_html: 本站詳細資訊頁的內文
你可以在此使用 HTML
+ title: 本站詳細資訊
+ site_title: 本站名稱
+ title: 網站設定
+ title: 管理
application_mailer:
- settings: '修改電郵設定︰ %{link}'
+ settings: 修改電郵設定︰ %{link}
signature: 來自 %{instance} 的 Mastodon 通知
- view: '進入瀏覽︰'
+ view: 進入瀏覽︰
applications:
invalid_url: 所提供的網址不正確
auth:
@@ -58,26 +163,33 @@ zh-HK:
authorize_follow:
error: 對不起,尋找這個跨站用戶的過程發生錯誤
follow: 關注
- prompt_html: '你 (
%{self}) 正準備關注︰'
+ prompt_html: 你 (
%{self}) 正準備關注︰
title: 關注 %{acct}
datetime:
distance_in_words:
about_x_hours: "%{count}小時前"
about_x_months: "%{count}個月前"
about_x_years: "%{count}年前"
- almost_x_years: "接近%{count}年前"
+ almost_x_years: 接近%{count}年前
half_a_minute: 剛剛
- less_than_x_minutes: "少於%{count}分鐘前"
+ less_than_x_minutes: 少於%{count}分鐘前
less_than_x_seconds: 剛剛
over_x_years: "%{count}y"
x_days: "%{count}日"
x_minutes: "%{count}分鐘"
x_months: "%{count}個月"
x_seconds: "%{count}秒"
+ errors:
+ '404': 找不到內容
+ '410': 內容已被刪除
+ '422':
+ content: 無法確認登入資訊。會不會你阻擋了本站使用 Cookies 的權限?
+ title: 無法確認登入資訊
exports:
blocks: 被你封鎖的用戶
csv: CSV
follows: 你所關注的用戶
+ mutes: 你所靜音的用戶
storage: 媒體容量大小
generic:
changes_saved_msg: 已成功儲存修改
@@ -92,15 +204,17 @@ zh-HK:
types:
blocking: 被你封鎖的用戶名單
following: 你所關注的用戶名單
+ muting: Muting list
upload: 上載
- landing_strip_html:
%{name} 是一個在
%{domain} 的用戶。只要你有任何 Mastodon 服務站、或者聯盟網站的用戶,便可以跨站關注此站用戶,或者與他們互動。如果你沒有這類用戶,歡迎在
此處登記。
+ landing_strip_html:
%{name} 是一個在
%{domain} 的用戶。只要你有任何
+ Mastodon 服務站、或者聯盟網站的用戶,便可以跨站關注此站用戶,或者與他們互動。如果你沒有這類用戶,歡迎在
此處登記。
media_attachments:
validations:
images_and_video: 不能在已有圖片的文章上加入影片
too_many: 不可以加入超過 4 個檔案
notification_mailer:
digest:
- body: '這是自從你在%{since}使用%{instance}以後,你錯失了的訊息︰'
+ body: 這是自從你在%{since}使用%{instance}以後,你錯失了的訊息︰
mention: "%{name} 在此提及了你︰"
new_followers_summary:
one: 你新獲得了 1 位關注者了!恭喜!
@@ -109,19 +223,19 @@ zh-HK:
one: "自從上次登入以來,你收到 1 則新的通知 \U0001F418"
other: "自從上次登入以來,你收到 %{count} 則新的通知 \U0001F418"
favourite:
- body: '你的文章獲得 %{name} 的喜愛'
+ body: 你的文章獲得 %{name} 的喜愛
subject: "%{name} 喜歡你的文章"
follow:
body: "%{name} 開始關注你!"
subject: "%{name} 現正關注你"
follow_request:
body: "%{name} 要求關注你"
- subject: '等候關注你的用戶︰ %{name}'
+ subject: 等候關注你的用戶︰ %{name}
mention:
- body: '%{name} 在文章中提及你︰'
- subject: '%{name} 在文章中提及你'
+ body: "%{name} 在文章中提及你︰"
+ subject: "%{name} 在文章中提及你"
reblog:
- body: '你的文章得到 %{name} 的轉推'
+ body: 你的文章得到 %{name} 的轉推
subject: "%{name} 轉推了你的文章"
pagination:
next: 下一頁
@@ -131,7 +245,25 @@ zh-HK:
acct: 請輸入你的︰用戶名稱@服務點域名
missing_resource: 無法找到你用戶的轉接網址
proceed: 下一步
- prompt: '你希望關注︰'
+ prompt: 你希望關注︰
+ reports:
+ comment:
+ label: 詳細解釋
+ none: 沒有
+ delete: 刪除
+ id: ID
+ mark_as_resolved: 標示為「已處理」
+ report: '舉報 #%{id}'
+ reported_account: 舉報 account
+ reported_by: 舉報者
+ reports: 舉報
+ resolved: 已處埋
+ silence_account: 將用戶靜音
+ status: 狀態
+ suspend_account: 將用戶停權
+ target: 對像
+ unresolved: 未處埋
+ view: 檢視
settings:
authorized_apps: 授權應用程式
back: 回到 Mastodon
@@ -162,7 +294,7 @@ zh-HK:
disable: 停用
enable: 啟用
enabled_success: 已成功啟用雙重認證
- instructions_html:
請用你手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裏的 QR 圖形碼。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。
+ instructions_html: "
請用你手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裏的QR 圖形碼。在雙重認證啟用後,你登入時將須要使用此應用程式產生的認證碼。"
manual_instructions: 如果你無法掃描 QR 圖形碼,請手動輸入這個文字密碼︰
setup: 設定
warning: 如果你現在無法正確設定你的應用程式,請即「停用」雙重認證,否則日後可能無法登入本站。
@@ -170,3 +302,5 @@ zh-HK:
users:
invalid_email: 電郵地址格式不正確
invalid_otp_token: 雙重認證確認碼不正確
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml
new file mode 100644
index 00000000..ba5e8d6f
--- /dev/null
+++ b/config/locales/zh-TW.yml
@@ -0,0 +1,300 @@
+---
+zh-TW:
+ about:
+ about_mastodon: Mastodon (長毛象)是一個
自由、開放原始碼的社群網站。它是一個分散式的服務,避免您的通訊被單一商業機構壟斷操控。請您選擇一家您信任的 Mastodon 服務站,在上面建立帳號,然後您就可以和任一 Mastodon 服務站上的使用者互通,享受無縫的
社群網路交流。
+ about_this: 關於本服務站
+ apps: 應用程式
+ business_email: 商務信箱︰
+ closed_registrations: 本服務站暫時停止接受註冊。
+ contact: 聯絡我們
+ description_headline: 關於 %{domain}
+ domain_count_after: 個服務站相連
+ domain_count_before: 與其他
+ features:
+ api: 開放 API,供各式應用程式及服務串接
+ blocks: 完善的封鎖使用者、靜音功能
+ characters: 每篇文章最多 500 字
+ chronology: 時間軸按時序顯示文章,不作多餘處理
+ ethics: 良心設計︰沒有廣告,不追蹤您的使用行為
+ gifv: 支援顯示 GIFV 短片
+ privacy: 可逐篇文章調整隱私設定
+ public: 公開時間軸
+ features_headline: Mastodon 與眾不同之處
+ get_started: 立即註冊
+ links: 連結
+ other_instances: 其他服務站
+ source_code: 原始碼
+ status_count_after: 篇文章
+ status_count_before: 他們共發佈了
+ terms: 使用條款
+ user_count_after: 位註冊使用者
+ user_count_before: 這裡共有
+ accounts:
+ follow: 關注
+ followers: 關注者
+ following: 正在關注
+ nothing_here: 暫時沒有內容可供顯示
+ people_followed_by: '%{name} 關注的人'
+ people_who_follow: 關注 %{name} 的人
+ posts: 文章
+ remote_follow: 跨站關注
+ unfollow: 取消關注
+ admin:
+ accounts:
+ are_you_sure: 您確定嗎?
+ display_name: 顯示名稱
+ domain: 網域
+ edit: 編輯
+ email: 信箱
+ feed_url: Feed URL
+ followers: 追蹤者
+ follows: 追蹤的
+ location:
+ all: 全部
+ local: 本地
+ remote: 遠端
+ title: 位置
+ media_attachments: 多媒體附件
+ moderation:
+ all: 全部
+ silenced: 靜音
+ suspended: 停權
+ title: 版務
+ most_recent_activity: 最近活動
+ most_recent_ip: 最近 IP
+ not_subscribed: 未訂閱
+ order:
+ alphabetic: 字典序
+ most_recent: 時間序
+ title: 排序
+ perform_full_suspension: 進行停權
+ profile_url: 個人檔案網址
+ public: 公開
+ push_subscription_expires: PuSH 訂閱逾期
+ salmon_url: Salmon URL
+ silence: 靜音
+ statuses: 狀態
+ title: 帳號
+ undo_silenced: 取消靜音
+ undo_suspension: 取消停權
+ username: 使用者名稱
+ web: Web
+ domain_block:
+ add_new: 新增
+ domain: 網域
+ new:
+ create: 新增封鎖
+ hint: 網域封鎖動作並不會阻止帳號紀錄被新增至資料庫,但會自動回溯性地對那些帳號套用特定管理設定。
+ severity:
+ desc_html: "
靜音將使得未追蹤該帳號的使用者無法看見該帳號之貼文。
停權將移除所有該使用者的帳號內容、多媒體檔案、以及個人檔案。"
+ silence: 靜音
+ suspend: 停權
+ title: 新封鎖網域
+ severity: 嚴重度
+ title: 網域封鎖
+ pubsubhubbub:
+ callback_url: Callback URL
+ confirmed: 已確認
+ expires_in: 期限
+ last_delivery: 最後遞送
+ title: PubSubHubbub
+ topic: 主題
+ reports:
+ comment:
+ label: 留言
+ none: 無
+ delete: 刪除
+ id: ID
+ mark_as_resolved: 標記為已解決
+ report: '檢舉 #%{id}'
+ reported_account: 被檢舉帳號
+ reported_by: 檢舉人
+ resolved: 已解決
+ silence_account: 靜音帳號
+ status: 狀態
+ suspend_account: 停權帳號
+ target: 目標
+ title: 檢舉
+ unresolved: 未解決
+ view: 檢視
+ settings:
+ click_to_edit: 點選以編輯
+ contact_information:
+ email: 請輸入輸入一個公開電子信箱
+ label: 聯絡資訊
+ username: 請輸入使用者名稱
+ registrations:
+ closed_message:
+ desc_html: 關閉註冊時顯示在首頁的內容,可使用 HTML 標籤。
+ title: 關閉註冊訊息
+ open:
+ disabled: 停用
+ enabled: 啟用
+ title: 開放註冊
+ setting: 設定
+ site_description:
+ desc_html: 顯示在首頁並且作為 meta 標籤的短文。
可使用 HTML 標籤,包括
<a>
及
<em>
。
+ title: 網站描述
+ site_description_extended:
+ desc_html: 顯示在資訊頁的長版描述,可使用 HTML 標籤。
+ title: 長版網站描述
+ site_title: 網站標題
+ title: 網站設定
+ title: 管理介面
+ application_mailer:
+ settings: '修改信箱設定︰ %{link}'
+ signature: 來自 %{instance} 的 Mastodon 通知
+ view: '進入瀏覽︰'
+ applications:
+ invalid_url: 網址不正確
+ auth:
+ change_password: 登入資訊
+ didnt_get_confirmation: 沒有收到驗證信?
+ forgot_password: 忘記密碼?
+ login: 登入
+ logout: 登出
+ register: 註冊
+ resend_confirmation: 重寄驗證信
+ reset_password: 重設密碼
+ set_new_password: 設定新密碼
+ authorize_follow:
+ error: 對不起,尋找這個跨站使用者的過程發生錯誤
+ follow: 關注
+ prompt_html: '您 (
%{self}) 正準備關注︰'
+ title: 關注 %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}小時前"
+ about_x_months: "%{count}個月前"
+ about_x_years: "%{count}年前"
+ almost_x_years: "接近%{count}年前"
+ half_a_minute: 剛剛
+ less_than_x_minutes: "小於%{count}分鐘前"
+ less_than_x_seconds: 剛剛
+ over_x_years: "%{count}y"
+ x_days: "%{count}天"
+ x_minutes: "%{count}分"
+ x_months: "%{count}個月"
+ x_seconds: "%{count}秒"
+ errors:
+ '404': 您所尋找的網頁不存在。
+ '410': 您所尋找的網頁已不存在。
+ '422':
+ content: 安全驗證失敗。請確定有開啟瀏覽器 Cookies 功能。
+ title: 安全驗證失敗
+ exports:
+ blocks: 您封鎖的使用者
+ csv: CSV
+ follows: 您關注的使用者
+ storage: 儲存空間大小
+ generic:
+ changes_saved_msg: 已成功儲存修改
+ powered_by: 網站由 %{link} 開發
+ save_changes: 儲存修改
+ validation_errors:
+ one: 送出的資料有問題
+ other: 送出的資料有 %{count} 個問題
+ imports:
+ preface: 您可以在此匯入您在其他服務站所匯出的資料檔,包括關注的使用者、封鎖的使用者名單。
+ success: 資料檔上傳成功,正在匯入,請稍候
+ types:
+ blocking: 您封鎖的使用者名單
+ following: 您關注的使用者名單
+ upload: 上傳
+ landing_strip_html:
%{name} 是一個在
%{domain} 的使用者。只要您有任何 Mastodon 服務站、或者聯盟網站的帳號,便可以跨站關注此站使用者,或者與他們互動。如果您沒有這些帳號,歡迎在
這裡註冊。
+ media_attachments:
+ validations:
+ images_and_video: 無法在已有圖片的文章上加入影片
+ too_many: 無法加入超過 4 個檔案
+ notification_mailer:
+ digest:
+ body: '這是自從您在%{since}使用%{instance}以後,您錯過的訊息︰'
+ mention: "%{name} 在此提及了您︰"
+ new_followers_summary:
+ one: 您新獲得了 1 位關注者!恭喜!
+ other: 您新獲得了 %{count} 位關注者!好厲害!
+ subject:
+ one: "自從上次登入以來,您收到 1 則新的通知 \U0001F418"
+ other: "自從上次登入以來,您收到 %{count} 則新的通知 \U0001F418"
+ favourite:
+ body: '您的文章被 %{name} 喜歡'
+ subject: "%{name} 喜歡您的文章"
+ follow:
+ body: "%{name} 開始關注您!"
+ subject: "%{name} 開始關注您"
+ follow_request:
+ body: "%{name} 要求關注您"
+ subject: '等待關注您的使用者︰ %{name}'
+ mention:
+ body: '%{name} 在文章中提及您︰'
+ subject: '%{name} 在文章中提及您'
+ reblog:
+ body: '您的文章被 %{name} 轉推'
+ subject: "%{name} 轉推了您的文章"
+ pagination:
+ next: 下一頁
+ prev: 上一頁
+ truncate: "……"
+ remote_follow:
+ acct: 請輸入您的︰使用者名稱@服務點網域
+ missing_resource: 無法找到資源
+ proceed: 下一步
+ prompt: '您希望關注︰'
+ reports:
+ comment:
+ label: 留言
+ none: 無
+ delete: 刪除
+ id: ID
+ mark_as_resolved: 標記為已解決
+ report: '檢舉 #%{id}'
+ reported_account: 被檢舉帳號
+ reported_by: 檢舉人
+ reports: 檢舉
+ resolved: 已解決
+ silence_account: 靜音帳號
+ status: 狀態
+ suspend_account: 停權帳號
+ target: 目標
+ unresolved: 未解決
+ view: 檢視
+ settings:
+ authorized_apps: 已授權應用程式
+ back: 回到 Mastodon
+ edit_profile: 修改個人資料
+ export: 匯出
+ import: 匯入
+ preferences: 偏好設定
+ settings: 設定
+ two_factor_auth: 雙因子認證
+ statuses:
+ open_in_web: 以網頁開啟
+ over_character_limit: 超過了 %{max} 字的限制
+ show_more: 顯示更多
+ visibilities:
+ private: 只有關注您的人能看
+ public: 公開
+ unlisted: 公開,但不在公共時間軸顯示
+ stream_entries:
+ click_to_show: 點選顯示
+ reblogged: 轉推
+ sensitive_content: 敏感內容
+ time:
+ formats:
+ default: "%Y年%-m月%d日 %H:%M"
+ two_factor_auth:
+ code_hint: 請輸入您認證器產生的代碼,以進行認證
+ description_html: 當您啟用
雙因子認證後,您登入時將需要使您手機、或其他種類認證器產生的代碼。
+ disable: 停用
+ enable: 啟用
+ enabled_success: 已成功啟用雙因子認證
+ instructions_html:
請用您手機的認證器應用程式(如 Google Authenticator、Authy),掃描這裡的 QR 圖形碼。在雙因子認證啟用後,您登入時將須要使用此應用程式產生的認證碼。
+ manual_instructions: 如果您無法掃描 QR 圖形碼,請手動輸入︰
+ setup: 設定
+ warning: 如果您現在無法正確設定您的應用程式,請立刻「停用」雙因子認證,否則日後可能無法登入本站。
+ wrong_code: 您輸入的認證碼並不正確!可能伺服器時間和您手機不一致,請檢查您手機的時間,或與本站管理員聯絡。
+ users:
+ invalid_email: 信箱地址格式不正確
+ invalid_otp_token: 雙因子認證碼不正確
+ will_paginate:
+ page_gap: "…"
diff --git a/config/routes.rb b/config/routes.rb
index 045be940..31909a4f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -64,6 +64,7 @@ Rails.application.routes.draw do
resource :two_factor_auth, only: [:show, :new, :create] do
member do
post :disable
+ post :recovery_codes
end
end
end
@@ -85,6 +86,7 @@ Rails.application.routes.draw do
end
resources :accounts, only: [:index, :show] do
+ resource :reset, only: [:create]
resource :silence, only: [:create, :destroy]
resource :suspension, only: [:create, :destroy]
end
diff --git a/db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb b/db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb
new file mode 100644
index 00000000..08c916fe
--- /dev/null
+++ b/db/migrate/20170409170753_add_last_webfingered_at_to_accounts.rb
@@ -0,0 +1,5 @@
+class AddLastWebfingeredAtToAccounts < ActiveRecord::Migration[5.0]
+ def change
+ add_column :accounts, :last_webfingered_at, :datetime
+ end
+end
diff --git a/db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb b/db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb
new file mode 100644
index 00000000..65517d9f
--- /dev/null
+++ b/db/migrate/20170414080609_add_devise_two_factor_backupable_to_users.rb
@@ -0,0 +1,5 @@
+class AddDeviseTwoFactorBackupableToUsers < ActiveRecord::Migration[5.0]
+ def change
+ add_column :users, :otp_backup_codes, :string, array: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 2820a537..62ff4207 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -47,6 +47,7 @@ ActiveRecord::Schema.define(version: 20170414132105) do
t.integer "statuses_count", default: 0, null: false
t.integer "followers_count", default: 0, null: false
t.integer "following_count", default: 0, null: false
+ t.datetime "last_webfingered_at"
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", using: :btree
t.index ["url"], name: "index_accounts_on_url", using: :btree
@@ -313,6 +314,7 @@ ActiveRecord::Schema.define(version: 20170414132105) do
t.integer "consumed_timestep"
t.boolean "otp_required_for_login"
t.datetime "last_emailed_at"
+ t.string "otp_backup_codes", array: true
t.index ["account_id"], name: "index_users_on_account_id", using: :btree
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
diff --git a/docker-compose.yml b/docker-compose.yml
index 910bf8cf..81c6fe98 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -18,6 +18,7 @@ services:
web:
restart: always
build: .
+ image: gargron/mastodon
env_file: .env.production
command: bundle exec rails s -p 3000 -b '0.0.0.0'
ports:
@@ -32,6 +33,7 @@ services:
streaming:
restart: always
build: .
+ image: gargron/mastodon
env_file: .env.production
command: npm run start
ports:
@@ -43,6 +45,7 @@ services:
sidekiq:
restart: always
build: .
+ image: gargron/mastodon
env_file: .env.production
command: bundle exec sidekiq -q default -q mailers -q pull -q push
depends_on:
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 4761b291..15b42374 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -82,6 +82,22 @@ namespace :mastodon do
end
end
+ namespace :settings do
+ desc 'Open registrations on this instance'
+ task open_registrations: :environment do
+ setting = Setting.where(var: 'open_registrations').first
+ setting.value = true
+ setting.save
+ end
+
+ desc 'Close registrations on this instance'
+ task close_registrations: :environment do
+ setting = Setting.where(var: 'open_registrations').first
+ setting.value = false
+ setting.save
+ end
+ end
+
namespace :maintenance do
desc 'Update counter caches'
task update_counter_caches: :environment do
diff --git a/package.json b/package.json
index df496c55..6f448238 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,11 @@
{
"name": "mastodon",
"scripts": {
- "test": "mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.jsx",
+ "start": "babel-node ./streaming/index.js --presets es2015,stage-2",
"storybook": "start-storybook -p 9001 -c storybook",
- "start": "babel-node ./streaming/index.js --presets es2015,stage-2"
+ "test": "npm run test:lint && npm run test:mocha",
+ "test:lint": "eslint -c .eslintrc --ext=js --ext=jsx app/assets/javascripts/",
+ "test:mocha": "mocha --require ./spec/javascript/setup.js --compilers js:babel-register ./spec/javascript/components/*.test.jsx"
},
"dependencies": {
"@kadira/storybook": "^2.35.3",
@@ -74,8 +76,12 @@
"ws": "^2.1.0"
},
"devDependencies": {
- "babel-eslint": "^7.2.1",
+ "babel-eslint": "^7.2.2",
"eslint": "^3.19.0",
+ "eslint-plugin-jsx-a11y": "^4.0.0",
"eslint-plugin-react": "^6.10.3"
+ },
+ "optionalDependencies": {
+ "fsevents": "*"
}
}
diff --git a/spec/controllers/admin/resets_controller_spec.rb b/spec/controllers/admin/resets_controller_spec.rb
new file mode 100644
index 00000000..283ab029
--- /dev/null
+++ b/spec/controllers/admin/resets_controller_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+describe Admin::ResetsController do
+ let(:account) { Fabricate(:account, user: Fabricate(:user)) }
+ before do
+ sign_in Fabricate(:user, admin: true), scope: :user
+ end
+
+ describe 'POST #create' do
+ it 'redirects to admin accounts page' do
+ post :create, params: { account_id: account.id }
+
+ expect(response).to redirect_to(admin_accounts_path)
+ end
+ end
+end
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index c2a1fbe9..0ec0b8f2 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -5,7 +5,7 @@ RSpec.describe Auth::SessionsController, type: :controller do
describe 'GET #new' do
before do
- request.env["devise.mapping"] = Devise.mappings[:user]
+ request.env['devise.mapping'] = Devise.mappings[:user]
end
it 'returns http success' do
@@ -15,19 +15,94 @@ RSpec.describe Auth::SessionsController, type: :controller do
end
describe 'POST #create' do
- let(:user) { Fabricate(:user, email: 'foo@bar.com', password: 'abcdefgh') }
-
before do
- request.env["devise.mapping"] = Devise.mappings[:user]
- post :create, params: { user: { email: user.email, password: user.password } }
+ request.env['devise.mapping'] = Devise.mappings[:user]
end
- it 'redirects to home' do
- expect(response).to redirect_to(root_path)
+ context 'using password authentication' do
+ let(:user) { Fabricate(:user, email: 'foo@bar.com', password: 'abcdefgh') }
+
+ context 'using a valid password' do
+ before do
+ post :create, params: { user: { email: user.email, password: user.password } }
+ end
+
+ it 'redirects to home' do
+ expect(response).to redirect_to(root_path)
+ end
+
+ it 'logs the user in' do
+ expect(controller.current_user).to eq user
+ end
+ end
+
+ context 'using an invalid password' do
+ before do
+ post :create, params: { user: { email: user.email, password: 'wrongpw' } }
+ end
+
+ it 'shows a login error' do
+ expect(flash[:alert]).to match I18n.t('devise.failure.invalid', authentication_keys: 'Email')
+ end
+
+ it "doesn't log the user in" do
+ expect(controller.current_user).to be_nil
+ end
+ end
end
- it 'logs the user in' do
- expect(controller.current_user).to eq user
+ context 'using two-factor authentication' do
+ let(:user) do
+ Fabricate(:user, email: 'x@y.com', password: 'abcdefgh',
+ otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+ end
+ let(:recovery_codes) do
+ codes = user.generate_otp_backup_codes!
+ user.save
+ return codes
+ end
+
+ context 'using a valid OTP' do
+ before do
+ post :create, params: { user: { otp_attempt: user.current_otp } }, session: { otp_user_id: user.id }
+ end
+
+ it 'redirects to home' do
+ expect(response).to redirect_to(root_path)
+ end
+
+ it 'logs the user in' do
+ expect(controller.current_user).to eq user
+ end
+ end
+
+ context 'using a valid recovery code' do
+ before do
+ post :create, params: { user: { otp_attempt: recovery_codes.first } }, session: { otp_user_id: user.id }
+ end
+
+ it 'redirects to home' do
+ expect(response).to redirect_to(root_path)
+ end
+
+ it 'logs the user in' do
+ expect(controller.current_user).to eq user
+ end
+ end
+
+ context 'using an invalid OTP' do
+ before do
+ post :create, params: { user: { otp_attempt: 'wrongotp' } }, session: { otp_user_id: user.id }
+ end
+
+ it 'shows a login error' do
+ expect(flash[:alert]).to match I18n.t('users.invalid_otp_token')
+ end
+
+ it "doesn't log the user in" do
+ expect(controller.current_user).to be_nil
+ end
+ end
end
end
end
diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb
index 567de05f..68a75807 100644
--- a/spec/fabricators/account_fabricator.rb
+++ b/spec/fabricators/account_fabricator.rb
@@ -1,3 +1,4 @@
Fabricator(:account) do
username { Faker::Internet.user_name(nil, %w(_)) }
+ last_webfingered_at { Time.now.utc }
end
diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb
new file mode 100644
index 00000000..4a634f6b
--- /dev/null
+++ b/spec/features/log_in_spec.rb
@@ -0,0 +1,16 @@
+require "rails_helper"
+
+feature "Log in" do
+ scenario "A valid email and password user is able to log in" do
+ email = "test@example.com"
+ password = "password"
+ Fabricate(:user, email: email, password: password)
+
+ visit new_user_session_path
+ fill_in "user_email", with: email
+ fill_in "user_password", with: password
+ click_on "Log in"
+
+ expect(page).to have_css "div.app-holder[data-react-class=Mastodon]"
+ end
+end
diff --git a/spec/helpers/admin/accounts_helper_spec.rb b/spec/helpers/admin/accounts_helper_spec.rb
deleted file mode 100644
index b91f258b..00000000
--- a/spec/helpers/admin/accounts_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Admin::AccountsHelper, type: :helper do
-
-end
diff --git a/spec/helpers/admin/filter_helper_spec.rb b/spec/helpers/admin/filter_helper_spec.rb
new file mode 100644
index 00000000..9d4ea282
--- /dev/null
+++ b/spec/helpers/admin/filter_helper_spec.rb
@@ -0,0 +1,20 @@
+require 'rails_helper'
+
+describe Admin::FilterHelper do
+ it 'Uses filter_link_to to create filter links' do
+ params = ActionController::Parameters.new(
+ { test: 'test' }
+ )
+ allow(helper).to receive(:params).and_return(params)
+ allow(helper).to receive(:url_for).and_return('/test')
+ result = helper.filter_link_to('text', { resolved: true })
+
+ expect(result).to match(/text/)
+ end
+
+ it 'Uses table_link_to to create icon links' do
+ result = helper.table_link_to 'icon', 'text', 'path'
+
+ expect(result).to match(/text/)
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index eb2a4aae..4ed1d5a0 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,6 +1,9 @@
require 'rails_helper'
+require 'devise_two_factor/spec_helpers'
RSpec.describe User, type: :model do
+ it_behaves_like 'two_factor_backupable'
+
describe 'validations' do
it 'is invalid without an account' do
user = Fabricate.build(:user, account: nil)
diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb
index 0f318d9c..2de63eac 100644
--- a/spec/presenters/instance_presenter_spec.rb
+++ b/spec/presenters/instance_presenter_spec.rb
@@ -28,9 +28,9 @@ describe InstancePresenter do
end
it "delegates contact_email to Setting" do
- Setting.contact_email = "admin@example.com"
+ Setting.site_contact_email = "admin@example.com"
- expect(instance_presenter.contact_email).to eq "admin@example.com"
+ expect(instance_presenter.site_contact_email).to eq "admin@example.com"
end
describe "contact_account" do
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index faac9698..60d45ddc 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -7,6 +7,7 @@ require 'spec_helper'
require 'rspec/rails'
require 'webmock/rspec'
require 'paperclip/matchers'
+require 'capybara/rspec'
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
@@ -22,8 +23,13 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace!
config.include Devise::Test::ControllerHelpers, type: :controller
- config.include Devise::TestHelpers, type: :view
+ config.include Devise::Test::ControllerHelpers, type: :view
config.include Paperclip::Shoulda::Matchers
+
+ config.before :each, type: :feature do
+ https = ENV['LOCAL_HTTPS'] == 'true'
+ Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}"
+ end
end
RSpec::Sidekiq.configure do |config|
diff --git a/spec/requests/localization_spec.rb b/spec/requests/localization_spec.rb
new file mode 100644
index 00000000..d1b7bdc1
--- /dev/null
+++ b/spec/requests/localization_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Localization' do
+ it 'uses a specific region when provided' do
+ headers = { 'Accept-Language' => 'zh-HK' }
+
+ get "/about", headers: headers
+ expect(response.body).to include(
+ I18n.t('about.about_mastodon', locale: 'zh-HK')
+ )
+ end
+
+ it 'falls back to a locale when region missing' do
+ headers = { 'Accept-Language' => 'es-FAKE' }
+
+ get "/about", headers: headers
+ expect(response.body).to include(
+ I18n.t('about.about_mastodon', locale: 'es')
+ )
+ end
+ it 'falls back to english when locale is missing' do
+ headers = { 'Accept-Language' => '12-FAKE' }
+
+ get "/about", headers: headers
+ expect(response.body).to include(
+ I18n.t('about.about_mastodon', locale: 'en')
+ )
+ end
+end
diff --git a/spec/services/account_search_service_spec.rb b/spec/services/account_search_service_spec.rb
new file mode 100644
index 00000000..fa421c44
--- /dev/null
+++ b/spec/services/account_search_service_spec.rb
@@ -0,0 +1,104 @@
+require 'rails_helper'
+
+describe AccountSearchService do
+ describe '.call' do
+ describe 'with a query to ignore' do
+ it 'returns empty array for missing query' do
+ results = subject.call('', 10)
+
+ expect(results).to eq []
+ end
+ it 'returns empty array for hashtag query' do
+ results = subject.call('#tag', 10)
+
+ expect(results).to eq []
+ end
+ end
+
+ describe 'searching for a simple term that is not an exact match' do
+ it 'does not return a nil entry in the array for the exact match' do
+ match = Fabricate(:account, username: 'matchingusername')
+
+ results = subject.call('match', 5)
+ expect(results).to eq [match]
+ end
+ end
+
+ describe 'searching local and remote users' do
+ describe 'when no domain' do
+ before do
+ allow(Account).to receive(:find_remote)
+ allow(Account).to receive(:search_for)
+ subject.call('one', 10)
+ end
+
+ it 'uses find_remote with nil domain to look for local accounts' do
+ expect(Account).to have_received(:find_remote).with('one', nil)
+ end
+
+ it 'uses search_for to find matches' do
+ expect(Account).to have_received(:search_for).with('one', 10)
+ end
+ end
+
+ describe 'when there is a domain' do
+ before do
+ allow(Account).to receive(:find_remote)
+ end
+
+ it 'uses find_remote to look for remote accounts' do
+ subject.call('two@example.com', 10)
+ expect(Account).to have_received(:find_remote).with('two', 'example.com')
+ end
+
+ describe 'and there is no account provided' do
+ it 'uses search_for to find matches' do
+ allow(Account).to receive(:search_for)
+ subject.call('two@example.com', 10, false, nil)
+
+ expect(Account).to have_received(:search_for).with('two example.com', 10)
+ end
+ end
+
+ describe 'and there is an account provided' do
+ it 'uses advanced_search_for to find matches' do
+ account = Fabricate(:account)
+ allow(Account).to receive(:advanced_search_for)
+ subject.call('two@example.com', 10, false, account)
+
+ expect(Account).to have_received(:advanced_search_for).with('two example.com', account, 10)
+ end
+ end
+ end
+ end
+
+ describe 'with an exact match' do
+ it 'returns exact match first, and does not return duplicates' do
+ partial = Fabricate(:account, username: 'exactness')
+ exact = Fabricate(:account, username: 'exact')
+
+ results = subject.call('exact', 10)
+ expect(results.size).to eq 2
+ expect(results).to eq [exact, partial]
+ end
+ end
+
+ describe 'when there is a domain but no exact match' do
+ it 'follows the remote account when resolve is true' do
+ service = double(call: nil)
+ allow(FollowRemoteAccountService).to receive(:new).and_return(service)
+
+ results = subject.call('newuser@remote.com', 10, true)
+ expect(service).to have_received(:call).with('newuser@remote.com')
+ end
+
+ it 'does not follow the remote account when resolve is false' do
+ service = double(call: nil)
+ allow(FollowRemoteAccountService).to receive(:new).and_return(service)
+
+ results = subject.call('newuser@remote.com', 10, false)
+ expect(service).not_to have_received(:call)
+ end
+ end
+ end
+end
diff --git a/spec/views/about/_contact.html.haml_spec.rb b/spec/views/about/_contact.html.haml_spec.rb
new file mode 100644
index 00000000..2a6decbc
--- /dev/null
+++ b/spec/views/about/_contact.html.haml_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'about/_contact.html.haml' do
+ describe 'the contact account' do
+ it 'shows info when account is present' do
+ account = Account.new(username: 'admin')
+ contact = double(contact_account: account, site_contact_email: '')
+ render 'about/contact', contact: contact
+
+ expect(rendered).to have_content('@admin')
+ end
+
+ it 'does not show info when account is missing' do
+ contact = double(contact_account: nil, site_contact_email: '')
+ render 'about/contact', contact: contact
+
+ expect(rendered).not_to have_content('@')
+ end
+ end
+
+ describe 'the contact email' do
+ it 'show info when email is present' do
+ contact = double(site_contact_email: 'admin@example.com', contact_account: nil)
+ render 'about/contact', contact: contact
+
+ expect(rendered).to have_content('admin@example.com')
+ end
+
+ it 'does not show info when email is missing' do
+ contact = double(site_contact_email: nil, contact_account: nil)
+ render 'about/contact', contact: contact
+
+ expect(rendered).not_to have_content(I18n.t('about.business_email'))
+ end
+ end
+end
diff --git a/spec/views/about/_links.html.haml_spec.rb b/spec/views/about/_links.html.haml_spec.rb
new file mode 100644
index 00000000..5d3950dc
--- /dev/null
+++ b/spec/views/about/_links.html.haml_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'about/_links.html.haml' do
+ it 'does not show sign in link when signed in' do
+ allow(view).to receive(:user_signed_in?).and_return(true)
+ render
+
+ expect(rendered).to have_content(I18n.t('about.get_started'))
+ expect(rendered).not_to have_content(I18n.t('auth.login'))
+ end
+
+ it 'shows sign in link when signed out' do
+ allow(view).to receive(:user_signed_in?).and_return(false)
+ render
+
+ expect(rendered).to have_content(I18n.t('about.get_started'))
+ expect(rendered).to have_content(I18n.t('auth.login'))
+ end
+end
diff --git a/spec/views/accounts/show.html.haml_spec.rb b/spec/views/accounts/show.html.haml_spec.rb
new file mode 100644
index 00000000..8265b2f4
--- /dev/null
+++ b/spec/views/accounts/show.html.haml_spec.rb
@@ -0,0 +1,67 @@
+require 'rails_helper'
+$LOAD_PATH << '../lib'
+require 'tag_manager'
+
+describe "stream_entries/show.html.haml" do
+
+ before do
+ double(:api_oembed_url => '')
+ double(:account_stream_entry_url => '')
+ end
+
+ it "has valid author h-card and basic data for a detailed_status" do
+ alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
+ bob = Fabricate(:account, username: 'bob', display_name: 'Bob')
+ status = Fabricate(:status, account: alice, text: 'Hello World')
+ reply = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
+
+ assign(:status, status)
+ assign(:stream_entry, status.stream_entry)
+ assign(:account, alice)
+ assign(:type, status.stream_entry.activity_type.downcase)
+
+ render(:template => 'stream_entries/show.html.haml')
+
+ mf2 = Microformats2.parse(rendered)
+
+ expect(mf2.entry.name.to_s).to eq status.text
+ expect(mf2.entry.url.to_s).not_to be_empty
+
+ expect(mf2.entry.author.format.name.to_s).to eq alice.display_name
+ expect(mf2.entry.author.format.url.to_s).not_to be_empty
+ end
+
+ it "has valid h-cites for p-in-reply-to and p-comment" do
+ alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
+ bob = Fabricate(:account, username: 'bob', display_name: 'Bob')
+ carl = Fabricate(:account, username: 'carl', display_name: 'Carl')
+ status = Fabricate(:status, account: alice, text: 'Hello World')
+ reply = Fabricate(:status, account: bob, thread: status, text: 'Hello Alice')
+ comment = Fabricate(:status, account: carl, thread: reply, text: 'Hello Bob')
+
+ assign(:status, reply)
+ assign(:stream_entry, reply.stream_entry)
+ assign(:account, alice)
+ assign(:type, reply.stream_entry.activity_type.downcase)
+ assign(:ancestors, reply.stream_entry.activity.ancestors(bob) )
+ assign(:descendants, reply.stream_entry.activity.descendants(bob))
+
+ render(:template => 'stream_entries/show.html.haml')
+
+ mf2 = Microformats2.parse(rendered)
+
+ expect(mf2.entry.name.to_s).to eq reply.text
+ expect(mf2.entry.url.to_s).not_to be_empty
+
+ expect(mf2.entry.comment.format.url.to_s).not_to be_empty
+ expect(mf2.entry.comment.format.author.format.name.to_s).to eq carl.display_name
+ expect(mf2.entry.comment.format.author.format.url.to_s).not_to be_empty
+
+ expect(mf2.entry.in_reply_to.format.url.to_s).not_to be_empty
+ expect(mf2.entry.in_reply_to.format.author.format.name.to_s).to eq alice.display_name
+ expect(mf2.entry.in_reply_to.format.author.format.url.to_s).not_to be_empty
+ end
+
+end
+
+
diff --git a/spec/views/stream_entries/show.html.haml_spec.rb b/spec/views/stream_entries/show.html.haml_spec.rb
new file mode 100644
index 00000000..18814e78
--- /dev/null
+++ b/spec/views/stream_entries/show.html.haml_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+describe "accounts/show.html.haml" do
+
+ it "has an h-feed with correct number of h-entry objects in it" do
+ alice = Fabricate(:account, username: 'alice', display_name: 'Alice')
+ status = Fabricate(:status, account: alice, text: 'Hello World')
+ status2 = Fabricate(:status, account: alice, text: 'Hello World Again')
+ status3 = Fabricate(:status, account: alice, text: 'Are You Still There World?')
+
+ assign(:account, alice)
+ assign(:statuses, alice.statuses)
+
+ render(:template => 'accounts/show.html.haml')
+
+ expect(Nokogiri::HTML(rendered).search('.h-feed .h-entry').size).to eq 3
+
+ end
+
+end
+
+
diff --git a/yarn.lock b/yarn.lock
index 7f1c48db..5c468a2f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -250,6 +250,12 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+aria-query@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-0.3.0.tgz#cb8a9984e2862711c83c80ade5b8f5ca0de2b467"
+ dependencies:
+ ast-types-flow "0.0.7"
+
arr-diff@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf"
@@ -357,6 +363,10 @@ assertion-error@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c"
+ast-types-flow@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
+
ast-types@0.9.5:
version "0.9.5"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.5.tgz#1a660a09945dbceb1f9c9cbb715002617424e04a"
@@ -511,9 +521,9 @@ babel-core@^6.11.4:
slash "^1.0.0"
source-map "^0.5.0"
-babel-eslint@^7.2.1:
- version "7.2.1"
- resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.1.tgz#079422eb73ba811e3ca0865ce87af29327f8c52f"
+babel-eslint@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.2.tgz#0da2cbe6554fd0fb069f19674f2db2f9c59270ff"
dependencies:
babel-code-frame "^6.22.0"
babel-traverse "^6.23.1"
@@ -2128,6 +2138,10 @@ d@1:
dependencies:
es5-ext "^0.10.9"
+damerau-levenshtein@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514"
+
dashdash@^1.12.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.0.tgz#29e486c5418bf0f356034a993d51686a33e84141"
@@ -2343,6 +2357,10 @@ elliptic@^6.0.0:
hash.js "^1.0.0"
inherits "^2.0.1"
+emoji-regex@^6.1.0:
+ version "6.4.2"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.4.2.tgz#a30b6fee353d406d96cfb9fa765bdc82897eff6e"
+
emojione-picker@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/emojione-picker/-/emojione-picker-2.0.1.tgz#62e58db67d37a400a883c82d39abb1cc1c8ed65a"
@@ -2542,6 +2560,17 @@ escope@^3.6.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
+eslint-plugin-jsx-a11y@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-4.0.0.tgz#779bb0fe7b08da564a422624911de10061e048ee"
+ dependencies:
+ aria-query "^0.3.0"
+ ast-types-flow "0.0.7"
+ damerau-levenshtein "^1.0.0"
+ emoji-regex "^6.1.0"
+ jsx-ast-utils "^1.0.0"
+ object-assign "^4.0.1"
+
eslint-plugin-react@^6.10.3:
version "6.10.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz#c5435beb06774e12c7db2f6abaddcbf900cd3f78"
@@ -2886,7 +2915,7 @@ fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-fsevents@^1.0.0:
+fsevents@*, fsevents@^1.0.0:
version "1.0.14"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.0.14.tgz#558e8cc38643d8ef40fe45158486d0d25758eee4"
dependencies:
@@ -3672,7 +3701,7 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.3.6"
-jsx-ast-utils@^1.3.4:
+jsx-ast-utils@^1.0.0, jsx-ast-utils@^1.3.4:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-1.4.0.tgz#5afe38868f56bc8cc7aeaef0100ba8c75bd12591"
dependencies: