-
+
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
index f98fe1b0..7ead6880 100644
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ b/app/assets/javascripts/components/features/status/index.jsx
@@ -38,7 +38,8 @@ const makeMapStateToProps = () => {
status: getStatus(state, Number(props.params.statusId)),
ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
- me: state.getIn(['meta', 'me'])
+ me: state.getIn(['meta', 'me']),
+ boostModal: state.getIn(['meta', 'boost_modal'])
});
return mapStateToProps;
@@ -55,7 +56,8 @@ const Status = React.createClass({
status: ImmutablePropTypes.map,
ancestorsIds: ImmutablePropTypes.list,
descendantsIds: ImmutablePropTypes.list,
- me: React.PropTypes.number
+ me: React.PropTypes.number,
+ boostModal: React.PropTypes.bool
},
mixins: [PureRenderMixin],
@@ -82,11 +84,19 @@ const Status = React.createClass({
this.props.dispatch(replyCompose(status, this.context.router));
},
- handleReblogClick (status) {
+ handleModalReblog (status) {
+ this.props.dispatch(reblog(status));
+ },
+
+ handleReblogClick (status, e) {
if (status.get('reblogged')) {
this.props.dispatch(unreblog(status));
} else {
- this.props.dispatch(reblog(status));
+ if (e.shiftKey || !this.props.boostModal) {
+ this.handleModalReblog(status);
+ } else {
+ this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
+ }
}
},
@@ -102,6 +112,10 @@ const Status = React.createClass({
this.props.dispatch(openModal('MEDIA', { media, index }));
},
+ handleOpenVideo (media, time) {
+ this.props.dispatch(openModal('VIDEO', { media, time }));
+ },
+
handleReport (status) {
this.props.dispatch(initReport(status.get('account'), status));
},
@@ -141,7 +155,7 @@ const Status = React.createClass({
{ancestors}
-
+
{descendants}
diff --git a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx
new file mode 100644
index 00000000..b5476863
--- /dev/null
+++ b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx
@@ -0,0 +1,77 @@
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import IconButton from '../../../components/icon_button';
+import Button from '../../../components/button';
+import StatusContent from '../../../components/status_content';
+import Avatar from '../../../components/avatar';
+import RelativeTimestamp from '../../../components/relative_timestamp';
+import DisplayName from '../../../components/display_name';
+
+const messages = defineMessages({
+ reblog: { id: 'status.reblog', defaultMessage: 'Boost' }
+});
+
+const BoostModal = React.createClass({
+ contextTypes: {
+ router: React.PropTypes.object
+ },
+
+ propTypes: {
+ status: ImmutablePropTypes.map.isRequired,
+ onReblog: React.PropTypes.func.isRequired,
+ onClose: React.PropTypes.func.isRequired,
+ intl: React.PropTypes.object.isRequired
+ },
+
+ mixins: [PureRenderMixin],
+
+ handleReblog() {
+ this.props.onReblog(this.props.status);
+ this.props.onClose();
+ },
+
+ handleAccountClick (e) {
+ if (e.button === 0) {
+ e.preventDefault();
+ this.props.onClose();
+ this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
+ }
+ },
+
+ render () {
+ const { status, intl, onClose } = this.props;
+
+ return (
+
+ );
+ }
+
+});
+
+export default injectIntl(BoostModal);
diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx
index 2b7e11bf..057b1a9a 100644
--- a/app/assets/javascripts/components/features/ui/components/column.jsx
+++ b/app/assets/javascripts/components/features/ui/components/column.jsx
@@ -41,8 +41,11 @@ const Column = React.createClass({
mixins: [PureRenderMixin],
handleHeaderClick () {
- let node = ReactDOM.findDOMNode(this);
- this._interruptScrollAnimation = scrollTop(node.querySelector('.scrollable'));
+ const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable');
+ if (!scrollable) {
+ return;
+ }
+ this._interruptScrollAnimation = scrollTop(scrollable);
},
handleWheel () {
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 35eb2cb0..130f48b4 100644
--- a/app/assets/javascripts/components/features/ui/components/media_modal.jsx
+++ b/app/assets/javascripts/components/features/ui/components/media_modal.jsx
@@ -111,7 +111,7 @@ const MediaModal = React.createClass({
if (attachment.get('type') === 'image') {
content =
;
} else if (attachment.get('type') === 'gifv') {
- content =
;
+ content =
;
}
return (
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 0d20d7f8..f31c1822 100644
--- a/app/assets/javascripts/components/features/ui/components/modal_root.jsx
+++ b/app/assets/javascripts/components/features/ui/components/modal_root.jsx
@@ -1,11 +1,15 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import MediaModal from './media_modal';
import OnboardingModal from './onboarding_modal';
+import VideoModal from './video_modal';
+import BoostModal from './boost_modal';
import { TransitionMotion, spring } from 'react-motion';
const MODAL_COMPONENTS = {
'MEDIA': MediaModal,
- 'ONBOARDING': OnboardingModal
+ 'ONBOARDING': OnboardingModal,
+ 'VIDEO': VideoModal,
+ 'BOOST': BoostModal
};
const ModalRoot = React.createClass({
diff --git a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx
index f27ac7ef..44922caf 100644
--- a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx
+++ b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx
@@ -6,47 +6,52 @@ import Permalink from '../../../components/permalink';
const PageOne = ({ acct, domain }) => (
-
-
}} />
-
{domain}, handle: @{acct}@{domain} }}/>
+
+
+
+
+
+
{acct}@{domain} }}/>
+
);
-const PageTwo = (
+const PageTwo = () => (
-
+
);
-
-const PageThree = (
+const PageThree = () => (
-
+
-
+
);
-const PageFour = (
+const PageFour = () => (
);
-const PageFive = (
+const PageFive = () => (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
);
@@ -63,8 +68,6 @@ const PageSix = ({ admin }) => (
);
-
-
const mapStateToProps = state => ({
me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
@@ -114,10 +117,10 @@ const OnboardingModal = React.createClass({
const pages = [
,
- PageTwo,
- PageThree,
- PageFour,
- PageFive,
+
,
+
,
+
,
+
,
];
diff --git a/app/assets/javascripts/components/features/ui/components/video_modal.jsx b/app/assets/javascripts/components/features/ui/components/video_modal.jsx
new file mode 100644
index 00000000..1c3519bd
--- /dev/null
+++ b/app/assets/javascripts/components/features/ui/components/video_modal.jsx
@@ -0,0 +1,47 @@
+import LoadingIndicator from '../../../components/loading_indicator';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ExtendedVideoPlayer from '../../../components/extended_video_player';
+import { defineMessages, injectIntl } from 'react-intl';
+import IconButton from '../../../components/icon_button';
+
+const messages = defineMessages({
+ close: { id: 'lightbox.close', defaultMessage: 'Close' }
+});
+
+const closeStyle = {
+ position: 'absolute',
+ zIndex: '100',
+ top: '4px',
+ right: '4px'
+};
+
+const VideoModal = React.createClass({
+
+ propTypes: {
+ media: ImmutablePropTypes.map.isRequired,
+ time: React.PropTypes.number,
+ onClose: React.PropTypes.func.isRequired,
+ intl: React.PropTypes.object.isRequired
+ },
+
+ mixins: [PureRenderMixin],
+
+ render () {
+ const { media, intl, time, onClose } = this.props;
+
+ const url = media.get('url');
+
+ return (
+
+ );
+ }
+
+});
+
+export default injectIntl(VideoModal);
diff --git a/app/assets/javascripts/components/link_header.jsx b/app/assets/javascripts/components/link_header.jsx
new file mode 100644
index 00000000..9a9ff7e7
--- /dev/null
+++ b/app/assets/javascripts/components/link_header.jsx
@@ -0,0 +1,33 @@
+import Link from 'http-link-header';
+import querystring from 'querystring';
+
+Link.parseAttrs = (link, parts) => {
+ let match = null
+ let attr = ''
+ let value = ''
+ let attrs = ''
+
+ let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts)
+
+ if(uriAttrs) {
+ attrs = uriAttrs[2]
+ link = Link.parseParams(link, uriAttrs[1])
+ }
+
+ while(match = Link.attrPattern.exec(attrs)) {
+ attr = match[1].toLowerCase()
+ value = match[4] || match[3] || match[2]
+
+ if( /\*$/.test(attr)) {
+ Link.setAttr(link, attr, Link.parseExtendedValue(value))
+ } else if(/%/.test(value)) {
+ Link.setAttr(link, attr, querystring.decode(value))
+ } else {
+ Link.setAttr(link, attr, value)
+ }
+ }
+
+ return link
+};
+
+export default Link;
diff --git a/app/assets/javascripts/components/locales/bg.jsx b/app/assets/javascripts/components/locales/bg.jsx
new file mode 100644
index 00000000..a194cdbd
--- /dev/null
+++ b/app/assets/javascripts/components/locales/bg.jsx
@@ -0,0 +1,68 @@
+const bg = {
+ "column_back_button.label": "Назад",
+ "lightbox.close": "Затвори",
+ "loading_indicator.label": "Зареждане...",
+ "status.mention": "Споменаване",
+ "status.delete": "Изтриване",
+ "status.reply": "Отговор",
+ "status.reblog": "Споделяне",
+ "status.favourite": "Предпочитани",
+ "status.reblogged_by": "{name} сподели",
+ "status.sensitive_warning": "Деликатно съдържание",
+ "status.sensitive_toggle": "Покажи",
+ "video_player.toggle_sound": "Звук",
+ "account.mention": "Споменаване",
+ "account.edit_profile": "Редактирай профила си",
+ "account.unblock": "Не блокирай",
+ "account.unfollow": "Не следвай",
+ "account.block": "Блокирай",
+ "account.follow": "Последвай",
+ "account.posts": "Публикации",
+ "account.follows": "Следвам",
+ "account.followers": "Последователи",
+ "account.follows_you": "Твой последовател",
+ "account.requested": "В очакване на одобрение",
+ "getting_started.heading": "Първи стъпки",
+ "getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн",
+ "getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.",
+ "getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social",
+ "getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
+ "column.home": "Начало",
+ "column.mentions": "Споменавания",
+ "column.public": "Публичен канал",
+ "column.notifications": "Известия",
+ "tabs_bar.compose": "Съставяне",
+ "tabs_bar.home": "Начало",
+ "tabs_bar.mentions": "Споменавания",
+ "tabs_bar.public": "Публичен канал",
+ "tabs_bar.notifications": "Известия",
+ "compose_form.placeholder": "Какво си мислиш?",
+ "compose_form.publish": "Раздумай",
+ "compose_form.sensitive": "Отбележи съдържанието като деликатно",
+ "compose_form.spoiler": "Скрий текста зад предупреждение",
+ "compose_form.private": "Отбележи като поверително",
+ "compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?",
+ "compose_form.unlisted": "Не показвай в публичния канал",
+ "navigation_bar.edit_profile": "Редактирай профил",
+ "navigation_bar.preferences": "Предпочитания",
+ "navigation_bar.public_timeline": "Публичен канал",
+ "navigation_bar.logout": "Излизане",
+ "reply_indicator.cancel": "Отказ",
+ "search.placeholder": "Търсене",
+ "search.account": "Акаунт",
+ "search.hashtag": "Хаштаг",
+ "upload_button.label": "Добави медия",
+ "upload_form.undo": "Отмяна",
+ "notification.follow": "{name} те последва",
+ "notification.favourite": "{name} хареса твоята публикация",
+ "notification.reblog": "{name} сподели твоята публикация",
+ "notification.mention": "{name} те спомена",
+ "notifications.column_settings.alert": "Десктоп известия",
+ "notifications.column_settings.show": "Покажи в колона",
+ "notifications.column_settings.follow": "Нови последователи:",
+ "notifications.column_settings.favourite": "Предпочитани:",
+ "notifications.column_settings.mention": "Споменавания:",
+ "notifications.column_settings.reblog": "Споделяния:",
+};
+
+export default bg;
diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx
index 882c31fa..bd98b759 100644
--- a/app/assets/javascripts/components/locales/de.jsx
+++ b/app/assets/javascripts/components/locales/de.jsx
@@ -1,15 +1,15 @@
-const en = {
+const de = {
"column_back_button.label": "Zurück",
"lightbox.close": "Schließen",
- "loading_indicator.label": "Lade...",
+ "loading_indicator.label": "Lade…",
"status.mention": "Erwähnen",
"status.delete": "Löschen",
"status.reply": "Antworten",
"status.reblog": "Teilen",
"status.favourite": "Favorisieren",
"status.reblogged_by": "{name} teilte",
- "status.sensitive_warning": "Sensible Inhalte",
- "status.sensitive_toggle": "Klicken um zu zeigen",
+ "status.sensitive_warning": "Heikle Inhalte",
+ "status.sensitive_toggle": "Klicke, um sie zu sehen",
"status.open": "Öffnen",
"video_player.toggle_sound": "Ton umschalten",
"account.mention": "Erwähnen",
@@ -20,17 +20,17 @@ const en = {
"account.follow": "Folgen",
"account.posts": "Beiträge",
"account.follows": "Folgt",
- "account.followers": "Folger",
+ "account.followers": "Folgende",
"account.follows_you": "Folgt dir",
"account.requested": "Warte auf Erlaubnis",
"getting_started.heading": "Erste Schritte",
- "getting_started.about_addressing": "Du kannst Leuten folgen, falls du ihren Nutzernamen und ihre Domain kennst, in dem du eine e-mail-artige Addresse in das Suchfeld oben an der Seite eingibst.",
- "getting_started.about_shortcuts": "Falls der Zielnutzer an derselben Domain ist wie du, funktioniert der Nutzername auch alleine. Das gilt auch für Erwähnungen in Beiträgen.",
+ "getting_started.about_addressing": "Du kannst Leuten folgen, falls du ihren Nutzernamen und ihre Domain kennst, in dem du eine e-mail-artige Addresse in das Suchfeld oben auf der Seite eingibst.",
+ "getting_started.about_shortcuts": "Falls die Person auf derselben Domain ist wie du, reicht auch ihr Nutzername alleine. Das gilt auch für Erwähnungen in Beiträgen.",
"getting_started.about_developer": "Der Entwickler des Projekts kann unter Gargron@mastodon.social gefunden werden",
"getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
"column.home": "Home",
"column.mentions": "Erwähnungen",
- "column.public": "Gesamtes Bekanntes Netz",
+ "column.public": "Gesamtes bekanntes Netz",
"column.notifications": "Mitteilungen",
"column.follow_requests": "Folgeanfragen",
"tabs_bar.compose": "Schreiben",
@@ -38,11 +38,11 @@ const en = {
"tabs_bar.mentions": "Erwähnungen",
"tabs_bar.public": "Gesamtes Netz",
"tabs_bar.notifications": "Mitteilungen",
- "compose_form.placeholder": "Worüber möchstest du schreiben?",
+ "compose_form.placeholder": "Worüber möchtest du schreiben?",
"compose_form.publish": "Tröt",
- "compose_form.sensitive": "Medien als sensitiv markieren",
- "compose_form.unlisted": "Öffentlich nicht auflisten",
+ "compose_form.sensitive": "Medien als heikel markieren",
"compose_form.private": "Als privat markieren",
+ "compose_form.unlisted": "Nicht öffentlich auflisten",
"navigation_bar.edit_profile": "Profil bearbeiten",
"navigation_bar.preferences": "Einstellungen",
"navigation_bar.public_timeline": "Öffentlich",
@@ -52,15 +52,15 @@ const en = {
"search.placeholder": "Suche",
"search.account": "Konto",
"search.hashtag": "Hashtag",
- "upload_button.label": "Media-Datei anfügen",
+ "upload_button.label": "Mediendatei hinzufügen",
"upload_form.undo": "Entfernen",
"notification.follow": "{name} folgt dir",
"notification.favourite": "{name} favorisierte deinen Status",
"notification.reblog": "{name} teilte deinen Status",
"notification.mention": "{name} erwähnte dich",
- "notifications.column_settings.alert": "Desktop-Benachrichtigunen",
+ "notifications.column_settings.alert": "Desktop-Benachrichtigungen",
"notifications.column_settings.show": "In der Spalte anzeigen",
- "notifications.column_settings.follow": "Neue Folger:",
+ "notifications.column_settings.follow": "Neue Folgende:",
"notifications.column_settings.favourite": "Favorisierungen:",
"notifications.column_settings.mention": "Erwähnungen:",
"notifications.column_settings.reblog": "Geteilte Beiträge:",
@@ -74,4 +74,4 @@ const en = {
"missing_indicator.label": "Nicht gefunden"
};
-export default en;
+export default de;
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
index 2d3360b6..47e23d98 100644
--- a/app/assets/javascripts/components/locales/en.jsx
+++ b/app/assets/javascripts/components/locales/en.jsx
@@ -1,68 +1,131 @@
+/**
+ * Note for Contributors:
+ * This file (en.jsx) serve as a template for other languages.
+ * To make other contributors' life easier, please REMEMBER:
+ * 1. to add your new string here; and
+ * 2. to remove old strings that are no longer needed; and
+ * 3. to sort the strings by the key.
+ * 4. To rename the `en` const name and export default name to match your locale.
+ * Thanks!
+ */
const en = {
- "column_back_button.label": "Back",
- "lightbox.close": "Close",
- "loading_indicator.label": "Loading...",
- "status.mention": "Mention @{name}",
- "status.delete": "Delete",
- "status.reply": "Reply",
- "status.reblog": "Boost",
- "status.favourite": "Favourite",
- "status.reblogged_by": "{name} boosted",
- "status.sensitive_warning": "Sensitive content",
- "status.sensitive_toggle": "Click to view",
- "video_player.toggle_sound": "Toggle sound",
- "account.mention": "Mention @{name}",
- "account.edit_profile": "Edit profile",
- "account.unblock": "Unblock @{name}",
- "account.unfollow": "Unfollow",
"account.block": "Block @{name}",
+ "account.disclaimer": "This user is from another instance. This number may be larger.",
+ "account.edit_profile": "Edit profile",
"account.follow": "Follow",
- "account.posts": "Posts",
- "account.follows": "Follows",
"account.followers": "Followers",
"account.follows_you": "Follows you",
+ "account.follows": "Follows",
+ "account.mention": "Mention @{name}",
+ "account.mute": "Mute @{name}",
+ "account.posts": "Posts",
+ "account.report": "Report @{name}",
"account.requested": "Awaiting approval",
- "getting_started.heading": "Getting started",
- "getting_started.about_addressing": "You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the search form.",
- "getting_started.about_shortcuts": "If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.",
- "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on github at {github}. {apps}.",
- "column.home": "Home",
+ "account.unblock": "Unblock @{name}",
+ "account.unfollow": "Unfollow",
+ "account.unmute": "Unmute @{name}",
+ "boost_modal.combo": "You can press {combo} to skip this next time",
+ "column_back_button.label": "Back",
+ "column.blocks": "Blocked users",
"column.community": "Local timeline",
- "column.public": "Federated timeline",
+ "column.favourites": "Favourites",
+ "column.follow_requests": "Follow requests",
+ "column.home": "Home",
"column.notifications": "Notifications",
- "tabs_bar.compose": "Compose",
- "tabs_bar.home": "Home",
- "tabs_bar.mentions": "Mentions",
- "tabs_bar.public": "Federated timeline",
- "tabs_bar.notifications": "Notifications",
+ "column.public": "Federated timeline",
"compose_form.placeholder": "What is on your mind?",
+ "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
"compose_form.publish": "Toot",
"compose_form.sensitive": "Mark media as sensitive",
+ "compose_form.spoiler_placeholder": "Content warning",
"compose_form.spoiler": "Hide text behind warning",
- "compose_form.private": "Mark as private",
- "compose_form.privacy_disclaimer": "Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.",
- "compose_form.unlisted": "Do not display on public timelines",
- "navigation_bar.edit_profile": "Edit profile",
- "navigation_bar.preferences": "Preferences",
+ "emoji_button.label": "Insert emoji",
+ "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
+ "empty_column.hashtag": "There is nothing in this hashtag yet.",
+ "empty_column.home.public_timeline": "the public timeline",
+ "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
+ "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",
+ "follow_request.authorize": "Authorize",
+ "follow_request.reject": "Reject",
+ "getting_started.apps": "Various apps are available",
+ "getting_started.heading": "Getting started",
+ "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
+ "home.column_settings.advanced": "Advanced",
+ "home.column_settings.basic": "Basic",
+ "home.column_settings.filter_regex": "Filter out by regular expressions",
+ "home.column_settings.show_reblogs": "Show boosts",
+ "home.column_settings.show_replies": "Show replies",
+ "home.settings": "Column settings",
+ "lightbox.close": "Close",
+ "loading_indicator.label": "Loading...",
+ "media_gallery.toggle_visible": "Toggle visibility",
+ "missing_indicator.label": "Not found",
+ "navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
- "navigation_bar.public_timeline": "Federated timeline",
+ "navigation_bar.edit_profile": "Edit profile",
+ "navigation_bar.favourites": "Favourites",
+ "navigation_bar.follow_requests": "Follow requests",
+ "navigation_bar.info": "Extended information",
"navigation_bar.logout": "Logout",
- "reply_indicator.cancel": "Cancel",
- "search.placeholder": "Search",
- "search.account": "Account",
- "search.hashtag": "Hashtag",
- "upload_button.label": "Add media",
- "upload_form.undo": "Undo",
- "notification.follow": "{name} followed you",
+ "navigation_bar.preferences": "Preferences",
+ "navigation_bar.public_timeline": "Federated timeline",
"notification.favourite": "{name} favourited your status",
+ "notification.follow": "{name} followed you",
"notification.reblog": "{name} boosted your status",
- "notification.mention": "{name} mentioned you",
+ "notifications.clear_confirmation": "Are you sure you want to clear all your notifications?",
+ "notifications.clear": "Clear notifications",
"notifications.column_settings.alert": "Desktop notifications",
- "notifications.column_settings.show": "Show in column",
- "notifications.column_settings.follow": "New followers:",
"notifications.column_settings.favourite": "Favourites:",
+ "notifications.column_settings.follow": "New followers:",
"notifications.column_settings.mention": "Mentions:",
"notifications.column_settings.reblog": "Boosts:",
+ "notifications.column_settings.show": "Show in column",
+ "notifications.column_settings.sound": "Play sound",
+ "notifications.settings": "Column settings",
+ "privacy.change": "Adjust status privacy",
+ "privacy.direct.long": "Post to mentioned users only",
+ "privacy.direct.short": "Direct",
+ "privacy.private.long": "Post to followers only",
+ "privacy.private.short": "Private",
+ "privacy.public.long": "Post to public timelines",
+ "privacy.public.short": "Public",
+ "privacy.unlisted.long": "Do not show in public timelines",
+ "privacy.unlisted.short": "Unlisted",
+ "reply_indicator.cancel": "Cancel",
+ "report.heading": "New report",
+ "report.placeholder": "Additional comments",
+ "report.submit": "Submit",
+ "report.target": "Reporting",
+ "search_results.total": "{count} {count, plural, one {result} other {results}}",
+ "search.placeholder": "Search",
+ "search.status_by": "Status by {name}",
+ "status.delete": "Delete",
+ "status.favourite": "Favourite",
+ "status.load_more": "Load more",
+ "status.media_hidden": "Media hidden",
+ "status.mention": "Mention @{name}",
+ "status.open": "Expand this status",
+ "status.reblog": "Boost",
+ "status.reblogged_by": "{name} boosted",
+ "status.reply": "Reply",
+ "status.report": "Report @{name}",
+ "status.sensitive_toggle": "Click to view",
+ "status.sensitive_warning": "Sensitive content",
+ "status.show_less": "Show less",
+ "status.show_more": "Show more",
+ "tabs_bar.compose": "Compose",
+ "tabs_bar.federated_timeline": "Federated",
+ "tabs_bar.home": "Home",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.notifications": "Notifications",
+ "upload_area.title": "Drag & drop to upload",
+ "upload_button.label": "Add media",
+ "upload_form.undo": "Undo",
+ "upload_progress.label": "Uploading...",
+ "video_player.toggle_sound": "Toggle sound",
+ "video_player.toggle_visible": "Toggle visibility",
+ "video_player.expand": "Expand video",
};
export default en;
diff --git a/app/assets/javascripts/components/locales/eo.jsx b/app/assets/javascripts/components/locales/eo.jsx
new file mode 100644
index 00000000..8c118b31
--- /dev/null
+++ b/app/assets/javascripts/components/locales/eo.jsx
@@ -0,0 +1,68 @@
+const eo = {
+ "column_back_button.label": "Reveni",
+ "lightbox.close": "Fermi",
+ "loading_indicator.label": "Ŝarĝanta...",
+ "status.mention": "Mencii @{name}",
+ "status.delete": "Forigi",
+ "status.reply": "Respondi",
+ "status.reblog": "Diskonigi",
+ "status.favourite": "Favori",
+ "status.reblogged_by": "{name} diskonigita",
+ "status.sensitive_warning": "Tikla enhavo",
+ "status.sensitive_toggle": "Alklaki por vidi",
+ "video_player.toggle_sound": "Aktivigi sonojn",
+ "account.mention": "Mencii @{name}",
+ "account.edit_profile": "Redakti la profilon",
+ "account.unblock": "Malbloki @{name}",
+ "account.unfollow": "Malsekvi",
+ "account.block": "Bloki @{name}",
+ "account.follow": "Sekvi",
+ "account.posts": "Mesaĝoj",
+ "account.follows": "Sekvatoj",
+ "account.followers": "Sekvantoj",
+ "account.follows_you": "Sekvas vin",
+ "account.requested": "Atendas aprobon",
+ "getting_started.heading": "Por komenci",
+ "getting_started.about_addressing": "Vi povas sekvi homojn se vi konas la uzantnomon kaj domajnon tajpinte retpoŝtecan adreson en la serĉilon.",
+ "getting_started.about_shortcuts": "Se la celita uzanto troviĝas en la sama domajno de vi, uzi nur la uzantnomon sufiĉos. La sama regulo validas por mencii aliajn uzantojn en mesaĝo.",
+ "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}. {apps}.",
+ "column.home": "Hejmo",
+ "column.community": "Loka tempolinio",
+ "column.public": "Fratara tempolinio",
+ "column.notifications": "Sciigoj",
+ "tabs_bar.compose": "Ekskribi",
+ "tabs_bar.home": "Hejmo",
+ "tabs_bar.mentions": "Sciigoj",
+ "tabs_bar.public": "Fratara tempolinio",
+ "tabs_bar.notifications": "Sciigoj",
+ "compose_form.placeholder": "Pri kio vi pensas?",
+ "compose_form.publish": "Hup",
+ "compose_form.sensitive": "Marki ke la enhavo estas tikla",
+ "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
+ "compose_form.private": "Marki ke la enhavo estas privata",
+ "compose_form.privacy_disclaimer": "Via privata mesaĝo estos sendita nur al menciitaj uzantoj en {domains}. Ĉu vi fidas {domainsCount, plural, one {tiun servilon} other {tiujn servilojn}}? Mesaĝa privateco funkcias nur en aperaĵoj de Mastodon. Se {domains} {domainsCount, plural, one {ne estas aperaĵo de Mastodon} other {ne estas aperaĵoj de Mastodon}}, estos neniu indiko ke via mesaĝo estas privata, kaj ĝi povus esti diskonigita aŭ videbligita al necelitaj ricevantoj.",
+ "compose_form.unlisted": "Ne afiŝi en publikaj tempolinioj",
+ "navigation_bar.edit_profile": "Redakti la profilon",
+ "navigation_bar.preferences": "Preferoj",
+ "navigation_bar.community_timeline": "Loka tempolinio",
+ "navigation_bar.public_timeline": "Fratara tempolinio",
+ "navigation_bar.logout": "Elsaluti",
+ "reply_indicator.cancel": "Rezigni",
+ "search.placeholder": "Serĉi",
+ "search.account": "Konto",
+ "search.hashtag": "Kradvorto",
+ "upload_button.label": "Aldoni enhavaĵon",
+ "upload_form.undo": "Malfari",
+ "notification.follow": "{name} sekvis vin",
+ "notification.favourite": "{name} favoris vian mesaĝon",
+ "notification.reblog": "{name} diskonigis vian mesaĝon",
+ "notification.mention": "{name} menciis vin",
+ "notifications.column_settings.alert": "Retumilaj atentigoj",
+ "notifications.column_settings.show": "Montri en kolono",
+ "notifications.column_settings.follow": "Novaj sekvantoj:",
+ "notifications.column_settings.favourite": "Favoroj:",
+ "notifications.column_settings.mention": "Mencioj:",
+ "notifications.column_settings.reblog": "Diskonigoj:",
+};
+
+export default eo;
diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx
index b75fb57d..7e9c0dc2 100644
--- a/app/assets/javascripts/components/locales/es.jsx
+++ b/app/assets/javascripts/components/locales/es.jsx
@@ -5,28 +5,35 @@ const es = {
"status.mention": "Mencionar",
"status.delete": "Borrar",
"status.reply": "Responder",
- "status.reblog": "Republicar",
+ "status.reblog": "Retoot",
"status.favourite": "Favorito",
- "status.reblogged_by": "{name} republicado",
+ "status.reblogged_by": "Retooteado por {name}",
+ "status.sensitive_warning": "Contenido sensible",
+ "status.sensitive_toggle": "Click para ver",
+ "status.show_more": "Mostrar más",
+ "status.show_less": "Mostrar menos",
+ "status.open": "Expandir estado",
+ "status.report": "Reportar",
"video_player.toggle_sound": "Act/Desac. sonido",
- "account.mention": "Mención",
+ "account.mention": "Mencionar",
"account.edit_profile": "Editar perfil",
"account.unblock": "Desbloquear",
"account.unfollow": "Dejar de seguir",
+ "account.mute": "Silenciar",
"account.block": "Bloquear",
"account.follow": "Seguir",
- "account.block": "Bloquear",
"account.posts": "Publicaciones",
"account.follows": "Seguir",
"account.followers": "Seguidores",
"account.follows_you": "Te sigue",
+ "account.requested": "Esperando aprobación",
"getting_started.heading": "Primeros pasos",
"getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.",
"getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.",
- "getting_started.about_developer": "Puedes seguir al desarrollador de este proyecto en Gargron@mastodon.social",
+ "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.",
"column.home": "Inicio",
- "column.mentions": "Menciones",
- "column.public": "Historia pública",
+ "column.community": "Historia local",
+ "column.public": "Historia federada",
"column.notifications": "Notificaciones",
"tabs_bar.compose": "Redactar",
"tabs_bar.home": "Inicio",
@@ -34,23 +41,47 @@ const es = {
"tabs_bar.public": "Público",
"tabs_bar.notifications": "Notificaciones",
"compose_form.placeholder": "¿En qué estás pensando?",
- "compose_form.publish": "Publicar",
- "compose_form.sensitive": "Marcar el contenido como sensible",
- "compose_form.unlisted": "Privado",
+ "compose_form.publish": "Tootear",
+ "compose_form.sensitive": "Marcar contenido como sensible",
+ "compose_form.spoiler": "Ocultar texto tras advertencia",
+ "compose_form.spoiler_placeholder": "Advertencia de contenido",
+ "composer_form.private": "Marcar como privado",
+ "composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.",
+ "compose_form.unlisted": "No mostrar en la historia federada",
"navigation_bar.edit_profile": "Editar perfil",
"navigation_bar.preferences": "Preferencias",
- "navigation_bar.public_timeline": "Público",
+ "navigation_bar.community_timeline": "Historia local",
+ "navigation_bar.public_timeline": "Historia federada",
+ "navigation_bar.favourites": "Favoritos",
+ "navigation_bar.blocks": "Usuarios bloqueados",
+ "navigation_bar.info": "Información adicional",
"navigation_bar.logout": "Cerrar sesión",
"reply_indicator.cancel": "Cancelar",
"search.placeholder": "Buscar",
"search.account": "Cuenta",
"search.hashtag": "Etiqueta",
- "upload_button.label": "Añadir medio",
+ "upload_button.label": "Subir multimedia",
"upload_form.undo": "Deshacer",
- "notification.follow": "{name} le esta ahora siguiendo",
- "notification.favourite": "{name} marcó como favorito su estado",
- "notification.reblog": "{name} volvió a publicar su estado",
- "notification.mention": "Fue mencionado por {name}"
+ "notification.follow": "{name} te empezó a seguir",
+ "notification.favourite": "{name} marcó tu estado como favorito",
+ "notification.reblog": "{name} ha retooteado tu estado",
+ "notification.mention": "{name} te ha mencionado",
+ "notifications.column_settings.alert": "Notificaciones de escritorio",
+ "notifications.column_settings.show": "Mostrar en columna",
+ "notifications.column_settings.follow": "Nuevos seguidores:",
+ "notifications.column_settings.favourite": "Favoritos:",
+ "notifications.column_settings.mention": "Menciones:",
+ "notifications.column_settings.reblog": "Retoots:",
+ "emoji_button.label": "Insertar emoji",
+ "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",
+ "privacy.private.short": "Privado",
+ "privacy.private.long": "Sólo mostrar a seguidores",
+ "privacy.direct.short": "Directo",
+ "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
+ "privacy.change": "Ajustar privacidad"
};
export default es;
diff --git a/app/assets/javascripts/components/locales/fi.jsx b/app/assets/javascripts/components/locales/fi.jsx
new file mode 100644
index 00000000..b3ae4bc5
--- /dev/null
+++ b/app/assets/javascripts/components/locales/fi.jsx
@@ -0,0 +1,68 @@
+const fi = {
+ "column_back_button.label": "Takaisin",
+ "lightbox.close": "Sulje",
+ "loading_indicator.label": "Ladataan...",
+ "status.mention": "Mainitse @{name}",
+ "status.delete": "Poista",
+ "status.reply": "Vastaa",
+ "status.reblog": "Buustaa",
+ "status.favourite": "Tykkää",
+ "status.reblogged_by": "{name} buustasi",
+ "status.sensitive_warning": "Arkaluontoista sisältöä",
+ "status.sensitive_toggle": "Klikkaa nähdäksesi",
+ "video_player.toggle_sound": "Äänet päälle/pois",
+ "account.mention": "Mainitse @{name}",
+ "account.edit_profile": "Muokkaa",
+ "account.unblock": "Salli @{name}",
+ "account.unfollow": "Lopeta seuraaminen",
+ "account.block": "Estä @{name}",
+ "account.follow": "Seuraa",
+ "account.posts": "Postit",
+ "account.follows": "Seuraa",
+ "account.followers": "Seuraajia",
+ "account.follows_you": "Seuraa sinua",
+ "account.requested": "Odottaa hyväksyntää",
+ "getting_started.heading": "Aloitus",
+ "getting_started.about_addressing": "Voit seurata ihmisiä jos tiedät heidän käyttäjänimensä ja domainin missä he ovat syöttämällä e-mail-esque osoitteen Etsi kenttään.",
+ "getting_started.about_shortcuts": "Jos etsimäsi henkilö on samassa domainissa kuin sinä, pelkkä käyttäjänimi kelpaa. Sama pätee kun mainitset ihmisiä statuksessasi",
+ "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}. {apps}.",
+ "column.home": "Koti",
+ "column.community": "Paikallinen aikajana",
+ "column.public": "Yleinen aikajana",
+ "column.notifications": "Ilmoitukset",
+ "tabs_bar.compose": "Luo",
+ "tabs_bar.home": "Koti",
+ "tabs_bar.mentions": "Maininnat",
+ "tabs_bar.public": "Yleinen aikajana",
+ "tabs_bar.notifications": "Ilmoitukset",
+ "compose_form.placeholder": "Mitä sinulla on mielessä?",
+ "compose_form.publish": "Toot",
+ "compose_form.sensitive": "Merkitse media herkäksi",
+ "compose_form.spoiler": "Piiloita teksti varoituksen taakse",
+ "compose_form.private": "Merkitse yksityiseksi",
+ "compose_form.privacy_disclaimer": "Sinun yksityinen status toimitetaan mainitsemallesi käyttäjille domaineissa {domains}. Luotatko {domainsCount, plural, one {tähän palvelimeen} other {näihin palvelimiin}}? Postauksen yksityisyys toimii van Mastodon palvelimilla. Jos {domains} {domainsCount, plural, one {ei ole Mastodon palvelin} other {eivät ole Mastodon palvelin}}, viestiin ei tule Yksityinen-merkintää, ja sitä voidaan boostata tai muuten tehdä näkyväksi muille vastaanottajille.",
+ "compose_form.unlisted": "Älä näytä yleisillä aikajanoilla",
+ "navigation_bar.edit_profile": "Muokkaa profiilia",
+ "navigation_bar.preferences": "Ominaisuudet",
+ "navigation_bar.community_timeline": "Paikallinen aikajana",
+ "navigation_bar.public_timeline": "Yleinen aikajana",
+ "navigation_bar.logout": "Kirjaudu ulos",
+ "reply_indicator.cancel": "Peruuta",
+ "search.placeholder": "Hae",
+ "search.account": "Tili",
+ "search.hashtag": "Hashtag",
+ "upload_button.label": "Lisää mediaa",
+ "upload_form.undo": "Peru",
+ "notification.follow": "{name} seurasi sinua",
+ "notification.favourite": "{name} tykkäsi statuksestasi",
+ "notification.reblog": "{name} buustasi statustasi",
+ "notification.mention": "{name} mainitsi sinut",
+ "notifications.column_settings.alert": "Työpöytä ilmoitukset",
+ "notifications.column_settings.show": "Näytä sarakkeessa",
+ "notifications.column_settings.follow": "Uusia seuraajia:",
+ "notifications.column_settings.favourite": "Tykkäyksiä:",
+ "notifications.column_settings.mention": "Mainintoja:",
+ "notifications.column_settings.reblog": "Buusteja:",
+};
+
+export default fi;
diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx
index 23fa9349..8838c264 100644
--- a/app/assets/javascripts/components/locales/fr.jsx
+++ b/app/assets/javascripts/components/locales/fr.jsx
@@ -10,7 +10,14 @@ const fr = {
"status.reblogged_by": "{name} a partagé :",
"status.sensitive_warning": "Contenu délicat",
"status.sensitive_toggle": "Cliquer pour dévoiler",
+ "status.show_more": "Déplier",
+ "status.show_less": "Replier",
+ "status.open": "Déplier ce statut",
+ "status.report": "Signaler @{name}",
+ "status.load_more": "Charger plus",
+ "status.media_hidden": "Média caché",
"video_player.toggle_sound": "Mettre/Couper le son",
+ "video_player.toggle_visible": "Afficher/Cacher la vidéo",
"account.mention": "Mentionner",
"account.edit_profile": "Modifier le profil",
"account.unblock": "Débloquer",
@@ -27,7 +34,7 @@ const fr = {
"account.report": "Signaler",
"account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
"getting_started.heading": "Pour commencer",
- "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champs de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.",
+ "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.",
"getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.",
"getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social",
"getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
@@ -35,18 +42,27 @@ const fr = {
"column.community": "Fil public local",
"column.public": "Fil public global",
"column.notifications": "Notifications",
- "column.public": "Fil public",
"column.blocks": "Utilisateurs bloqués",
"column.favourites": "Favoris",
+ "column.follow_requests": "Demandes de suivi",
+ "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.",
+ "empty_column.public": "Il n'y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs d'autres instances pour remplir le fil public.",
+ "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d'autres utilisateurs.",
+ "empty_column.home.public_timeline": "le fil public",
+ "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !",
+ "empty_column.hashtag": "Il n'y a encore aucun contenu relatif à ce hashtag",
"tabs_bar.compose": "Composer",
"tabs_bar.home": "Accueil",
"tabs_bar.mentions": "Mentions",
"tabs_bar.public": "Fil public global",
"tabs_bar.notifications": "Notifications",
+ "tabs_bar.local_timeline": "Fil public local",
+ "tabs_bar.federated_timeline": "Fil public global",
"compose_form.placeholder": "Qu’avez-vous en tête ?",
- "compose_form.publish": "Pouet ",
+ "compose_form.publish": "Pouet",
"compose_form.sensitive": "Marquer le média comme délicat",
- "compose_form.spoiler": "Masquer le texte par un avertissement",
+ "compose_form.spoiler": "Masquer le texte derrière un avertissement",
+ "compose_form.spoiler_placeholder": "Avertissement",
"compose_form.private": "Rendre privé",
"compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodons. Si {domains} {domainsCount, plural, one {n'est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n'y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d'une autre manière à d'autres personnes imprévues",
"compose_form.unlisted": "Ne pas afficher dans les fils publics",
@@ -58,25 +74,32 @@ const fr = {
"navigation_bar.blocks": "Utilisateurs bloqués",
"navigation_bar.favourites": "Favoris",
"navigation_bar.info": "Plus d'informations",
- "notification.favourite": "{name} a ajouté à ses favoris :",
"navigation_bar.logout": "Déconnexion",
+ "navigation_bar.follow_requests": "Demandes de suivi",
"reply_indicator.cancel": "Annuler",
- "search.placeholder": "Chercher",
+ "search.placeholder": "Rechercher",
"search.account": "Compte",
"search.hashtag": "Mot-clé",
"search_results.total": "{count} {count, plural, one {résultat} other {résultats}}",
+ "search.status_by": "Statuts de {name}",
"upload_button.label": "Joindre un média",
"upload_form.undo": "Annuler",
+ "upload_progress.label": "Envoi en cours…",
+ "upload_area.title": "Glissez et déposez pour envoyer",
"notification.follow": "{name} vous suit.",
"notification.favourite": "{name} a ajouté à ses favoris :",
"notification.reblog": "{name} a partagé votre statut :",
"notification.mention": "{name} vous a mentionné⋅e :",
"notifications.column_settings.alert": "Notifications locales",
"notifications.column_settings.show": "Afficher dans la colonne",
+ "notifications.column_settings.sound": "Émettre un son",
"notifications.column_settings.follow": "Nouveaux abonnés :",
"notifications.column_settings.favourite": "Favoris :",
"notifications.column_settings.mention": "Mentions :",
"notifications.column_settings.reblog": "Partages :",
+ "notifications.clear": "Nettoyer",
+ "notifications.clear_confirmation": "Voulez-vous vraiment nettoyer toutes vos notifications ?",
+ "notifications.settings": "Paramètres de la colonne",
"privacy.public.short": "Public",
"privacy.public.long": "Afficher dans les fils publics",
"privacy.unlisted.short": "Non-listé",
@@ -84,8 +107,22 @@ const fr = {
"privacy.private.short": "Privé",
"privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s",
"privacy.direct.short": "Direct",
- "privacy.direct.long": "N’afficher que pour les personnes mentionné⋅e⋅s",
+ "privacy.direct.long": "N’afficher que pour les personnes mentionnées",
"privacy.change": "Ajuster la confidentialité du message",
+ "media_gallery.toggle_visible": "Modifier la visibilité",
+ "missing_indicator.label": "Non trouvé",
+ "follow_request.authorize": "Autoriser",
+ "follow_request.reject": "Rejeter",
+ "home.settings": "Paramètres de la colonne",
+ "home.column_settings.basic": "Basique",
+ "home.column_settings.show_reblogs": "Afficher les partages",
+ "home.column_settings.show_replies": "Afficher les réponses",
+ "home.column_settings.advanced": "Avancé",
+ "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
+ "report.heading": "Nouveau signalement",
+ "report.placeholder": "Commentaires additionnels",
+ "report.submit": "Envoyer",
+ "report.target": "Signalement"
};
export default fr;
diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx
index 203929d6..7525022b 100644
--- a/app/assets/javascripts/components/locales/index.jsx
+++ b/app/assets/javascripts/components/locales/index.jsx
@@ -3,8 +3,16 @@ import de from './de';
import es from './es';
import hu from './hu';
import fr from './fr';
+import nl from './nl';
+import no from './no';
import pt from './pt';
import uk from './uk';
+import fi from './fi';
+import eo from './eo';
+import ru from './ru';
+import ja from './ja';
+import zh_hk from './zh-hk';
+import bg from './bg';
const locales = {
en,
@@ -12,8 +20,16 @@ const locales = {
es,
hu,
fr,
+ nl,
+ no,
pt,
- uk
+ uk,
+ fi,
+ eo,
+ ru,
+ ja,
+ 'zh-HK': zh_hk,
+ bg,
};
export default function getMessagesForLocale (locale) {
diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx
new file mode 100644
index 00000000..565e2e6c
--- /dev/null
+++ b/app/assets/javascripts/components/locales/ja.jsx
@@ -0,0 +1,119 @@
+const ja = {
+ "column_back_button.label": "戻る",
+ "lightbox.close": "閉じる",
+ "loading_indicator.label": "読み込み中...",
+ "status.mention": "@{name} さんへの返信",
+ "status.delete": "削除",
+ "status.reply": "返信",
+ "status.reblog": "ブースト",
+ "status.favourite": "お気に入り",
+ "status.reblogged_by": "{name} さんにブーストされました",
+ "status.sensitive_warning": "不適切なコンテンツ",
+ "status.sensitive_toggle": "クリックして表示",
+ "status.show_more": "もっと見る",
+ "status.load_more": "もっと見る",
+ "status.show_less": "隠す",
+ "status.open": "Expand this status",
+ "status.report": "@{name} さんを通報",
+ "status.media_hidden": "非表示のメデイア",
+ "video_player.toggle_sound": "音の切り替え",
+ "account.mention": "@{name} さんに返信",
+ "account.edit_profile": "プロフィールを編集",
+ "account.unblock": "@{name} さんのブロックを解除",
+ "account.unfollow": "フォロー解除",
+ "account.block": "@{name} さんをブロック",
+ "account.mute": "ミュート",
+ "account.unmute": "ミュート解除",
+ "account.follow": "フォロー",
+ "account.report": "@{name}を通報する",
+ "account.posts": "投稿",
+ "account.follows": "フォロー",
+ "account.followers": "フォロワー",
+ "account.follows_you": "フォローされています",
+ "account.requested": "承認待ち",
+ "follow_request.authorize": "許可",
+ "follow_request.reject": "拒否",
+ "getting_started.heading": "スタート",
+ "getting_started.about_addressing": "ドメインとユーザー名を知っているなら検索フォームに入力すればフォローできます。",
+ "getting_started.about_shortcuts": "対象のアカウントがあなたと同じドメインのユーザーならばユーザー名のみで検索できます。これは返信のときも一緒です。",
+ "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
+ "column.home": "ホーム",
+ "column.community": "ローカルタイムライン",
+ "column.public": "連合タイムライン",
+ "column.notifications": "通知",
+ "column.favourites": "お気に入り",
+ "tabs_bar.compose": "投稿",
+ "tabs_bar.home": "ホーム",
+ "tabs_bar.mentions": "返信",
+ "tabs_bar.local_timeline": "ローカル",
+ "tabs_bar.federated_timeline": "連合",
+ "tabs_bar.notifications": "通知",
+ "compose_form.placeholder": "今なにしてる?",
+ "compose_form.publish": "トゥート",
+ "compose_form.sensitive": "メディアを不適切なコンテンツとしてマークする",
+ "compose_form.spoiler": "テキストを隠す",
+ "compose_form.spoiler_placeholder": "内容注意メッセージ",
+ "compose_form.private": "非公開にする",
+ "compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先のユーザー(at {domains})に公開されます。{domainsCount, plural, one {that server} other {those servers}}を信頼しますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 もし{domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}ならばあなたの投稿のプライバシーは保護されず、ブーストされたり予期しないユーザーに見られる可能性があります。",
+ "compose_form.unlisted": "公開タイムラインに表示しない",
+ "privacy.public.short": "公開",
+ "privacy.public.long": "公開TLに投稿する",
+ "privacy.unlisted.short": "未収載",
+ "privacy.unlisted.long": "公開TLで表示しない",
+ "privacy.private.short": "非公開",
+ "privacy.private.long": "フォロワーだけに公開",
+ "privacy.direct.short": "ダイレクト",
+ "privacy.direct.long": "含んだユーザーだけに公開",
+ "privacy.change": "投稿のプライバシーを変更2",
+ "report.heading": "新規通報",
+ "report.placeholder": "コメント",
+ "report.target": "問題のユーザー",
+ "report.submit": "通報する",
+ "navigation_bar.edit_profile": "プロフィールを編集",
+ "navigation_bar.preferences": "ユーザー設定",
+ "navigation_bar.community_timeline": "ローカルタイムライン",
+ "navigation_bar.public_timeline": "連合タイムライン",
+ "navigation_bar.logout": "ログアウト",
+ "navigation_bar.favourites": "お気に入り",
+ "navigation_bar.blocks": "ブロックしたユーザー",
+ "navigation_bar.info": "サーバー情報",
+ "reply_indicator.cancel": "キャンセル",
+ "search.placeholder": "検索",
+ "search.account": "アカウント",
+ "search.hashtag": "ハッシュタグ",
+ "search.status_by": "{uuuname}からの投稿",
+ "upload_area.title": "ファイルをこちらにドラッグしてください",
+ "upload_button.label": "メディアを追加",
+ "upload_form.undo": "やり直す",
+ "notification.follow": "{name} さんにフォローされました",
+ "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
+ "notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
+ "notification.mention": "{name} さんがあなたに返信しました",
+ "notifications.clear": "通知を片付ける",
+ "notifications.clear_confirmation": "通知を全部片付けます。大丈夫ですか?",
+ "notifications.column_settings.alert": "デスクトップ通知",
+ "notifications.column_settings.show": "カラムに表示",
+ "notifications.column_settings.follow": "新しいフォロワー",
+ "notifications.column_settings.favourite": "お気に入り",
+ "notifications.column_settings.mention": "返信",
+ "notifications.column_settings.reblog": "ブースト",
+ "notifications.column_settings.sound": "通知音を再生",
+ "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
+ "empty_column.home.public_timeline": "連合タイムライン",
+ "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
+ "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
+ "empty_column.hashtag": "このハッシュタグはまだ使っていません。",
+ "upload_progress.label": "アップロード中…",
+ "emoji_button.label": "絵文字を追加",
+ "home.column_settings.basic": "シンプル",
+ "home.column_settings.advanced": "エキスパート",
+ "home.column_settings.show_reblogs": "ブースト表示",
+ "home.column_settings.show_replies": "返信表示",
+ "home.column_settings.filter_regex": "正規表現でフィルター",
+ "home.settings": "カラム設定",
+ "notification.settings": "カラム設定",
+ "missing_indicator.label": "見つかりません",
+ "boost_modal.combo": "次は{combo}を押せば、これをスキップできます。"
+};
+
+export default ja;
diff --git a/app/assets/javascripts/components/locales/nl.jsx b/app/assets/javascripts/components/locales/nl.jsx
new file mode 100644
index 00000000..cc80854f
--- /dev/null
+++ b/app/assets/javascripts/components/locales/nl.jsx
@@ -0,0 +1,68 @@
+const nl = {
+ "column_back_button.label": "terug",
+ "lightbox.close": "Sluiten",
+ "loading_indicator.label": "Laden...",
+ "status.mention": "Vermeld @{name}",
+ "status.delete": "Verwijder",
+ "status.reply": "Reageer",
+ "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",
+ "account.posts": "Berichten",
+ "account.follows": "Volgt",
+ "account.followers": "Volgers",
+ "account.follows_you": "Volgt jou",
+ "account.requested": "Wacht op goedkeuring",
+ "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}.",
+ "column.home": "Thuis",
+ "column.community": "Lokale tijdlijn",
+ "column.public": "Federatietijdlijn",
+ "column.notifications": "Meldingen",
+ "tabs_bar.compose": "Schrijven",
+ "tabs_bar.home": "Thuis",
+ "tabs_bar.mentions": "Vermeldingen",
+ "tabs_bar.public": "Federatietijdlijn",
+ "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",
+ "navigation_bar.preferences": "Voorkeuren",
+ "navigation_bar.community_timeline": "Lokale tijdlijn",
+ "navigation_bar.public_timeline": "Federatietijdlijn",
+ "navigation_bar.logout": "Uitloggen",
+ "reply_indicator.cancel": "Annuleren",
+ "search.placeholder": "Zoeken",
+ "search.account": "Account",
+ "search.hashtag": "Hashtag",
+ "upload_button.label": "Toevoegen media",
+ "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.follow": "Nieuwe volgers:",
+ "notifications.column_settings.favourite": "Favoriten:",
+ "notifications.column_settings.mention": "Vermeldingen:",
+ "notifications.column_settings.reblog": "Boosts:",
+};
+
+export default nl;
diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx
new file mode 100644
index 00000000..64b53fb1
--- /dev/null
+++ b/app/assets/javascripts/components/locales/no.jsx
@@ -0,0 +1,77 @@
+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.edit_profile": "Rediger profil",
+ "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",
+ "column.blocks": "Blokkerte brukere",
+ "column.favourites": "Likt",
+ "tabs_bar.compose": "Komponer",
+ "tabs_bar.home": "Hjem",
+ "tabs_bar.mentions": "Nevninger",
+ "tabs_bar.public": "Forent tidslinje",
+ "tabs_bar.notifications": "Varslinger",
+ "compose_form.placeholder": "Hva har du på hjertet?",
+ "compose_form.publish": "Tut",
+ "compose_form.sensitive": "Merk media som følsomt",
+ "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",
+ "navigation_bar.blocks": "Blokkerte brukere",
+ "navigation_bar.info": "Utvidet informasjon",
+ "navigation_bar.favourites": "Likt",
+ "reply_indicator.cancel": "Avbryt",
+ "search.placeholder": "Søk",
+ "search.account": "Konto",
+ "search.hashtag": "Hashtag",
+ "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:",
+};
+
+export default no;
diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx
index d68724b1..cd345a58 100644
--- a/app/assets/javascripts/components/locales/pt.jsx
+++ b/app/assets/javascripts/components/locales/pt.jsx
@@ -2,54 +2,127 @@ const pt = {
"column_back_button.label": "Voltar",
"lightbox.close": "Fechar",
"loading_indicator.label": "Carregando...",
- "status.mention": "Menção",
- "status.delete": "Deletar",
+ "status.mention": "Mencionar @{name}",
+ "status.delete": "Eliminar",
"status.reply": "Responder",
- "status.reblog": "Reblogar",
- "status.favourite": "Favoritar",
- "status.reblogged_by": "{name} reblogou",
- "video_player.toggle_sound": "Alterar som",
- "account.mention": "Menção",
+ "status.reblog": "Partilhar",
+ "status.favourite": "Adicionar aos favoritos",
+ "status.reblogged_by": "{name} partilhou",
+ "status.sensitive_warning": "Conteúdo sensível",
+ "status.sensitive_toggle": "Clique para ver",
+ "status.show_more": "Mostrar mais",
+ "status.show_less": "Mostrar menos",
+ "status.open": "Expandir",
+ "status.report": "Reportar @{name}",
+ "status.load_more": "Carregar mais",
+ "status.media_hidden": "Media escondida",
+ "video_player.toggle_sound": "Ligar/Desligar som",
+ "video_player.toggle_visible": "Ligar/Desligar vídeo",
+ "account.mention": "Mencionar @{name}",
"account.edit_profile": "Editar perfil",
- "account.unblock": "Desbloquear",
- "account.unfollow": "Unfollow",
- "account.block": "Bloquear",
+ "account.unblock": "Não bloquear @{name}",
+ "account.unfollow": "Não seguir",
+ "account.block": "Bloquear @{name}",
+ "account.mute": "Mute",
+ "account.unmute": "Remover Mute",
"account.follow": "Seguir",
- "account.block": "Bloquear",
"account.posts": "Posts",
"account.follows": "Segue",
"account.followers": "Seguidores",
- "account.follows_you": "Segue você",
+ "account.follows_you": "É teu seguidor",
+ "account.requested": "A aguardar aprovação",
+ "account.report": "Denunciar",
+ "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
"getting_started.heading": "Primeiros passos",
- "getting_started.about_addressing": "Podes seguir pessoas se sabes o nome de usuário deles e o domínio em que estão entrando um endereço similar a e-mail no campo no topo da barra lateral.",
+ "getting_started.about_addressing": "Podes seguir pessoas se sabes o nome de usuário deles e o domínio em que estão colocando um endereço similar a e-mail no campo no topo da barra lateral.",
"getting_started.about_shortcuts": "Se o usuário alvo está no mesmo domínio, só o nome funcionará. A mesma regra se aplica a mencionar pessoas nas postagens.",
- "getting_started.about_developer": "O desenvolvedor desse projeto pode ser seguido em Gargron@mastodon.social",
+ "getting_started.about_developer": "Pode seguir o developer deste projecto em Gargron@mastodon.social",
+ "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
"column.home": "Home",
- "column.mentions": "Menções",
- "column.public": "Público",
- "tabs_bar.compose": "Compôr",
+ "column.community": "Local",
+ "column.public": "Global",
+ "column.notifications": "Notificações",
+ "column.blocks": "Utilizadores Bloqueados",
+ "column.favourites": "Favoritos",
+ "column.follow_requests": "Seguidores Pendentes",
+ "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
+ "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
+ "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
+ "empty_column.home.public_timeline": "global",
+ "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
+ "empty_column.hashtag": "Não existe qualquer conteúdo com essa hashtag",
+ "tabs_bar.compose": "Criar",
"tabs_bar.home": "Home",
"tabs_bar.mentions": "Menções",
"tabs_bar.public": "Público",
"tabs_bar.notifications": "Notificações",
- "compose_form.placeholder": "Que estás pensando?",
+ "tabs_bar.local_timeline": "Local",
+ "tabs_bar.federated_timeline": "Global",
+ "compose_form.placeholder": "Em que estás a pensar?",
"compose_form.publish": "Publicar",
- "compose_form.sensitive": "Marcar conteúdo como sensível",
- "compose_form.unlisted": "Modo não-listado",
+ "compose_form.sensitive": "Marcar media como conteúdo sensível",
+ "compose_form.spoiler": "Esconder texto com aviso",
+ "compose_form.spoiler_placeholder": "Aviso",
+ "compose_form.private": "Tornar privado",
+ "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
+ "compose_form.unlisted": "Não mostrar na listagem pública",
+ "emoji_button.label": "Inserir Emoji",
"navigation_bar.edit_profile": "Editar perfil",
"navigation_bar.preferences": "Preferências",
- "navigation_bar.public_timeline": "Timeline Pública",
- "navigation_bar.logout": "Logout",
+ "navigation_bar.community_timeline": "Local",
+ "navigation_bar.public_timeline": "Global",
+ "navigation_bar.blocks": "Utilizadores bloqueados",
+ "navigation_bar.favourites": "Favoritos",
+ "navigation_bar.info": "Mais informações",
+ "navigation_bar.logout": "Sair",
+ "navigation_bar.follow_requests": "Seguidores pendentes",
"reply_indicator.cancel": "Cancelar",
- "search.placeholder": "Busca",
+ "search.placeholder": "Pesquisar",
"search.account": "Conta",
"search.hashtag": "Hashtag",
+ "search_results.total": "{count} {count, plural, one {resultado} other {resultados}}",
+ "search.status_by": "Post de {name}",
"upload_button.label": "Adicionar media",
- "upload_form.undo": "Desfazer",
- "notification.follow": "{name} seguiu você",
- "notification.favourite": "{name} favoritou seu post",
- "notification.reblog": "{name} reblogou o seu post",
- "notification.mention": "{name} mecionou você"
+ "upload_form.undo": "Anular",
+ "upload_progress.label": "A gravar…",
+ "upload_area.title": "Arraste e solte para enviar",
+ "notification.follow": "{name} seguiu-te",
+ "notification.favourite": "{name} adicionou o teu post aos favoritos",
+ "notification.reblog": "{name} partilhou o teu post",
+ "notification.mention": "{name} mencionou-te",
+ "notifications.column_settings.alert": "Notificações no computador",
+ "notifications.column_settings.show": "Mostrar nas colunas",
+ "notifications.column_settings.sound": "Reproduzir som",
+ "notifications.column_settings.follow": "Novos seguidores:",
+ "notifications.column_settings.favourite": "Favoritos:",
+ "notifications.column_settings.mention": "Menções:",
+ "notifications.column_settings.reblog": "Partilhas:",
+ "notifications.clear": "Limpar notificações",
+ "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
+ "notifications.settings": "Parâmetros da lista de Notificações",
+ "privacy.public.short": "Público",
+ "privacy.public.long": "Publicar em todos os feeds",
+ "privacy.unlisted.short": "Não listar",
+ "privacy.unlisted.long": "Não publicar nos feeds públicos",
+ "privacy.private.short": "Privado",
+ "privacy.private.long": "Apenas para os seguidores",
+ "privacy.direct.short": "Directo",
+ "privacy.direct.long": "Apenas para utilizadores mencionados",
+ "privacy.change": "Ajustar a privacidade da mensagem",
+ "media_gallery.toggle_visible": "Modificar a visibilidade",
+ "missing_indicator.label": "Não encontrado",
+ "follow_request.authorize": "Autorizar",
+ "follow_request.reject": "Rejeitar",
+ "home.settings": "Parâmetros da coluna Home",
+ "home.column_settings.basic": "Básico",
+ "home.column_settings.show_reblogs": "Mostrar as partilhas",
+ "home.column_settings.show_replies": "Mostrar as respostas",
+ "home.column_settings.advanced": "Avançadas",
+ "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
+ "report.heading": "Nova denuncia",
+ "report.placeholder": "Comentários adicionais",
+ "report.submit": "Enviar",
+ "report.target": "Denunciar"
};
export default pt;
diff --git a/app/assets/javascripts/components/locales/ru.jsx b/app/assets/javascripts/components/locales/ru.jsx
new file mode 100644
index 00000000..30a92df8
--- /dev/null
+++ b/app/assets/javascripts/components/locales/ru.jsx
@@ -0,0 +1,101 @@
+const ru = {
+ "column_back_button.label": "Назад",
+ "lightbox.close": "Закрыть",
+ "loading_indicator.label": "Загрузка...",
+ "status.mention": "Упомянуть @{name}",
+ "status.delete": "Удалить",
+ "status.reply": "Ответить",
+ "status.reblog": "Продвинуть",
+ "status.favourite": "Нравится",
+ "status.reblogged_by": "{name} продвинул(а)",
+ "status.sensitive_warning": "Чувствительный контент",
+ "status.sensitive_toggle": "Нажмите для просмотра",
+ "status.show_more": "Развернуть",
+ "status.show_less": "Свернуть",
+ "status.open": "Развернуть статус",
+ "status.report": "Пожаловаться",
+ "status.load_more": "Показать еще",
+ "video_player.toggle_sound": "Вкл./выкл. звук",
+ "account.mention": "Упомянуть",
+ "account.edit_profile": "Изменить профиль",
+ "account.unblock": "Разблокировать",
+ "account.unfollow": "Отписаться",
+ "account.block": "Блокировать",
+ "account.mute": "Заглушить",
+ "account.follow": "Подписаться",
+ "account.posts": "Посты",
+ "account.follows": "Подписки",
+ "account.followers": "Подписаны",
+ "account.follows_you": "Подписан(а) на Вас",
+ "account.requested": "Ожидает подтверждения",
+ "getting_started.heading": "Добро пожаловать",
+ "getting_started.about_addressing": "Вы можете подписаться на человека, зная имя пользователя и домен, на котором он находится, введя e-mail-подобный адрес в форму поиска.",
+ "getting_started.about_shortcuts": "Если пользователь находится на одном с Вами домене, можно использовать только имя. То же правило применимо к упоминанию пользователей в статусах.",
+ "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.",
+ "getting_started.apps": "Доступны различные приложения.",
+ "column.home": "Главная",
+ "column.community": "Локальная лента",
+ "column.public": "Глобальная лента",
+ "column.notifications": "Уведомления",
+ "tabs_bar.compose": "Написать",
+ "tabs_bar.home": "Главная",
+ "tabs_bar.mentions": "Упоминания",
+ "tabs_bar.public": "Глобальная лента",
+ "tabs_bar.notifications": "Уведомления",
+ "compose_form.placeholder": "О чем Вы думаете?",
+ "compose_form.publish": "Трубить",
+ "compose_form.sensitive": "Отметить как чувствительный контент",
+ "compose_form.spoiler": "Скрыть текст за предупреждением",
+ "compose_form.private": "Отметить как приватное",
+ "compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
+ "compose_form.unlisted": "Не отображать в публичных лентах",
+ "navigation_bar.edit_profile": "Изменить профиль",
+ "navigation_bar.preferences": "Опции",
+ "navigation_bar.community_timeline": "Локальная лента",
+ "navigation_bar.public_timeline": "Глобальная лента",
+ "navigation_bar.logout": "Выйти",
+ "navigation_bar.info": "Об узле",
+ "navigation_bar.favourites": "Понравившееся",
+ "navigation_bar.blocks": "Список блокировки",
+ "reply_indicator.cancel": "Отмена",
+ "search.placeholder": "Поиск",
+ "search.account": "Аккаунт",
+ "search.hashtag": "Хэштег",
+ "upload_button.label": "Добавить медиаконтент",
+ "upload_form.undo": "Отменить",
+ "notification.follow": "{name} подписался(-лась) на Вас",
+ "notification.favourite": "{name} понравился Ваш статус",
+ "notification.reblog": "{name} продвинул(а) Ваш статус",
+ "notification.mention": "{name} упомянул(а) Вас",
+ "home.settings": "Настройки колонки",
+ "home.column_settings.basic": "Основные",
+ "home.column_settings.advanced": "Дополнительные",
+ "home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
+ "home.column_settings.show_replies": "Показывать продвижения",
+ "home.column_settings.show_replies": "Показывать ответы",
+ "notifications.clear": "Очистить уведомления",
+ "notifications.settings": "Настройки колонки",
+ "notifications.column_settings.alert": "Десктопные уведомления",
+ "notifications.column_settings.show": "Показывать в колонке",
+ "notifications.column_settings.follow": "Новые подписчики:",
+ "notifications.column_settings.favourite": "Нравится:",
+ "notifications.column_settings.mention": "Упоминания:",
+ "notifications.column_settings.reblog": "Продвижения:",
+ "notifications.column_settings.sound": "Проигрывать звук",
+ "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
+ "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
+ "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
+ "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
+ "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
+ "empty_column.home.public_timeline": "публичные ленты",
+ "privacy.public.short": "Публичный",
+ "privacy.public.long": "Показать в публичных лентах",
+ "privacy.unlisted.short": "Скрытый",
+ "privacy.unlisted.long": "Не показывать в лентах",
+ "privacy.private.short": "Приватный",
+ "privacy.private.long": "Показать только подписчикам",
+ "privacy.direct.short": "Направленный",
+ "privacy.direct.long": "Показать только упомянутым",
+};
+
+export default ru;
diff --git a/app/assets/javascripts/components/locales/zh-hk.jsx b/app/assets/javascripts/components/locales/zh-hk.jsx
new file mode 100644
index 00000000..b26a8c3d
--- /dev/null
+++ b/app/assets/javascripts/components/locales/zh-hk.jsx
@@ -0,0 +1,113 @@
+import zh from 'react-intl/locale-data/zh';
+
+const localeData = zh.reduce(function (acc, localeData) {
+ if (localeData.locale === "zh-Hant-HK") {
+ // rename the locale "zh-Hant-HK" as "zh-HK"
+ // (match the code usually used in Accepted-Language header)
+ acc.push(Object.assign({},
+ localeData,
+ {
+ "locale": "zh-HK",
+ "parentLocale": "zh-Hant-HK",
+ }
+ ));
+ }
+ return acc;
+}, []);
+
+export { localeData as localeData };
+
+const zh_hk = {
+ "account.block": "封鎖 @{name}",
+ "account.edit_profile": "修改個人資料",
+ "account.follow": "關注",
+ "account.followers": "關注的人",
+ "account.follows_you": "關注你",
+ "account.follows": "正在關注",
+ "account.mention": "提及 @{name}",
+ "account.posts": "文章",
+ "account.requested": "等候審批",
+ "account.unblock": "解除對 @{name} 的封鎖",
+ "account.unfollow": "取消關注",
+ "column_back_button.label": "先前顯示",
+ "column.community": "本站時間軸",
+ "column.home": "家",
+ "column.notifications": "通知",
+ "column.public": "跨站公共時間軸",
+ "compose_form.placeholder": "你在想甚麼?",
+ "compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任 {domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。",
+ "compose_form.private": "標示為「只有關注你的人能看」",
+ "compose_form.publish": "發文",
+ "compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
+ "compose_form.spoiler": "將部份文字藏於警告訊息之後",
+ "compose_form.unlisted": "請勿在公共時間軸顯示",
+ "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.",
+ "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.basic": "基本",
+ "home.column_settings.show_reblogs": "顯示被轉推的文章",
+ "home.column_settings.show_replies": "顯示回應文章",
+ "home.column_settings.advanced": "進階",
+ "lightbox.close": "關閉",
+ "loading_indicator.label": "載入中...",
+ "missing_indicator.label": "找不到內容",
+ "navigation_bar.community_timeline": "本站時間軸",
+ "navigation_bar.edit_profile": "修改個人資料",
+ "navigation_bar.logout": "登出",
+ "navigation_bar.preferences": "個人設定",
+ "navigation_bar.public_timeline": "跨站公共時間軸",
+ "notification.favourite": "{name} 喜歡你的文章",
+ "notification.follow": "{name} 開始開始你",
+ "notification.mention": "{name} 提及你",
+ "notification.reblog": "{name} 轉推你的文章",
+ "notifications.column_settings.alert": "顯示桌面通知",
+ "notifications.column_settings.favourite": "喜歡你的文章:",
+ "notifications.column_settings.follow": "關注你:",
+ "notifications.column_settings.mention": "提及你:",
+ "notifications.column_settings.reblog": "轉推你的文章:",
+ "notifications.column_settings.show": "在通知欄顯示",
+ "notifications.column_settings.sound": "播放音效",
+ "reply_indicator.cancel": "取消",
+ "report.target": "Reporting",
+ "search.account": "用戶",
+ "search.hashtag": "標籤",
+ "search.placeholder": "搜尋",
+ "search_results.total": "{count} 項結果",
+ "search.status_by": "按用戶名稱搜尋文章",
+ "status.delete": "刪除",
+ "status.favourite": "喜歡",
+ "status.load_more": "載入更多",
+ "status.media_hidden": "隱藏媒體內容",
+ "status.mention": "提及 @{name}",
+ "status.open": "展開文章",
+ "status.reblog": "轉推",
+ "status.reblogged_by": "{name} 轉推",
+ "status.reply": "回應",
+ "status.report": "舉報 @{name}",
+ "status.sensitive_toggle": "點擊顯示",
+ "status.sensitive_warning": "敏感內容",
+ "status.show_less": "減少顯示",
+ "status.show_more": "顯示更多",
+ "tabs_bar.compose": "撰寫",
+ "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": "還原",
+ "video_player.toggle_sound": "開關音效",
+};
+
+export default zh_hk;
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index 4470ad64..7349cc35 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -67,6 +67,7 @@ function clearAll(state) {
map.set('is_submitting', false);
map.set('in_reply_to', null);
map.set('privacy', state.get('default_privacy'));
+ map.set('sensitive', false);
map.update('media_attachments', list => list.clear());
});
};
@@ -76,7 +77,8 @@ function appendMedia(state, media) {
map.update('media_attachments', list => list.push(media));
map.set('is_uploading', false);
map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
- map.update('text', oldText => `${oldText} ${media.get('text_url')}`.trim());
+ map.set('focusDate', new Date());
+ map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`.trim() + ' ');
});
};
@@ -156,6 +158,9 @@ export default function compose(state = initialState, action) {
if (action.status.get('spoiler_text').length > 0) {
map.set('spoiler', true);
map.set('spoiler_text', action.status.get('spoiler_text'));
+ } else {
+ map.set('spoiler', false);
+ map.set('spoiler_text', '');
}
});
case COMPOSE_REPLY_CANCEL:
diff --git a/app/assets/javascripts/components/reducers/reports.jsx b/app/assets/javascripts/components/reducers/reports.jsx
index e1cce1c5..eab00437 100644
--- a/app/assets/javascripts/components/reducers/reports.jsx
+++ b/app/assets/javascripts/components/reducers/reports.jsx
@@ -4,7 +4,8 @@ import {
REPORT_SUBMIT_SUCCESS,
REPORT_SUBMIT_FAIL,
REPORT_CANCEL,
- REPORT_STATUS_TOGGLE
+ REPORT_STATUS_TOGGLE,
+ REPORT_COMMENT_CHANGE
} from '../actions/reports';
import Immutable from 'immutable';
@@ -39,6 +40,8 @@ export default function reports(state = initialState, action) {
return set.remove(action.statusId);
});
+ case REPORT_COMMENT_CHANGE:
+ return state.setIn(['new', 'comment'], action.comment);
case REPORT_SUBMIT_REQUEST:
return state.setIn(['new', 'isSubmitting'], true);
case REPORT_SUBMIT_FAIL:
diff --git a/app/assets/stylesheets/about.scss b/app/assets/stylesheets/about.scss
index 2ff1d145..8bf950d4 100644
--- a/app/assets/stylesheets/about.scss
+++ b/app/assets/stylesheets/about.scss
@@ -114,10 +114,6 @@
padding: 20px;
}
- .screenshot-with-signup .mascot {
- display: none;
- }
-
.features-list {
display: block;
}
@@ -158,6 +154,14 @@
color: $color5;
}
}
+
+ @media screen and (max-width: 500px) {
+ flex-direction: column;
+
+ .section {
+ text-align: left;
+ }
+ }
}
.owner {
@@ -281,6 +285,15 @@
}
}
}
+
+ @media screen and (max-width: 625px) {
+ flex-direction: column;
+
+ .sidebar {
+ border: 1px solid lighten($color1, 10%);
+ width: auto;
+ }
+ }
}
.features-list {
@@ -319,7 +332,7 @@
}
}
- .simple_form {
+ .simple_form, .closed-registrations-message {
width: 300px;
flex: 0 0 auto;
background: rgba(darken($color1, 7%), 0.5);
@@ -339,4 +352,22 @@
}
}
}
+
+ @media screen and (max-width: 625px) {
+ .mascot {
+ display: none;
+ }
+
+ .simple_form, .closed-registrations-message {
+ flex: auto;
+ }
+ }
+}
+
+.closed-registrations-message {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
}
diff --git a/app/assets/stylesheets/accounts.scss b/app/assets/stylesheets/accounts.scss
index 25e24a95..f9a69882 100644
--- a/app/assets/stylesheets/accounts.scss
+++ b/app/assets/stylesheets/accounts.scss
@@ -14,7 +14,7 @@
}
&:after {
- background: rgba($color8, 0.5);
+ background: linear-gradient(rgba($color8, 0.5), rgba($color8, 0.8));
display: block;
content: "";
position: absolute;
@@ -34,6 +34,7 @@
text-align: center;
position: relative;
z-index: 2;
+ text-shadow: 0 0 2px $color8;
small {
display: block;
@@ -82,7 +83,7 @@
.counter {
width: 80px;
color: $color3;
- padding: 0 10px;
+ padding: 5px 10px 0px;
margin-bottom: 10px;
border-right: 1px solid $color3;
cursor: default;
@@ -128,6 +129,7 @@
text-transform: uppercase;
display: block;
margin-bottom: 5px;
+ text-shadow: 0 0 2px $color8;
}
.counter-number {
@@ -146,7 +148,7 @@
order: 1;
}
- @media screen and (max-width: 360px) {
+ @media screen and (max-width: 480px) {
.details {
display: block;
}
@@ -171,7 +173,7 @@
text-align: center;
overflow: hidden;
- a, .current, .next_page, .previous_page, .gap {
+ a, .current, .page, .gap {
font-size: 14px;
color: $color5;
font-weight: 500;
@@ -191,12 +193,12 @@
cursor: default;
}
- .previous_page, .next_page {
+ .prev, .next {
text-transform: uppercase;
color: $color2;
}
- .previous_page {
+ .prev {
float: left;
padding-left: 0;
@@ -206,7 +208,7 @@
}
}
- .next_page {
+ .next {
float: right;
padding-right: 0;
@@ -224,11 +226,11 @@
@media screen and (max-width: 360px) {
padding: 30px 20px;
- a, .current, .next_page, .previous_page, .gap {
+ a, .current, .next, .prev, .gap {
display: none;
}
- .next_page, .previous_page {
+ .next, .prev {
display: inline-block;
}
}
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index ba16d4a2..35bdd3b6 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -4,305 +4,13 @@
@import 'fonts/montserrat';
@import 'font-awesome';
-/* http://meyerweb.com/eric/tools/css/reset/
- v2.0 | 20110126
- License: none (public domain)
-*/
-
-html, body, div, span, applet, object, iframe,
-h1, h2, h3, h4, h5, h6, p, blockquote, pre,
-a, abbr, acronym, address, big, cite, code,
-del, dfn, em, img, ins, kbd, q, s, samp,
-small, strike, strong, sub, sup, tt, var,
-b, u, i, center,
-dl, dt, dd, ol, ul, li,
-fieldset, form, label, legend,
-table, caption, tbody, tfoot, thead, tr, th, td,
-article, aside, canvas, details, embed,
-figure, figcaption, footer, header, hgroup,
-menu, nav, output, ruby, section, summary,
-time, mark, audio, video {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font: inherit;
- vertical-align: baseline;
-}
-
-/* HTML5 display-role reset for older browsers */
-article, aside, details, figcaption, figure,
-footer, header, hgroup, menu, nav, section {
- display: block;
-}
-
-body {
- line-height: 1;
-}
-
-ol, ul {
- list-style: none;
-}
-
-blockquote, q {
- quotes: none;
-}
-
-blockquote:before, blockquote:after,
-q:before, q:after {
- content: '';
- content: none;
-}
-
-table {
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-::-webkit-scrollbar {
- width: 8px;
- height: 8px;
-}
-
-::-webkit-scrollbar-thumb {
- background: lighten($color1, 4%);
- border: 0px none $color5;
- border-radius: 50px;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: lighten($color1, 6%);
-}
-
-::-webkit-scrollbar-thumb:active {
- background: lighten($color1, 4%);
-}
-
-::-webkit-scrollbar-track {
- border: 0px none $color5;
- border-radius: 0;
- background: rgba($color8, 0.1);
-}
-
-::-webkit-scrollbar-track:hover {
- background: $color1;
-}
-
-::-webkit-scrollbar-track:active {
- background: $color1;
-}
-
-::-webkit-scrollbar-corner {
- background: transparent;
-}
-
-body {
- font-family: 'Roboto', sans-serif;
- background: $color1 image-url('background-photo.jpeg');
- background-size: cover;
- background-attachment: fixed;
- font-size: 13px;
- line-height: 18px;
- font-weight: 400;
- color: $color5;
- padding-bottom: 140px;
- text-rendering: optimizelegibility;
- font-feature-settings: "kern";
- text-size-adjust: none;
-
- &.app-body {
- position: fixed;
- width: 100%;
- height: 100%;
- padding: 0;
- background: $color1;
- }
-
- &.embed {
- background: transparent;
- margin: 0;
-
- .container {
- position: absolute;
- width: 100%;
- height: 100%;
- overflow: hidden;
- }
- }
-
- &.admin {
- background: darken($color1, 4%);
- position: fixed;
- width: 100%;
- height: 100%;
- padding: 0;
- }
-
- @media screen and (max-width: 360px) {
- padding-bottom: 0;
- }
-}
-
-button:focus {
- outline: none;
-}
-
-.app-holder {
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
-}
-
-.container {
- width: 700px;
- margin: 0 auto;
- margin-top: 40px;
-
- @media screen and (max-width: 700px) {
- width: 100%;
- margin: 0;
- }
-}
-
-.logo-container {
- max-width: 400px;
- margin: 100px auto;
- margin-bottom: 0;
- cursor: default;
-
- @media screen and (max-width: 360px) {
- margin: 30px auto;
- }
-
- h1 {
- display: block;
- text-align: center;
- color: $color5;
- font-size: 48px;
- font-weight: 500;
-
- img {
- display: block;
- margin: 20px auto;
- width: 180px;
- height: 180px;
- }
-
- a {
- color: inherit;
- text-decoration: none;
- outline: 0;
-
- img {
- opacity: 0.8;
- transition: all 0.8s ease;
- }
-
- &:hover {
- img {
- opacity: 1;
- transition-duration: 0.2s;
- }
- }
- }
-
- small {
- display: block;
- font-size: 12px;
- font-weight: 400;
- font-family: 'Roboto Mono', monospace;
- }
- }
-}
-
-.no-list {
- list-style: none;
-
- li {
- display: inline-block;
- margin: 0 5px;
- }
-}
-
-.footer {
- text-align: center;
- margin-top: 30px;
- font-size: 12px;
- color: darken($color2, 25%);
-
- .domain {
- font-weight: 500;
-
- a {
- color: inherit;
- text-decoration: none;
- }
- }
-
- .powered-by {
- font-weight: 400;
-
- a {
- color: inherit;
- text-decoration: underline;
- font-weight: 500;
-
- &:hover {
- text-decoration: none;
- }
- }
- }
-}
-
-.compact-header {
- h1 {
- font-size: 24px;
- line-height: 28px;
- color: $color3;
- overflow: hidden;
- font-weight: 500;
- margin-bottom: 20px;
-
- a {
- color: inherit;
- text-decoration: none;
- }
-
- small {
- font-weight: 400;
- color: $color2;
- }
-
- img {
- display: inline-block;
- margin-bottom: -5px;
- margin-right: 15px;
- width: 36px;
- height: 36px;
- }
- }
-}
-
-.landing-strip {
- background: rgba(darken($color1, 7%), 0.8);
- color: $color3;
- font-weight: 400;
- padding: 14px;
- border-radius: 4px;
- margin-bottom: 20px;
-
- strong, a {
- font-weight: 500;
- }
-
- a {
- color: inherit;
- text-decoration: underline;
- }
-}
-
+@import 'reset';
+@import 'basics';
+@import 'containers';
+@import 'lists';
+@import 'footer';
+@import 'compact_header';
+@import 'landing_strip';
@import 'forms';
@import 'accounts';
@import 'stream_entries';
diff --git a/app/assets/stylesheets/basics.scss b/app/assets/stylesheets/basics.scss
new file mode 100644
index 00000000..c8ff7f5d
--- /dev/null
+++ b/app/assets/stylesheets/basics.scss
@@ -0,0 +1,58 @@
+body {
+ font-family: 'Roboto', sans-serif;
+ background: $color1 image-url('background-photo.jpeg');
+ background-size: cover;
+ background-attachment: fixed;
+ font-size: 13px;
+ line-height: 18px;
+ font-weight: 400;
+ color: $color5;
+ padding-bottom: 140px;
+ text-rendering: optimizelegibility;
+ font-feature-settings: "kern";
+ text-size-adjust: none;
+
+ &.app-body {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ background: $color1;
+ }
+
+ &.embed {
+ background: transparent;
+ margin: 0;
+
+ .container {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ }
+ }
+
+ &.admin {
+ background: darken($color1, 4%);
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ }
+
+ @media screen and (max-width: 360px) {
+ padding-bottom: 0;
+ }
+}
+
+button:focus {
+ outline: none;
+}
+
+.app-holder {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+}
diff --git a/app/assets/stylesheets/boost.scss b/app/assets/stylesheets/boost.scss
index a2e6421f..6688f90f 100644
--- a/app/assets/stylesheets/boost.scss
+++ b/app/assets/stylesheets/boost.scss
@@ -2,6 +2,6 @@
@return '%23' + str-slice('#{$colour}', 2, -1)
}
-button i.fa-retweet {
+button.icon-button i.fa-retweet {
background-image: url("data:image/svg+xml;utf8,
");
}
diff --git a/app/assets/stylesheets/compact_header.scss b/app/assets/stylesheets/compact_header.scss
new file mode 100644
index 00000000..8fef05f0
--- /dev/null
+++ b/app/assets/stylesheets/compact_header.scss
@@ -0,0 +1,28 @@
+.compact-header {
+ h1 {
+ font-size: 24px;
+ line-height: 28px;
+ color: $color3;
+ overflow: hidden;
+ font-weight: 500;
+ margin-bottom: 20px;
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+
+ small {
+ font-weight: 400;
+ color: $color2;
+ }
+
+ img {
+ display: inline-block;
+ margin-bottom: -5px;
+ margin-right: 15px;
+ width: 36px;
+ height: 36px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss
index 567d8fee..83b6ce6d 100644
--- a/app/assets/stylesheets/components.scss
+++ b/app/assets/stylesheets/components.scss
@@ -1,5 +1,10 @@
@import 'variables';
+.app-body{
+ -webkit-overflow-scrolling: touch;
+ -ms-overflow-style: -ms-autohiding-scrollbar;
+}
+
.button {
background-color: darken($color4, 3%);
font-family: inherit;
@@ -45,6 +50,22 @@
}
}
+.column-icon-clear {
+ font-size: 16px;
+ padding: 15px;
+ position: absolute;
+ right: 48px;
+ top: 0;
+ cursor: pointer;
+ z-index: 2;
+}
+
+@media screen and (min-width: 1025px) {
+ .column-icon-clear {
+ top: 10px;
+ }
+}
+
.icon-button {
display: inline-block;
padding: 0;
@@ -91,6 +112,18 @@
color: $color3;
}
}
+
+ &.overlayed {
+ box-sizing: content-box;
+ background: rgba($color8, 0.6);
+ color: rgba($color5, 0.7);
+ border-radius: 4px;
+ padding: 2px;
+
+ &:hover {
+ background: rgba($color8, 0.9);
+ }
+ }
}
.text-icon-button {
@@ -145,6 +178,14 @@
}
}
+.avatar {
+ border-radius: 4px;
+ background: transparent no-repeat;
+ background-position: 50%;
+ background-clip: padding-box;
+ position: relative;
+}
+
.lightbox .icon-button {
color: $color1;
}
@@ -321,6 +362,43 @@ a.status__content__spoiler-link {
.status__display-name {
color: lighten($color1, 26%);
}
+
+ &.light {
+ .status__relative-time {
+ color: $color3;
+ }
+
+ .status__display-name {
+ color: $color1;
+ }
+
+ .display-name {
+ strong {
+ color: $color1;
+ }
+
+ span {
+ color: $color3;
+ }
+ }
+
+ .status__content {
+ color: $color1;
+
+ a {
+ color: $color4;
+ }
+
+ a.status__content__spoiler-link {
+ color: $color5;
+ background: $color3;
+
+ &:hover {
+ background: lighten($color3, 8%);
+ }
+ }
+ }
+ }
}
.status-check-box {
@@ -639,6 +717,12 @@ a.status__content__spoiler-link {
left: 8px;
}
+ &.light {
+ &:before {
+ border-color: transparent transparent $color5 transparent;
+ }
+ }
+
& > ul {
list-style: none;
background: $color2;
@@ -656,7 +740,7 @@ a.status__content__spoiler-link {
}
& > .emoji-dialog {
- left: -249px;
+ left: -210px;
}
}
@@ -710,7 +794,7 @@ a.status__content__spoiler-link {
@media screen and (min-width: 360px) {
.columns-area {
- margin: 10px;
+ padding: 10px;
}
}
@@ -718,9 +802,12 @@ a.status__content__spoiler-link {
width: 330px;
position: relative;
box-sizing: border-box;
- background: $color1;
display: flex;
flex-direction: column;
+
+ > .scrollable {
+ background: $color1;
+ }
}
.ui {
@@ -752,6 +839,58 @@ a.status__content__spoiler-link {
border-bottom: 2px solid transparent;
}
+.column, .drawer {
+ flex: 1 1 100%;
+ overflow: hidden;
+}
+
+@media screen and (min-width: 360px) {
+ .tabs-bar {
+ margin: 10px;
+ margin-bottom: 0;
+ }
+
+ .search {
+ margin-bottom: 10px;
+ }
+}
+
+@media screen and (max-width: 1024px) {
+ .column, .drawer {
+ width: 100%;
+ padding: 0;
+ }
+
+ .columns-area {
+ flex-direction: column;
+ }
+
+ .search__input, .autosuggest-textarea__textarea {
+ font-size: 16px;
+ }
+}
+
+@media screen and (min-width: 1025px) {
+ .columns-area {
+ padding: 0;
+ }
+
+ .column, .drawer {
+ flex: 0 0 auto;
+ padding: 10px;
+ padding-left: 5px;
+ padding-right: 5px;
+
+ &:first-child {
+ padding-left: 10px;
+ }
+
+ &:last-child {
+ padding-right: 10px;
+ }
+ }
+}
+
@media screen and (min-width: 2560px) {
.columns-area {
justify-content: center;
@@ -811,37 +950,6 @@ a.status__content__spoiler-link {
}
}
-.column, .drawer {
- margin-left: 5px;
- margin-right: 5px;
- flex: 0 0 auto;
- overflow: hidden;
-}
-
-.column:first-child, .drawer:first-child {
- margin-left: 0;
-}
-
-.column:last-child, .drawer:last-child {
- margin-right: 0;
-}
-
-@media screen and (max-width: 1024px) {
- .column, .drawer {
- width: 100%;
- margin: 0;
- flex: 1 1 100%;
- }
-
- .columns-area {
- flex-direction: column;
- }
-
- .search__input, .autosuggest-textarea__textarea {
- font-size: 16px;
- }
-}
-
.tabs-bar {
display: flex;
background: lighten($color1, 8%);
@@ -852,17 +960,18 @@ a.status__content__spoiler-link {
.tabs-bar__link {
display: block;
flex: 1 1 auto;
- padding: 10px 5px;
+ padding: 15px 10px;
color: $color5;
text-decoration: none;
text-align: center;
- font-size:12px;
+ font-size: 14px;
font-weight: 500;
border-bottom: 2px solid lighten($color1, 8%);
transition: all 200ms linear;
.fa {
font-weight: 400;
+ font-size: 16px;
}
&.active {
@@ -876,27 +985,13 @@ a.status__content__spoiler-link {
}
span {
+ margin-left: 5px;
display: none;
}
}
-@media screen and (min-width: 360px) {
- .tabs-bar {
- margin: 10px;
- margin-bottom: 0;
- }
-
- .search {
- margin-bottom: 10px;
- }
-}
-
@media screen and (min-width: 600px) {
.tabs-bar__link {
- .fa {
- margin-right: 5px;
- }
-
span {
display: inline;
}
@@ -1149,10 +1244,9 @@ a.status__content__spoiler-link {
.getting-started {
box-sizing: border-box;
- overflow-y: auto;
padding-bottom: 235px;
background: image-url('mastodon-getting-started.png') no-repeat 0 100% local;
- height: 100%;
+ flex: 1 0 auto;
p {
color: $color2;
@@ -1177,7 +1271,7 @@ a.status__content__spoiler-link {
@import 'boost';
-button i.fa-retweet {
+button.icon-button i.fa-retweet {
height: 19px;
width: 22px;
background-position: 0 0;
@@ -1189,7 +1283,7 @@ button i.fa-retweet {
}
}
-button.active i.fa-retweet {
+button.icon-button.active i.fa-retweet {
transition-duration: 0.9s;
background-position: 0 100%;
}
@@ -1359,12 +1453,15 @@ button.active i.fa-retweet {
.empty-column-indicator {
color: lighten($color1, 20%);
+ background: $color1;
text-align: center;
padding: 20px;
- padding-top: 100px;
font-size: 15px;
font-weight: 400;
cursor: default;
+ display: flex;
+ flex: 1 1 auto;
+ align-items: center;
a {
color: $color4;
@@ -1390,22 +1487,23 @@ button.active i.fa-retweet {
}
.emoji-dialog {
- width: 280px;
- height: 220px;
- background: $color2;
+ width: 245px;
+ height: 270px;
+ background: $color5;
box-sizing: border-box;
- border-radius: 2px;
+ border-radius: 4px;
overflow: hidden;
position: relative;
- box-shadow: 0 0 15px rgba($color8, 0.4);
+ box-shadow: 0 0 8px rgba($color8, 0.2);
.emojione {
margin: 0;
+ width: 100%;
+ height: auto;
}
.emoji-dialog-header {
padding: 0 10px;
- background-color: $color3;
ul {
padding: 0;
@@ -1416,18 +1514,29 @@ button.active i.fa-retweet {
li {
display: inline-block;
box-sizing: border-box;
- height: 42px;
- padding: 9px 5px;
+ padding: 10px 5px;
cursor: pointer;
+ border-bottom: 2px solid transparent;
+
+ .emoji {
+ width: 18px;
+ height: 18px;
+ }
img, svg {
- width: 22px;
- height: 22px;
+ width: 18px;
+ height: 18px;
filter: grayscale(100%);
}
+ &:hover {
+ img, svg {
+ filter: grayscale(0);
+ }
+ }
+
&.active {
- background: lighten($color3, 6%);
+ border-bottom-color: $color4;
img, svg {
filter: grayscale(0);
@@ -1451,7 +1560,7 @@ button.active i.fa-retweet {
.emoji-category-header {
box-sizing: border-box;
overflow-y: hidden;
- padding: 8px 16px 0;
+ padding: 10px 8px 10px 16px;
display: table;
> * {
@@ -1461,10 +1570,10 @@ button.active i.fa-retweet {
}
.emoji-category-title {
- font-size: 14px;
- font-family: sans-serif;
- font-weight: normal;
- color: $color1;
+ font-size: 12px;
+ text-transform: uppercase;
+ font-weight: 500;
+ color: darken($color2, 18%);
cursor: default;
}
@@ -1504,7 +1613,7 @@ button.active i.fa-retweet {
width: 7px;
height: 7px;
border-radius: 10px;
- border: 2px solid $color1;
+ border: 2px solid $color5;
top: 2px;
left: 2px;
}
@@ -1512,14 +1621,20 @@ button.active i.fa-retweet {
}
.emoji-search-wrapper {
- padding: 6px 16px;
+ padding: 10px;
+ border-bottom: 1px solid lighten($color2, 4%);
}
.emoji-search {
- font-size: 12px;
- padding: 6px 4px;
+ font-size: 14px;
+ font-weight: 400;
+ padding: 7px 9px;
+ font-family: inherit;
+ display: block;
width: 100%;
- border: 1px solid #ddd;
+ background: rgba($color2, 0.3);
+ color: darken($color2, 18%);
+ border: 1px solid $color2;
border-radius: 4px;
}
@@ -1532,11 +1647,21 @@ button.active i.fa-retweet {
}
.emoji-search-wrapper + .emoji-categories-wrapper {
- top: 83px;
+ top: 93px;
}
- .emoji-row .emoji:hover {
- background: lighten($color2, 3%);
+ .emoji-row .emoji {
+ img, svg {
+ transition: transform 60ms ease-in-out;
+ }
+
+ &:hover {
+ background: lighten($color2, 3%);
+
+ img, svg {
+ transform: translateZ(0) scale(1.2);
+ }
+ }
}
.emoji {
@@ -1976,8 +2101,8 @@ button.active i.fa-retweet {
}
.onboarding-modal__page {
- text-align: center;
cursor: default;
+ line-height: 21px;
h1 {
font-size: 18px;
@@ -1995,11 +2120,10 @@ button.active i.fa-retweet {
}
p {
- font-size: 18px;
+ font-size: 16px;
color: lighten($color1, 8%);
margin-top: 10px;
margin-bottom: 10px;
- line-height: 10x;
&:last-child {
margin-bottom: 0;
@@ -2007,24 +2131,85 @@ button.active i.fa-retweet {
strong {
font-weight: 500;
+ background: $color1;
+ color: $color2;
+ border-radius: 4px;
+ font-size: 14px;
+ padding: 3px 6px;
}
}
}
- .onboarding-modal__image {
- border-radius: 8px;
- width: 70vw;
- max-width: 450px;
- max-height: auto;
- display: block;
- margin: auto;
+.onboarding-modal__page-one {
+ display: flex;
+}
+
+.onboarding-modal__page-one__elephant-friend {
+ background: image-url('elephant-friend.png') no-repeat 0 0;
+ width: 147px;
+ height: 160px;
+ margin-right: 10px;
+}
+
+.onboarding-modal__page-two__compose {
+ position: relative;
+ height: 300px;
+
+ & > div {
+ position: absolute;
}
-
+}
+
+.onboarding-modal__image {
+ border-radius: 8px;
+ width: 70vw;
+ max-width: 450px;
+ max-height: auto;
+ display: block;
+ margin: auto;
+}
+
.onboard-sliders {
- display: inline-block;
- max-width: 30px;
- max-height: auto;
- margin-left: 10px;
+ display: inline-block;
+ max-width: 30px;
+ max-height: auto;
+ margin-left: 10px;
+}
+
+.boost-modal {
+ background: lighten($color2, 8%);
+ color: $color1;
+ border-radius: 8px;
+ overflow: hidden;
+ max-width: 90vw;
+ width: 480px;
+ position: relative;
+ flex-direction: column;
+}
+
+.boost-modal__container {
+ padding: 10px;
+
+ .status {
+ user-select: text;
+ border-bottom: 0;
}
-
-
+}
+
+.boost-modal__action-bar {
+ display: flex;
+ background: $color2;
+ padding: 10px;
+ line-height: 36px;
+
+ & > div {
+ flex: 1 1 auto;
+ text-align: right;
+ color: lighten($color1, 33%);
+ padding-right: 10px;
+ }
+
+ .button {
+ flex: 0 0 auto;
+ }
+}
diff --git a/app/assets/stylesheets/containers.scss b/app/assets/stylesheets/containers.scss
new file mode 100644
index 00000000..43705b19
--- /dev/null
+++ b/app/assets/stylesheets/containers.scss
@@ -0,0 +1,61 @@
+.container {
+ width: 700px;
+ margin: 0 auto;
+ margin-top: 40px;
+
+ @media screen and (max-width: 700px) {
+ width: 100%;
+ margin: 0;
+ }
+}
+
+.logo-container {
+ max-width: 400px;
+ margin: 100px auto;
+ margin-bottom: 0;
+ cursor: default;
+
+ @media screen and (max-width: 360px) {
+ margin: 30px auto;
+ }
+
+ h1 {
+ display: block;
+ text-align: center;
+ color: $color5;
+ font-size: 48px;
+ font-weight: 500;
+
+ img {
+ display: block;
+ margin: 20px auto;
+ width: 180px;
+ height: 180px;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ outline: 0;
+
+ img {
+ opacity: 0.8;
+ transition: all 0.8s ease;
+ }
+
+ &:hover {
+ img {
+ opacity: 1;
+ transition-duration: 0.2s;
+ }
+ }
+ }
+
+ small {
+ display: block;
+ font-size: 12px;
+ font-weight: 400;
+ font-family: 'Roboto Mono', monospace;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/footer.scss b/app/assets/stylesheets/footer.scss
new file mode 100644
index 00000000..719bfcf4
--- /dev/null
+++ b/app/assets/stylesheets/footer.scss
@@ -0,0 +1,29 @@
+.footer {
+ text-align: center;
+ margin-top: 30px;
+ font-size: 12px;
+ color: darken($color2, 25%);
+
+ .domain {
+ font-weight: 500;
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+ }
+
+ .powered-by {
+ font-weight: 400;
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+ font-weight: 500;
+
+ &:hover {
+ text-decoration: none;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/forms.scss b/app/assets/stylesheets/forms.scss
index ceccc14c..e5e8697a 100644
--- a/app/assets/stylesheets/forms.scss
+++ b/app/assets/stylesheets/forms.scss
@@ -25,6 +25,10 @@ code {
margin-bottom: 15px;
}
+ strong {
+ font-weight: 500;
+ }
+
.label_input {
display: flex;
@@ -84,7 +88,7 @@ code {
}
}
- input[type=text], input[type=email], input[type=password], textarea {
+ input[type=text], input[type=number], input[type=email], input[type=password], textarea {
background: transparent;
box-sizing: border-box;
border: 0;
@@ -224,7 +228,12 @@ code {
}
}
+.qr-wrapper {
+ display: flex;
+}
+
.qr-code {
+ flex: 0 0 auto;
background: #fff;
padding: 4px;
margin-bottom: 20px;
@@ -236,3 +245,13 @@ code {
margin: 0;
}
}
+
+.qr-alternative {
+ margin-left: 10px;
+ color: $color3;
+
+ samp {
+ display: block;
+ font-size: 14px;
+ }
+}
diff --git a/app/assets/stylesheets/landing_strip.scss b/app/assets/stylesheets/landing_strip.scss
new file mode 100644
index 00000000..4c240316
--- /dev/null
+++ b/app/assets/stylesheets/landing_strip.scss
@@ -0,0 +1,17 @@
+.landing-strip {
+ background: rgba(darken($color1, 7%), 0.8);
+ color: $color3;
+ font-weight: 400;
+ padding: 14px;
+ border-radius: 4px;
+ margin-bottom: 20px;
+
+ strong, a {
+ font-weight: 500;
+ }
+
+ a {
+ color: inherit;
+ text-decoration: underline;
+ }
+}
diff --git a/app/assets/stylesheets/lists.scss b/app/assets/stylesheets/lists.scss
new file mode 100644
index 00000000..eac9f5a2
--- /dev/null
+++ b/app/assets/stylesheets/lists.scss
@@ -0,0 +1,8 @@
+.no-list {
+ list-style: none;
+
+ li {
+ display: inline-block;
+ margin: 0 5px;
+ }
+}
diff --git a/app/assets/stylesheets/reset.scss b/app/assets/stylesheets/reset.scss
new file mode 100644
index 00000000..71064cc3
--- /dev/null
+++ b/app/assets/stylesheets/reset.scss
@@ -0,0 +1,91 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+
+body {
+ line-height: 1;
+}
+
+ol, ul {
+ list-style: none;
+}
+
+blockquote, q {
+ quotes: none;
+}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-thumb {
+ background: lighten($color1, 4%);
+ border: 0px none $color5;
+ border-radius: 50px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: lighten($color1, 6%);
+}
+
+::-webkit-scrollbar-thumb:active {
+ background: lighten($color1, 4%);
+}
+
+::-webkit-scrollbar-track {
+ border: 0px none $color5;
+ border-radius: 0;
+ background: rgba($color8, 0.1);
+}
+
+::-webkit-scrollbar-track:hover {
+ background: $color1;
+}
+
+::-webkit-scrollbar-track:active {
+ background: $color1;
+}
+
+::-webkit-scrollbar-corner {
+ background: transparent;
+}
diff --git a/app/assets/stylesheets/stream_entries.scss b/app/assets/stylesheets/stream_entries.scss
index 4a6dc6aa..7bd180c1 100644
--- a/app/assets/stylesheets/stream_entries.scss
+++ b/app/assets/stylesheets/stream_entries.scss
@@ -218,6 +218,7 @@
margin-top: 8px;
height: 300px;
overflow: hidden;
+ position: relative;
video {
position: relative;
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index abf4b7df..04e7ddac 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -2,28 +2,25 @@
class AboutController < ApplicationController
before_action :set_body_classes
+ before_action :set_instance_presenter, only: [:show, :more]
- def index
- @description = Setting.site_description
+ def show; end
- @user = User.new
- @user.build_account
- end
-
- def more
- @description = Setting.site_description
- @extended_description = Setting.site_extended_description
- @contact_account = Account.find_local(Setting.site_contact_username)
- @contact_email = Setting.site_contact_email
- @user_count = Rails.cache.fetch('user_count') { User.count }
- @status_count = Rails.cache.fetch('local_status_count') { Status.local.count }
- @domain_count = Rails.cache.fetch('distinct_domain_count') { Account.distinct.count(:domain) }
- end
+ def more; end
def terms; end
private
+ def new_user
+ User.new.tap(&:build_account)
+ end
+ helper_method :new_user
+
+ def set_instance_presenter
+ @instance_presenter = InstancePresenter.new
+ end
+
def set_body_classes
@body_classes = 'about-body'
end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index dc1aeb5e..d4f15761 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -16,7 +16,8 @@ class AccountsController < ApplicationController
end
format.atom do
- @entries = @account.stream_entries.order('id desc').where(activity_type: 'Status').where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
+ @entries = @account.stream_entries.order('id desc').where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
+ render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
end
format.activitystreams2
@@ -34,11 +35,11 @@ class AccountsController < ApplicationController
end
def followers
- @followers = @account.followers.order('follows.created_at desc').paginate(page: params[:page], per_page: 12)
+ @followers = @account.followers.order('follows.created_at desc').page(params[:page]).per(12)
end
def following
- @following = @account.following.order('follows.created_at desc').paginate(page: params[:page], per_page: 12)
+ @following = @account.following.order('follows.created_at desc').page(params[:page]).per(12)
end
private
@@ -52,7 +53,7 @@ class AccountsController < ApplicationController
end
def webfinger_account_url
- webfinger_url(resource: "acct:#{@account.acct}@#{Rails.configuration.x.local_domain}")
+ webfinger_url(resource: @account.to_webfinger_s)
end
def check_account_suspension
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index df2c7beb..0e9e52f4 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -1,51 +1,30 @@
# frozen_string_literal: true
-class Admin::AccountsController < ApplicationController
- before_action :require_admin!
- before_action :set_account, except: :index
+module Admin
+ class AccountsController < BaseController
+ def index
+ @accounts = filtered_accounts.page(params[:page])
+ end
- layout 'admin'
+ def show
+ @account = Account.find(params[:id])
+ end
- def index
- @accounts = Account.alphabetic.paginate(page: params[:page], per_page: 40)
+ private
- @accounts = @accounts.local if params[:local].present?
- @accounts = @accounts.remote if params[:remote].present?
- @accounts = @accounts.where(domain: params[:by_domain]) if params[:by_domain].present?
- @accounts = @accounts.silenced if params[:silenced].present?
- @accounts = @accounts.recent if params[:recent].present?
- @accounts = @accounts.suspended if params[:suspended].present?
- end
+ def filtered_accounts
+ AccountFilter.new(filter_params).results
+ end
- def show; end
-
- def suspend
- Admin::SuspensionWorker.perform_async(@account.id)
- redirect_to admin_accounts_path
- end
-
- def unsuspend
- @account.update(suspended: false)
- redirect_to admin_accounts_path
- end
-
- def silence
- @account.update(silenced: true)
- redirect_to admin_accounts_path
- end
-
- def unsilence
- @account.update(silenced: false)
- redirect_to admin_accounts_path
- end
-
- private
-
- def set_account
- @account = Account.find(params[:id])
- end
-
- def account_params
- params.require(:account).permit(:silenced, :suspended)
+ def filter_params
+ params.permit(
+ :local,
+ :remote,
+ :by_domain,
+ :silenced,
+ :recent,
+ :suspended
+ )
+ end
end
end
diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb
new file mode 100644
index 00000000..11fe326b
--- /dev/null
+++ b/app/controllers/admin/base_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Admin
+ class BaseController < ApplicationController
+ before_action :require_admin!
+
+ layout 'admin'
+ end
+end
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index e362957e..a8b56c08 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -1,14 +1,30 @@
# frozen_string_literal: true
-class Admin::DomainBlocksController < ApplicationController
- before_action :require_admin!
+module Admin
+ class DomainBlocksController < BaseController
+ def index
+ @blocks = DomainBlock.page(params[:page])
+ end
- layout 'admin'
+ def new
+ @domain_block = DomainBlock.new
+ end
- def index
- @blocks = DomainBlock.paginate(page: params[:page], per_page: 40)
- end
+ def create
+ @domain_block = DomainBlock.new(resource_params)
- def create
+ if @domain_block.save
+ DomainBlockWorker.perform_async(@domain_block.id)
+ redirect_to admin_domain_blocks_path, notice: 'Domain block is now being processed'
+ else
+ render action: :new
+ end
+ end
+
+ private
+
+ def resource_params
+ params.require(:domain_block).permit(:domain, :severity)
+ end
end
end
diff --git a/app/controllers/admin/pubsubhubbub_controller.rb b/app/controllers/admin/pubsubhubbub_controller.rb
index b9e840ff..31c80a17 100644
--- a/app/controllers/admin/pubsubhubbub_controller.rb
+++ b/app/controllers/admin/pubsubhubbub_controller.rb
@@ -1,11 +1,9 @@
# frozen_string_literal: true
-class Admin::PubsubhubbubController < ApplicationController
- before_action :require_admin!
-
- layout 'admin'
-
- def index
- @subscriptions = Subscription.order('id desc').includes(:account).paginate(page: params[:page], per_page: 40)
+module Admin
+ class PubsubhubbubController < BaseController
+ def index
+ @subscriptions = Subscription.order('id desc').includes(:account).page(params[:page])
+ end
end
end
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb
index 0117a18e..3c308231 100644
--- a/app/controllers/admin/reports_controller.rb
+++ b/app/controllers/admin/reports_controller.rb
@@ -1,45 +1,44 @@
# frozen_string_literal: true
-class Admin::ReportsController < ApplicationController
- before_action :require_admin!
- before_action :set_report, except: [:index]
+module Admin
+ class ReportsController < BaseController
+ before_action :set_report, except: [:index]
- layout 'admin'
+ def index
+ @reports = Report.includes(:account, :target_account).order('id desc').page(params[:page])
+ @reports = params[:action_taken].present? ? @reports.resolved : @reports.unresolved
+ end
- def index
- @reports = Report.includes(:account, :target_account).order('id desc').paginate(page: params[:page], per_page: 40)
- @reports = params[:action_taken].present? ? @reports.resolved : @reports.unresolved
- end
+ def show
+ @statuses = Status.where(id: @report.status_ids)
+ end
- def show
- @statuses = Status.where(id: @report.status_ids)
- end
+ def resolve
+ @report.update(action_taken: true, action_taken_by_account_id: current_account.id)
+ redirect_to admin_report_path(@report)
+ end
- def resolve
- @report.update(action_taken: true)
- redirect_to admin_report_path(@report)
- end
+ def suspend
+ Admin::SuspensionWorker.perform_async(@report.target_account.id)
+ Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
+ redirect_to admin_report_path(@report)
+ end
- def suspend
- Admin::SuspensionWorker.perform_async(@report.target_account.id)
- @report.update(action_taken: true)
- redirect_to admin_report_path(@report)
- end
+ def silence
+ @report.target_account.update(silenced: true)
+ Report.unresolved.where(target_account: @report.target_account).update_all(action_taken: true, action_taken_by_account_id: current_account.id)
+ redirect_to admin_report_path(@report)
+ end
- def silence
- @report.target_account.update(silenced: true)
- @report.update(action_taken: true)
- redirect_to admin_report_path(@report)
- end
+ def remove
+ RemovalWorker.perform_async(params[:status_id])
+ redirect_to admin_report_path(@report)
+ end
- def remove
- RemovalWorker.perform_async(params[:status_id])
- redirect_to admin_report_path(@report)
- end
+ private
- private
-
- def set_report
- @report = Report.find(params[:id])
+ def set_report
+ @report = Report.find(params[:id])
+ end
end
end
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index af0be882..6cca5c3e 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -1,25 +1,33 @@
# frozen_string_literal: true
-class Admin::SettingsController < ApplicationController
- before_action :require_admin!
-
- layout 'admin'
-
- def index
- @settings = Setting.all_as_records
- end
-
- def update
- @setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
-
- if @setting.value != params[:setting][:value]
- @setting.value = params[:setting][:value]
- @setting.save
+module Admin
+ class SettingsController < BaseController
+ def index
+ @settings = Setting.all_as_records
end
- respond_to do |format|
- format.html { redirect_to admin_settings_path }
- format.json { respond_with_bip(@setting) }
+ def update
+ @setting = Setting.where(var: params[:id]).first_or_initialize(var: params[:id])
+ value = settings_params[:value]
+
+ # Special cases
+ value = value == 'true' if @setting.var == 'open_registrations'
+
+ if @setting.value != value
+ @setting.value = value
+ @setting.save
+ end
+
+ respond_to do |format|
+ format.html { redirect_to admin_settings_path }
+ format.json { respond_with_bip(@setting) }
+ end
+ end
+
+ private
+
+ def settings_params
+ params.require(:setting).permit(:value)
end
end
end
diff --git a/app/controllers/admin/silences_controller.rb b/app/controllers/admin/silences_controller.rb
new file mode 100644
index 00000000..81a3008b
--- /dev/null
+++ b/app/controllers/admin/silences_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Admin
+ class SilencesController < BaseController
+ before_action :set_account
+
+ def create
+ @account.update(silenced: true)
+ redirect_to admin_accounts_path
+ end
+
+ def destroy
+ @account.update(silenced: false)
+ redirect_to admin_accounts_path
+ end
+
+ private
+
+ def set_account
+ @account = Account.find(params[:account_id])
+ end
+ end
+end
diff --git a/app/controllers/admin/suspensions_controller.rb b/app/controllers/admin/suspensions_controller.rb
new file mode 100644
index 00000000..5d9048d9
--- /dev/null
+++ b/app/controllers/admin/suspensions_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Admin
+ class SuspensionsController < BaseController
+ before_action :set_account
+
+ def create
+ Admin::SuspensionWorker.perform_async(@account.id)
+ redirect_to admin_accounts_path
+ end
+
+ def destroy
+ @account.update(suspended: false)
+ 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/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index da18474c..2c44e36a 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -1,10 +1,11 @@
# frozen_string_literal: true
class Api::V1::AccountsController < ApiController
- before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
+ before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute, :update_credentials]
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
+ before_action -> { doorkeeper_authorize! :write }, only: [:update_credentials]
before_action :require_user!, except: [:show, :following, :followers, :statuses]
- before_action :set_account, except: [:verify_credentials, :suggestions, :search]
+ before_action :set_account, except: [:verify_credentials, :update_credentials, :suggestions, :search]
respond_to :json
@@ -15,15 +16,19 @@ class Api::V1::AccountsController < ApiController
render action: :show
end
+ def update_credentials
+ current_account.update!(account_params)
+ @account = current_account
+ render action: :show
+ end
+
def following
results = Follow.where(account: @account).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }
- # set_account_counters_maps(@accounts)
-
- next_path = following_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
- prev_path = following_api_v1_account_url(since_id: results.first.id) unless results.empty?
+ next_path = following_api_v1_account_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ prev_path = following_api_v1_account_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@@ -35,10 +40,8 @@ class Api::V1::AccountsController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] }
- # set_account_counters_maps(@accounts)
-
- next_path = followers_api_v1_account_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
- prev_path = followers_api_v1_account_url(since_id: results.first.id) unless results.empty?
+ next_path = followers_api_v1_account_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ prev_path = followers_api_v1_account_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@@ -52,11 +55,9 @@ class Api::V1::AccountsController < ApiController
@statuses = cache_collection(@statuses, Status)
set_maps(@statuses)
- # set_counters_maps(@statuses)
- # set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
- next_path = statuses_api_v1_account_url(max_id: @statuses.last.id) unless @statuses.empty?
- prev_path = statuses_api_v1_account_url(since_id: @statuses.first.id) unless @statuses.empty?
+ next_path = statuses_api_v1_account_url(statuses_pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
+ prev_path = statuses_api_v1_account_url(statuses_pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
end
@@ -117,8 +118,6 @@ class Api::V1::AccountsController < ApiController
def search
@accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
- # set_account_counters_maps(@accounts) unless @accounts.nil?
-
render action: :index
end
@@ -135,4 +134,16 @@ class Api::V1::AccountsController < ApiController
@muting = Account.muting_map([@account.id], current_user.account_id)
@requested = Account.requested_map([@account.id], current_user.account_id)
end
+
+ def pagination_params(core_params)
+ params.permit(:limit).merge(core_params)
+ end
+
+ def statuses_pagination_params(core_params)
+ params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
+ end
+
+ def account_params
+ params.permit(:display_name, :note, :avatar, :header)
+ end
end
diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb
index ca9dd0b7..2ec7280a 100644
--- a/app/controllers/api/v1/apps_controller.rb
+++ b/app/controllers/api/v1/apps_controller.rb
@@ -4,6 +4,12 @@ class Api::V1::AppsController < ApiController
respond_to :json
def create
- @app = Doorkeeper::Application.create!(name: params[:client_name], redirect_uri: params[:redirect_uris], scopes: (params[:scopes] || Doorkeeper.configuration.default_scopes), website: params[:website])
+ @app = Doorkeeper::Application.create!(name: app_params[:client_name], redirect_uri: app_params[:redirect_uris], scopes: (app_params[:scopes] || Doorkeeper.configuration.default_scopes), website: app_params[:website])
+ end
+
+ private
+
+ def app_params
+ params.permit(:client_name, :redirect_uris, :scopes, :website)
end
end
diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb
index dadf2126..742717ba 100644
--- a/app/controllers/api/v1/blocks_controller.rb
+++ b/app/controllers/api/v1/blocks_controller.rb
@@ -11,11 +11,15 @@ class Api::V1::BlocksController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }.compact
- # set_account_counters_maps(@accounts)
-
- next_path = api_v1_blocks_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
- prev_path = api_v1_blocks_url(since_id: results.first.id) unless results.empty?
+ next_path = api_v1_blocks_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ prev_path = api_v1_blocks_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
+
+ private
+
+ def pagination_params(core_params)
+ params.permit(:limit).merge(core_params)
+ end
end
diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb
index 8a5b81e6..22b93fe7 100644
--- a/app/controllers/api/v1/favourites_controller.rb
+++ b/app/controllers/api/v1/favourites_controller.rb
@@ -11,11 +11,16 @@ class Api::V1::FavouritesController < ApiController
@statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status)
set_maps(@statuses)
- # set_counters_maps(@statuses)
- next_path = api_v1_favourites_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
- prev_path = api_v1_favourites_url(since_id: results.first.id) unless results.empty?
+ next_path = api_v1_favourites_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
+ prev_path = api_v1_favourites_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
+
+ private
+
+ def pagination_params(core_params)
+ params.permit(:limit).merge(core_params)
+ end
end
diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb
index 3b8e8c07..73cfaf10 100644
--- a/app/controllers/api/v1/follow_requests_controller.rb
+++ b/app/controllers/api/v1/follow_requests_controller.rb
@@ -9,10 +9,8 @@ class Api::V1::FollowRequestsController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] }
- # set_account_counters_maps(@accounts)
-
- next_path = api_v1_follow_requests_url(max_id: results.last.id) if results.size == DEFAULT_ACCOUNTS_LIMIT
- prev_path = api_v1_follow_requests_url(since_id: results.first.id) unless results.empty?
+ next_path = api_v1_follow_requests_url(pagination_params(max_id: results.last.id)) if results.size == DEFAULT_ACCOUNTS_LIMIT
+ prev_path = api_v1_follow_requests_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
@@ -26,4 +24,10 @@ class Api::V1::FollowRequestsController < ApiController
RejectFollowService.new.call(Account.find(params[:id]), current_account)
render_empty
end
+
+ private
+
+ def pagination_params(core_params)
+ params.permit(:limit).merge(core_params)
+ end
end
diff --git a/app/controllers/api/v1/follows_controller.rb b/app/controllers/api/v1/follows_controller.rb
index c22dacba..7c0f44f0 100644
--- a/app/controllers/api/v1/follows_controller.rb
+++ b/app/controllers/api/v1/follows_controller.rb
@@ -7,7 +7,7 @@ class Api::V1::FollowsController < ApiController
respond_to :json
def create
- raise ActiveRecord::RecordNotFound if params[:uri].blank?
+ raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
render action: :show
@@ -16,6 +16,10 @@ class Api::V1::FollowsController < ApiController
private
def target_uri
- params[:uri].strip.gsub(/\A@/, '')
+ follow_params[:uri].strip.gsub(/\A@/, '')
+ end
+
+ def follow_params
+ params.permit(:uri)
end
end
diff --git a/app/controllers/api/v1/media_controller.rb b/app/controllers/api/v1/media_controller.rb
index f8139ade..aed3578d 100644
--- a/app/controllers/api/v1/media_controller.rb
+++ b/app/controllers/api/v1/media_controller.rb
@@ -10,10 +10,16 @@ class Api::V1::MediaController < ApiController
respond_to :json
def create
- @media = MediaAttachment.create!(account: current_user.account, file: params[:file])
+ @media = MediaAttachment.create!(account: current_user.account, file: media_params[:file])
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: { error: 'File type of uploaded media could not be verified' }, status: 422
rescue Paperclip::Error
render json: { error: 'Error processing thumbnail for uploaded media' }, status: 500
end
+
+ private
+
+ def media_params
+ params.permit(:file)
+ end
end
diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb
index 6f48de04..cbd98732 100644
--- a/app/controllers/api/v1/mutes_controller.rb
+++ b/app/controllers/api/v1/mutes_controller.rb
@@ -11,11 +11,15 @@ class Api::V1::MutesController < ApiController
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.target_account_id] }
- # set_account_counters_maps(@accounts)
-
- next_path = api_v1_mutes_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
- prev_path = api_v1_mutes_url(since_id: results.first.id) unless results.empty?
+ next_path = api_v1_mutes_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ prev_path = api_v1_mutes_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
end
+
+ private
+
+ def pagination_params(core_params)
+ params.permit(:limit).merge(core_params)
+ end
end
diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb
index 7bbc5419..3cff2998 100644
--- a/app/controllers/api/v1/notifications_controller.rb
+++ b/app/controllers/api/v1/notifications_controller.rb
@@ -9,16 +9,14 @@ class Api::V1::NotificationsController < ApiController
DEFAULT_NOTIFICATIONS_LIMIT = 15
def index
- @notifications = Notification.where(account: current_account).browserable.paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
+ @notifications = Notification.where(account: current_account).browserable(exclude_types).paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
@notifications = cache_collection(@notifications, Notification)
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
set_maps(statuses)
- # set_counters_maps(statuses)
- # set_account_counters_maps(@notifications.map(&:from_account))
- next_path = api_v1_notifications_url(max_id: @notifications.last.id) unless @notifications.empty?
- prev_path = api_v1_notifications_url(since_id: @notifications.first.id) unless @notifications.empty?
+ next_path = api_v1_notifications_url(pagination_params(max_id: @notifications.last.id)) unless @notifications.empty?
+ prev_path = api_v1_notifications_url(pagination_params(since_id: @notifications.first.id)) unless @notifications.empty?
set_pagination_headers(next_path, prev_path)
end
@@ -31,4 +29,16 @@ class Api::V1::NotificationsController < ApiController
Notification.where(account: current_account).delete_all
render_empty
end
+
+ private
+
+ def exclude_types
+ val = params.permit(exclude_types: [])[:exclude_types] || []
+ val = [val] unless val.is_a?(Enumerable)
+ val
+ end
+
+ def pagination_params(core_params)
+ params.permit(:limit, exclude_types: []).merge(core_params)
+ end
end
diff --git a/app/controllers/api/v1/reports_controller.rb b/app/controllers/api/v1/reports_controller.rb
index 46bdddbc..f83c573c 100644
--- a/app/controllers/api/v1/reports_controller.rb
+++ b/app/controllers/api/v1/reports_controller.rb
@@ -12,13 +12,19 @@ class Api::V1::ReportsController < ApiController
end
def create
- status_ids = params[:status_ids].is_a?(Enumerable) ? params[:status_ids] : [params[:status_ids]]
+ status_ids = report_params[:status_ids].is_a?(Enumerable) ? report_params[:status_ids] : [report_params[:status_ids]]
@report = Report.create!(account: current_account,
- target_account: Account.find(params[:account_id]),
+ target_account: Account.find(report_params[:account_id]),
status_ids: Status.find(status_ids).pluck(:id),
- comment: params[:comment])
+ comment: report_params[:comment])
render :show
end
+
+ private
+
+ def report_params
+ params.permit(:account_id, :comment, status_ids: [])
+ end
end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 024258c0..1976ce33 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -23,7 +23,6 @@ class Api::V1::StatusesController < ApiController
statuses = [@status] + @context[:ancestors] + @context[:descendants]
set_maps(statuses)
- # set_counters_maps(statuses)
end
def card
@@ -36,10 +35,8 @@ class Api::V1::StatusesController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |r| accounts[r.account_id] }
- # set_account_counters_maps(@accounts)
-
- next_path = reblogged_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
- prev_path = reblogged_by_api_v1_status_url(since_id: results.first.id) unless results.empty?
+ next_path = reblogged_by_api_v1_status_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ prev_path = reblogged_by_api_v1_status_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@@ -51,10 +48,8 @@ class Api::V1::StatusesController < ApiController
accounts = Account.where(id: results.map(&:account_id)).map { |a| [a.id, a] }.to_h
@accounts = results.map { |f| accounts[f.account_id] }
- # set_account_counters_maps(@accounts)
-
- next_path = favourited_by_api_v1_status_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
- prev_path = favourited_by_api_v1_status_url(since_id: results.first.id) unless results.empty?
+ next_path = favourited_by_api_v1_status_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
+ prev_path = favourited_by_api_v1_status_url(pagination_params(since_id: results.first.id)) unless results.empty?
set_pagination_headers(next_path, prev_path)
@@ -62,11 +57,11 @@ class Api::V1::StatusesController < ApiController
end
def create
- @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids],
- sensitive: params[:sensitive],
- spoiler_text: params[:spoiler_text],
- visibility: params[:visibility],
- application: doorkeeper_token.application)
+ @status = PostStatusService.new.call(current_user.account, status_params[:status], status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]), media_ids: status_params[:media_ids],
+ sensitive: status_params[:sensitive],
+ spoiler_text: status_params[:spoiler_text],
+ visibility: status_params[:visibility],
+ application: doorkeeper_token.application)
render action: :show
end
@@ -111,4 +106,12 @@ class Api::V1::StatusesController < ApiController
@status = Status.find(params[:id])
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
end
+
+ def status_params
+ params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, media_ids: [])
+ end
+
+ def pagination_params(core_params)
+ params.permit(:limit).merge(core_params)
+ end
end
diff --git a/app/controllers/api/v1/timelines_controller.rb b/app/controllers/api/v1/timelines_controller.rb
index 0446b9e4..e55e7d71 100644
--- a/app/controllers/api/v1/timelines_controller.rb
+++ b/app/controllers/api/v1/timelines_controller.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
class Api::V1::TimelinesController < ApiController
- before_action -> { doorkeeper_authorize! :read }
- before_action :require_user!, only: [:home, :mentions]
+ before_action -> { doorkeeper_authorize! :read }, only: [:home]
+ before_action :require_user!, only: [:home]
respond_to :json
@@ -11,11 +11,9 @@ class Api::V1::TimelinesController < ApiController
@statuses = cache_collection(@statuses)
set_maps(@statuses)
- # set_counters_maps(@statuses)
- # set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
- next_path = api_v1_home_timeline_url(max_id: @statuses.last.id) unless @statuses.empty?
- prev_path = api_v1_home_timeline_url(since_id: @statuses.first.id) unless @statuses.empty?
+ next_path = api_v1_home_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
+ prev_path = api_v1_home_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
@@ -27,11 +25,9 @@ class Api::V1::TimelinesController < ApiController
@statuses = cache_collection(@statuses)
set_maps(@statuses)
- # set_counters_maps(@statuses)
- # set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
- next_path = api_v1_public_timeline_url(max_id: @statuses.last.id) unless @statuses.empty?
- prev_path = api_v1_public_timeline_url(since_id: @statuses.first.id) unless @statuses.empty?
+ next_path = api_v1_public_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
+ prev_path = api_v1_public_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
@@ -44,11 +40,9 @@ class Api::V1::TimelinesController < ApiController
@statuses = cache_collection(@statuses)
set_maps(@statuses)
- # set_counters_maps(@statuses)
- # set_account_counters_maps(@statuses.flat_map { |s| [s.account, s.reblog? ? s.reblog.account : nil] }.compact.uniq)
- next_path = api_v1_hashtag_timeline_url(params[:id], max_id: @statuses.last.id) unless @statuses.empty?
- prev_path = api_v1_hashtag_timeline_url(params[:id], since_id: @statuses.first.id) unless @statuses.empty?
+ next_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
+ prev_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
set_pagination_headers(next_path, prev_path)
@@ -60,4 +54,8 @@ class Api::V1::TimelinesController < ApiController
def cache_collection(raw)
super(raw, Status)
end
+
+ def pagination_params(core_params)
+ params.permit(:local, :limit).merge(core_params)
+ end
end
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index db16f82e..57604f1d 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -7,6 +7,7 @@ class ApiController < ApplicationController
protect_from_forgery with: :null_session
skip_before_action :verify_authenticity_token
+ skip_before_action :store_current_location
before_action :set_rate_limit_headers
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ef936489..61ca7112 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -7,6 +7,7 @@ class ApplicationController < ActionController::Base
force_ssl if: "Rails.env.production? && ENV['LOCAL_HTTPS'] == 'true'"
+ include Localized
helper_method :current_account
rescue_from ActionController::RoutingError, with: :not_found
@@ -14,7 +15,6 @@ class ApplicationController < ActionController::Base
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
- before_action :set_locale
before_action :set_user_activity
before_action :check_suspension, if: :user_signed_in?
@@ -28,18 +28,18 @@ class ApplicationController < ActionController::Base
store_location_for(:user, request.url)
end
- def set_locale
- I18n.locale = current_user.try(:locale) || I18n.default_locale
- rescue I18n::InvalidLocale
- I18n.locale = I18n.default_locale
- end
-
def require_admin!
redirect_to root_path unless current_user&.admin?
end
def set_user_activity
- current_user.touch(:current_sign_in_at) if !current_user.nil? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < 24.hours.ago)
+ return unless !current_user.nil? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < 24.hours.ago)
+
+ # Mark user as signed-in today
+ current_user.update_tracked_fields(request)
+
+ # If the sign in is after a two week break, we need to regenerate their feed
+ RegenerationWorker.perform_async(current_user.account_id) if current_user.last_sign_in_at < 14.days.ago
end
def check_suspension
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 501e6680..4881c074 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -3,7 +3,7 @@
class Auth::RegistrationsController < Devise::RegistrationsController
layout :determine_layout
- before_action :check_single_user_mode
+ before_action :check_enabled_registrations, only: [:new, :create]
before_action :configure_sign_up_params, only: [:create]
protected
@@ -27,12 +27,12 @@ class Auth::RegistrationsController < Devise::RegistrationsController
new_user_session_path
end
- def check_single_user_mode
- redirect_to root_path if Rails.configuration.x.single_user_mode
+ def check_enabled_registrations
+ redirect_to root_path if Rails.configuration.x.single_user_mode || !Setting.open_registrations
end
-
+
private
-
+
def determine_layout
%w(edit update).include?(action_name) ? 'admin' : 'auth'
end
diff --git a/app/controllers/concerns/localized.rb b/app/controllers/concerns/localized.rb
new file mode 100644
index 00000000..22bb5b21
--- /dev/null
+++ b/app/controllers/concerns/localized.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Localized
+ extend ActiveSupport::Concern
+
+ included do
+ around_action :set_locale
+ end
+
+ private
+
+ def set_locale
+ locale = default_locale
+
+ if user_signed_in?
+ begin
+ locale = current_user.try(:locale) || default_locale
+ rescue I18n::InvalidLocale
+ locale = default_locale
+ end
+ end
+
+ I18n.with_locale(locale) do
+ yield
+ end
+ end
+
+ def default_locale
+ ENV.fetch('DEFAULT_LOCALE') {
+ http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
+ }
+ end
+end
diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb
index feaad04f..e9cdf9fa 100644
--- a/app/controllers/oauth/authorizations_controller.rb
+++ b/app/controllers/oauth/authorizations_controller.rb
@@ -6,6 +6,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
before_action :store_current_location
before_action :authenticate_resource_owner!
+ include Localized
+
private
def store_current_location
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
new file mode 100644
index 00000000..395fbc51
--- /dev/null
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController
+ skip_before_action :authenticate_resource_owner!
+
+ before_action :store_current_location
+ before_action :authenticate_resource_owner!
+
+ include Localized
+
+ private
+
+ def store_current_location
+ store_location_for(:user, request.url)
+ end
+end
diff --git a/app/controllers/remote_follow_controller.rb b/app/controllers/remote_follow_controller.rb
index 7d4bfe6c..22e37683 100644
--- a/app/controllers/remote_follow_controller.rb
+++ b/app/controllers/remote_follow_controller.rb
@@ -8,6 +8,7 @@ class RemoteFollowController < ApplicationController
def new
@remote_follow = RemoteFollow.new
+ @remote_follow.acct = session[:remote_follow] if session.key?(:remote_follow)
end
def create
@@ -22,7 +23,9 @@ class RemoteFollowController < ApplicationController
render(:new) && return
end
- redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: "#{@account.username}@#{Rails.configuration.x.local_domain}").to_s
+ session[:remote_follow] = @remote_follow.acct
+
+ redirect_to Addressable::Template.new(redirect_url_link.template).expand(uri: @account.to_webfinger_s).to_s
else
render :new
end
diff --git a/app/controllers/settings/exports/base_controller.rb b/app/controllers/settings/exports/base_controller.rb
new file mode 100644
index 00000000..c082ed80
--- /dev/null
+++ b/app/controllers/settings/exports/base_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Settings
+ module Exports
+ class BaseController < ApplicationController
+ before_action :authenticate_user!
+
+ def index
+ @export = Export.new(current_account)
+
+ respond_to do |format|
+ format.csv { send_data export_data, filename: export_filename }
+ end
+ end
+
+ private
+
+ def export_filename
+ "#{controller_name}.csv"
+ end
+ end
+ end
+end
diff --git a/app/controllers/settings/exports/blocked_accounts_controller.rb b/app/controllers/settings/exports/blocked_accounts_controller.rb
new file mode 100644
index 00000000..f1115b21
--- /dev/null
+++ b/app/controllers/settings/exports/blocked_accounts_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Settings
+ module Exports
+ class BlockedAccountsController < BaseController
+ private
+
+ def export_data
+ @export.to_blocked_accounts_csv
+ end
+ end
+ end
+end
diff --git a/app/controllers/settings/exports/following_accounts_controller.rb b/app/controllers/settings/exports/following_accounts_controller.rb
new file mode 100644
index 00000000..0011d246
--- /dev/null
+++ b/app/controllers/settings/exports/following_accounts_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Settings
+ module Exports
+ class FollowingAccountsController < BaseController
+ private
+
+ def export_data
+ @export.to_following_accounts_csv
+ end
+ end
+ end
+end
diff --git a/app/controllers/settings/exports/muted_accounts_controller.rb b/app/controllers/settings/exports/muted_accounts_controller.rb
new file mode 100644
index 00000000..dfe72cfc
--- /dev/null
+++ b/app/controllers/settings/exports/muted_accounts_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Settings
+ module Exports
+ class MutedAccountsController < BaseController
+ private
+
+ def export_data
+ @export.to_muted_accounts_csv
+ end
+ end
+ end
+end
diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index 4fcec532..ae62f00c 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -1,46 +1,11 @@
# frozen_string_literal: true
-require 'csv'
-
class Settings::ExportsController < ApplicationController
layout 'admin'
before_action :authenticate_user!
- before_action :set_account
def show
- @total_storage = current_account.media_attachments.sum(:file_file_size)
- @total_follows = current_account.following.count
- @total_blocks = current_account.blocking.count
- end
-
- def download_following_list
- @accounts = current_account.following
-
- respond_to do |format|
- format.csv { render text: accounts_list_to_csv(@accounts) }
- end
- end
-
- def download_blocking_list
- @accounts = current_account.blocking
-
- respond_to do |format|
- format.csv { render text: accounts_list_to_csv(@accounts) }
- end
- end
-
- private
-
- def set_account
- @account = current_user.account
- end
-
- def accounts_list_to_csv(list)
- CSV.generate do |csv|
- list.each do |account|
- csv << [(account.local? ? "#{account.username}@#{Rails.configuration.x.local_domain}" : account.acct)]
- end
- end
+ @export = Export.new(current_account)
end
end
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index 60400e46..c758e4ef 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -23,8 +23,9 @@ class Settings::PreferencesController < ApplicationController
}
current_user.settings['default_privacy'] = user_params[:setting_default_privacy]
+ current_user.settings['boost_modal'] = user_params[:setting_boost_modal] == '1'
- if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy))
+ if current_user.update(user_params.except(:notification_emails, :interactions, :setting_default_privacy, :setting_boost_modal))
redirect_to settings_preferences_path, notice: I18n.t('generic.changes_saved_msg')
else
render action: :show
@@ -34,6 +35,6 @@ class Settings::PreferencesController < ApplicationController
private
def user_params
- params.require(:user).permit(:locale, :setting_default_privacy, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
+ params.require(:user).permit(:locale, :setting_default_privacy, :setting_boost_modal, notification_emails: [:follow, :follow_request, :reblog, :favourite, :mention, :digest], interactions: [:must_be_follower, :must_be_following])
end
end
diff --git a/app/controllers/settings/two_factor_auths_controller.rb b/app/controllers/settings/two_factor_auths_controller.rb
index cfee9239..203d1fc4 100644
--- a/app/controllers/settings/two_factor_auths_controller.rb
+++ b/app/controllers/settings/two_factor_auths_controller.rb
@@ -5,19 +5,29 @@ class Settings::TwoFactorAuthsController < ApplicationController
before_action :authenticate_user!
- def show
- return unless current_user.otp_required_for_login
+ def show; end
- @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain)
- @qrcode = RQRCode::QRCode.new(@provision_url)
+ def new
+ redirect_to settings_two_factor_auth_path if current_user.otp_required_for_login
+
+ @confirmation = Form::TwoFactorConfirmation.new
+ current_user.otp_secret = User.generate_otp_secret(32)
+ current_user.save!
+ set_qr_code
end
- def enable
- current_user.otp_required_for_login = true
- current_user.otp_secret = User.generate_otp_secret
- current_user.save!
+ def create
+ if current_user.validate_and_consume_otp!(confirmation_params[:code])
+ current_user.otp_required_for_login = true
+ current_user.save!
- redirect_to settings_two_factor_auth_path
+ redirect_to settings_two_factor_auth_path, notice: I18n.t('two_factor_auth.enabled_success')
+ else
+ @confirmation = Form::TwoFactorConfirmation.new
+ set_qr_code
+ flash.now[:alert] = I18n.t('two_factor_auth.wrong_code')
+ render action: :new
+ end
end
def disable
@@ -26,4 +36,15 @@ class Settings::TwoFactorAuthsController < ApplicationController
redirect_to settings_two_factor_auth_path
end
+
+ private
+
+ def set_qr_code
+ @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain)
+ @qrcode = RQRCode::QRCode.new(@provision_url)
+ end
+
+ def confirmation_params
+ params.require(:form_two_factor_confirmation).permit(:code)
+ end
end
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
index de38b360..a9ee7350 100644
--- a/app/controllers/stream_entries_controller.rb
+++ b/app/controllers/stream_entries_controller.rb
@@ -19,14 +19,14 @@ class StreamEntriesController < ApplicationController
end
end
- format.atom
+ format.atom do
+ render xml: AtomSerializer.render(AtomSerializer.new.entry(@stream_entry, true))
+ end
end
end
def embed
response.headers['X-Frame-Options'] = 'ALLOWALL'
- @external_links = true
-
return gone if @stream_entry.activity.nil?
render layout: 'embedded'
diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb
new file mode 100644
index 00000000..2f0960ac
--- /dev/null
+++ b/app/controllers/well_known/host_meta_controller.rb
@@ -0,0 +1,13 @@
+ # frozen_string_literal: true
+
+module WellKnown
+ class HostMetaController < ApplicationController
+ def show
+ @webfinger_template = "#{webfinger_url}?resource={uri}"
+
+ respond_to do |format|
+ format.xml { render content_type: 'application/xrd+xml' }
+ end
+ end
+ end
+end
diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb
new file mode 100644
index 00000000..1a8ef5f9
--- /dev/null
+++ b/app/controllers/well_known/webfinger_controller.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module WellKnown
+ class WebfingerController < ApplicationController
+ def show
+ @account = Account.find_local!(username_from_resource)
+ @canonical_account_uri = @account.to_webfinger_s
+ @magic_key = pem_to_magic_key(@account.keypair.public_key)
+
+ respond_to do |format|
+ format.xml { render content_type: 'application/xrd+xml' }
+ format.json { render content_type: 'application/jrd+json' }
+ end
+ rescue ActiveRecord::RecordNotFound
+ head 404
+ end
+
+ private
+
+ def username_from_resource
+ WebfingerResource.new(resource_param).username
+ end
+
+ def pem_to_magic_key(public_key)
+ modulus, exponent = [public_key.n, public_key.e].map do |component|
+ result = []
+
+ until component.zero?
+ result << [component % 256].pack('C')
+ component >>= 8
+ end
+
+ result.reverse.join
+ end
+
+ (['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
+ end
+
+ def resource_param
+ params.require(:resource)
+ end
+ end
+end
diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb
deleted file mode 100644
index 6db87cef..00000000
--- a/app/controllers/xrd_controller.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-# frozen_string_literal: true
-
-class XrdController < ApplicationController
- before_action :set_default_format_json, only: :webfinger
- before_action :set_default_format_xml, only: :host_meta
-
- def host_meta
- @webfinger_template = "#{webfinger_url}?resource={uri}"
-
- respond_to do |format|
- format.xml { render content_type: 'application/xrd+xml' }
- end
- end
-
- def webfinger
- @account = Account.find_local!(username_from_resource)
- @canonical_account_uri = "acct:#{@account.username}@#{Rails.configuration.x.local_domain}"
- @magic_key = pem_to_magic_key(@account.keypair.public_key)
-
- respond_to do |format|
- format.xml { render content_type: 'application/xrd+xml' }
- format.json { render content_type: 'application/jrd+json' }
- end
- rescue ActiveRecord::RecordNotFound
- head 404
- end
-
- private
-
- def set_default_format_xml
- request.format = 'xml' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil?
- end
-
- def set_default_format_json
- request.format = 'json' if request.headers['HTTP_ACCEPT'].nil? && params[:format].nil?
- end
-
- def username_from_resource
- if resource_param =~ /\Ahttps?:\/\//
- path_params = Rails.application.routes.recognize_path(resource_param)
- raise ActiveRecord::RecordNotFound unless path_params[:controller] == 'users' && path_params[:action] == 'show'
- path_params[:username]
- else
- username, domain = resource_param.gsub(/\Aacct:/, '').split('@')
- raise ActiveRecord::RecordNotFound unless TagManager.instance.local_domain?(domain)
- username
- end
- end
-
- def pem_to_magic_key(public_key)
- modulus, exponent = [public_key.n, public_key.e].map do |component|
- result = []
-
- until component.zero?
- result << [component % 256].pack('C')
- component >>= 8
- end
-
- result.reverse.join
- end
-
- (['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
- end
-
- def resource_param
- params.require(:resource)
- end
-end
diff --git a/app/helpers/about_helper.rb b/app/helpers/about_helper.rb
deleted file mode 100644
index 0f57a7b5..00000000
--- a/app/helpers/about_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module AboutHelper
-end
diff --git a/app/helpers/accounts_helper.rb b/app/helpers/accounts_helper.rb
deleted file mode 100644
index af23a78d..00000000
--- a/app/helpers/accounts_helper.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-# frozen_string_literal: true
-
-module AccountsHelper
- def pagination_options
- {
- previous_label: safe_join([fa_icon('chevron-left'), t('pagination.prev')], ' '),
- next_label: safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '),
- inner_window: 1,
- outer_window: 0,
- }
- end
-end
diff --git a/app/helpers/admin/accounts_helper.rb b/app/helpers/admin/accounts_helper.rb
index c539229b..6cda7781 100644
--- a/app/helpers/admin/accounts_helper.rb
+++ b/app/helpers/admin/accounts_helper.rb
@@ -6,10 +6,21 @@ module Admin::AccountsHelper
end
def filter_link_to(text, more_params)
- link_to text, filter_params(more_params), class: params.merge(more_params).compact == params.compact ? 'selected' : ''
+ new_url = filtered_url_for(more_params)
+ link_to text, new_url, class: filter_link_class(new_url)
end
def table_link_to(icon, text, path, options = {})
link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link')
end
+
+ private
+
+ def filter_link_class(new_url)
+ filtered_url_for(params) == new_url ? 'selected' : ''
+ end
+
+ def filtered_url_for(params)
+ url_for filter_params(params)
+ end
end
diff --git a/app/helpers/admin/domain_blocks_helper.rb b/app/helpers/admin/domain_blocks_helper.rb
deleted file mode 100644
index d66c8d5e..00000000
--- a/app/helpers/admin/domain_blocks_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module Admin::DomainBlocksHelper
-end
diff --git a/app/helpers/admin/pubsubhubbub_helper.rb b/app/helpers/admin/pubsubhubbub_helper.rb
deleted file mode 100644
index c2fc2e7d..00000000
--- a/app/helpers/admin/pubsubhubbub_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module Admin::PubsubhubbubHelper
-end
diff --git a/app/helpers/atom_builder_helper.rb b/app/helpers/atom_builder_helper.rb
deleted file mode 100644
index b750eeb0..00000000
--- a/app/helpers/atom_builder_helper.rb
+++ /dev/null
@@ -1,285 +0,0 @@
-# frozen_string_literal: true
-
-module AtomBuilderHelper
- def stream_updated_at
- if @account.stream_entries.last
- (@account.updated_at > @account.stream_entries.last.created_at ? @account.updated_at : @account.stream_entries.last.created_at)
- else
- @account.updated_at
- end
- end
-
- def entry(xml, is_root = false, &block)
- if is_root
- root_tag(xml, :entry, &block)
- else
- xml.entry(&block)
- end
- end
-
- def feed(xml, &block)
- root_tag(xml, :feed, &block)
- end
-
- def unique_id(xml, date, id, type)
- xml.id_ TagManager.instance.unique_tag(date, id, type)
- end
-
- def simple_id(xml, id)
- xml.id_ id
- end
-
- def published_at(xml, date)
- xml.published date.iso8601
- end
-
- def updated_at(xml, date)
- xml.updated date.iso8601
- end
-
- def verb(xml, verb)
- xml['activity'].send('verb', TagManager::VERBS[verb])
- end
-
- def content(xml, content, warning = nil)
- xml.summary(warning) unless warning.blank?
- xml.content({ type: 'html' }, content) unless content.blank?
- end
-
- def title(xml, title)
- xml.title strip_tags(title || '').truncate(80)
- end
-
- def author(xml, &block)
- xml.author(&block)
- end
-
- def category(xml, term)
- xml.category(term: term)
- end
-
- def target(xml, &block)
- xml['activity'].object(&block)
- end
-
- def object_type(xml, type)
- xml['activity'].send('object-type', TagManager::TYPES[type])
- end
-
- def uri(xml, uri)
- xml.uri uri
- end
-
- def name(xml, name)
- xml.name name
- end
-
- def summary(xml, summary)
- xml.summary(summary) unless summary.blank?
- end
-
- def subtitle(xml, subtitle)
- xml.subtitle(subtitle) unless subtitle.blank?
- end
-
- def link_alternate(xml, url)
- xml.link(rel: 'alternate', type: 'text/html', href: url)
- end
-
- def link_self(xml, url)
- xml.link(rel: 'self', type: 'application/atom+xml', href: url)
- end
-
- def link_next(xml, url)
- xml.link(rel: 'next', type: 'application/atom+xml', href: url)
- end
-
- def link_hub(xml, url)
- xml.link(rel: 'hub', href: url)
- end
-
- def link_salmon(xml, url)
- xml.link(rel: 'salmon', href: url)
- end
-
- def portable_contact(xml, account)
- xml['poco'].preferredUsername account.username
- xml['poco'].displayName(account.display_name) unless account.display_name.blank?
- xml['poco'].note(Formatter.instance.simplified_format(account)) unless account.note.blank?
- end
-
- def in_reply_to(xml, uri, url)
- xml['thr'].send('in-reply-to', ref: uri, href: url, type: 'text/html')
- end
-
- def link_mention(xml, account)
- xml.link(:rel => 'mentioned', :href => TagManager.instance.uri_for(account), 'ostatus:object-type' => TagManager::TYPES[:person])
- end
-
- def link_enclosure(xml, media)
- xml.link(rel: 'enclosure', href: full_asset_url(media.file.url(:original, false)), type: media.file_content_type, length: media.file_file_size)
- end
-
- def link_avatar(xml, account)
- single_link_avatar(xml, account, :original, 120)
- end
-
- def link_header(xml, account)
- xml.link('rel' => 'header', 'type' => account.header_content_type, 'media:width' => 700, 'media:height' => 335, 'href' => full_asset_url(account.header.url(:original)))
- end
-
- def logo(xml, url)
- xml.logo url
- end
-
- def email(xml, email)
- xml.email email
- end
-
- def conditionally_formatted(activity)
- if activity.is_a?(Status)
- Formatter.instance.format(activity.reblog? ? activity.reblog : activity)
- elsif activity.nil?
- nil
- else
- activity.content
- end
- end
-
- def link_visibility(xml, item)
- return unless item.respond_to?(:visibility) && item.public_visibility?
- xml.link(:rel => 'mentioned', :href => TagManager::COLLECTIONS[:public], 'ostatus:object-type' => TagManager::TYPES[:collection])
- end
-
- def privacy_scope(xml, level)
- xml['mastodon'].scope(level)
- end
-
- def include_author(xml, account)
- simple_id xml, TagManager.instance.uri_for(account)
- object_type xml, :person
- uri xml, TagManager.instance.uri_for(account)
- name xml, account.username
- email xml, account.local? ? "#{account.acct}@#{Rails.configuration.x.local_domain}" : account.acct
- summary xml, account.note
- link_alternate xml, TagManager.instance.url_for(account)
- link_avatar xml, account
- link_header xml, account
- portable_contact xml, account
- privacy_scope xml, account.locked? ? :private : :public
- end
-
- def rich_content(xml, activity)
- if activity.is_a?(Status)
- content xml, conditionally_formatted(activity), activity.spoiler_text
- else
- content xml, conditionally_formatted(activity)
- end
- end
-
- def include_target(xml, target)
- simple_id xml, TagManager.instance.uri_for(target)
-
- if target.object_type == :person
- include_author xml, target
- else
- object_type xml, target.object_type
- verb xml, target.verb
- title xml, target.title
- link_alternate xml, TagManager.instance.url_for(target)
- end
-
- # Statuses have content and author
- return unless target.is_a?(Status)
-
- rich_content xml, target
- verb xml, target.verb
- published_at xml, target.created_at
- updated_at xml, target.updated_at
-
- author(xml) do
- include_author xml, target.account
- end
-
- if target.reply?
- in_reply_to xml, TagManager.instance.uri_for(target.thread), TagManager.instance.url_for(target.thread)
- end
-
- link_visibility xml, target
-
- target.mentions.each do |mention|
- link_mention xml, mention.account
- end
-
- target.media_attachments.each do |media|
- link_enclosure xml, media
- end
-
- target.tags.each do |tag|
- category xml, tag.name
- end
-
- category(xml, 'nsfw') if target.sensitive?
- privacy_scope(xml, target.visibility)
- end
-
- def include_entry(xml, stream_entry)
- unique_id xml, stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type
- published_at xml, stream_entry.created_at
- updated_at xml, stream_entry.updated_at
- title xml, stream_entry.title
- rich_content xml, stream_entry.activity
- verb xml, stream_entry.verb
- link_self xml, account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom')
- link_alternate xml, account_stream_entry_url(stream_entry.account, stream_entry)
- object_type xml, stream_entry.object_type
-
- # Comments need thread element
- if stream_entry.threaded?
- in_reply_to xml, TagManager.instance.uri_for(stream_entry.thread), TagManager.instance.url_for(stream_entry.thread)
- end
-
- if stream_entry.targeted?
- target(xml) do
- include_target(xml, stream_entry.target)
- end
- end
-
- link_visibility xml, stream_entry.activity
-
- stream_entry.mentions.each do |mentioned|
- link_mention xml, mentioned
- end
-
- return unless stream_entry.activity.is_a?(Status)
-
- stream_entry.activity.media_attachments.each do |media|
- link_enclosure xml, media
- end
-
- stream_entry.activity.tags.each do |tag|
- category xml, tag.name
- end
-
- category(xml, 'nsfw') if stream_entry.activity.sensitive?
- privacy_scope(xml, stream_entry.activity.visibility)
- end
-
- private
-
- def root_tag(xml, tag, &block)
- xml.send(tag, {
- 'xmlns' => TagManager::XMLNS,
- 'xmlns:thr' => TagManager::THR_XMLNS,
- 'xmlns:activity' => TagManager::AS_XMLNS,
- 'xmlns:poco' => TagManager::POCO_XMLNS,
- 'xmlns:media' => TagManager::MEDIA_XMLNS,
- 'xmlns:ostatus' => TagManager::OS_XMLNS,
- 'xmlns:mastodon' => TagManager::MTDN_XMLNS,
- }, &block)
- end
-
- def single_link_avatar(xml, account, size, px)
- xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => px, 'media:height' => px, 'href' => full_asset_url(account.avatar.url(size)))
- end
-end
diff --git a/app/helpers/authorize_follow_helper.rb b/app/helpers/authorize_follow_helper.rb
deleted file mode 100644
index 99ee03c2..00000000
--- a/app/helpers/authorize_follow_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module AuthorizeFollowHelper
-end
diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb
index 74215e8d..c6ffe184 100644
--- a/app/helpers/settings_helper.rb
+++ b/app/helpers/settings_helper.rb
@@ -5,11 +5,19 @@ module SettingsHelper
en: 'English',
de: 'Deutsch',
es: 'Español',
- pt: 'Português',
+ eo: 'Esperanto',
fr: 'Français',
hu: 'Magyar',
+ nl: 'Nederlands',
+ no: 'Norsk',
+ pt: 'Português',
+ fi: 'Suomi',
+ ru: 'Русский',
uk: 'Українська',
+ ja: '日本語',
'zh-CN': '简体中文',
+ 'zh-HK': '繁體中文(香港)',
+ bg: 'Български',
}.freeze
def human_locale(locale)
diff --git a/app/helpers/site_title_helper.rb b/app/helpers/site_title_helper.rb
new file mode 100644
index 00000000..d2caa920
--- /dev/null
+++ b/app/helpers/site_title_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module SiteTitleHelper
+ def site_title
+ Setting.site_title.to_s
+ end
+end
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index a26e912a..00a01df4 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -2,40 +2,38 @@
module StreamEntriesHelper
def display_name(account)
- account.display_name.blank? ? account.username : account.display_name
+ account.display_name.presence || account.username
+ end
+
+ def stream_link_target
+ embedded_view? ? '_blank' : nil
end
def acct(account)
- "@#{account.acct}#{@external_links && account.local? ? "@#{Rails.configuration.x.local_domain}" : ''}"
+ "@#{account.acct}#{embedded_view? && account.local? ? "@#{Rails.configuration.x.local_domain}" : ''}"
end
- def avatar_for_status_url(status)
- status.reblog? ? status.reblog.account.avatar.url(:original) : status.account.avatar.url(:original)
- end
-
- def entry_classes(status, is_predecessor, is_successor, include_threads)
+ def style_classes(status, is_predecessor, is_successor, include_threads)
classes = ['entry']
- classes << 'entry-reblog u-repost-of h-cite' if status.reblog?
- classes << 'entry-predecessor u-in-reply-to h-cite' if is_predecessor
- classes << 'entry-successor u-comment h-cite' if is_successor
- classes << 'entry-center h-entry' if include_threads
+ classes << 'entry-predecessor' if is_predecessor
+ classes << 'entry-reblog' if status.reblog?
+ classes << 'entry-successor' if is_successor
+ classes << 'entry-center' if include_threads
classes.join(' ')
end
- def relative_time(date)
- date < 5.days.ago ? date.strftime('%d.%m.%Y') : "#{time_ago_in_words(date)} ago"
+ def microformats_classes(status, is_direct_parent, is_direct_child)
+ classes = []
+ classes << 'p-in-reply-to' if is_direct_parent
+ classes << 'p-repost-of' if status.reblog? && is_direct_parent
+ classes << 'p-comment' if is_direct_child
+ classes.join(' ')
end
- def reblogged_by_me_class(status)
- user_signed_in? && @reblogged.key?(status.id) ? 'reblogged' : ''
- end
-
- def favourited_by_me_class(status)
- user_signed_in? && @favourited.key?(status.id) ? 'favourited' : ''
- end
-
- def proper_status(status)
- status.reblog? ? status.reblog : status
+ def microformats_h_class(status, is_predecessor, is_successor, include_threads)
+ return 'h-cite' if is_predecessor || status.reblog || is_successor
+ return 'h-entry' unless include_threads
+ ''
end
def rtl?(text)
@@ -50,4 +48,10 @@ module StreamEntriesHelper
rtl_size / ltr_size > 0.3
end
+
+ private
+
+ def embedded_view?
+ params[:controller] == 'stream_entries' && params[:action] == 'embed'
+ end
end
diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb
deleted file mode 100644
index 5b2b3ca5..00000000
--- a/app/helpers/tags_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module TagsHelper
-end
diff --git a/app/helpers/xrd_helper.rb b/app/helpers/xrd_helper.rb
deleted file mode 100644
index 2281a027..00000000
--- a/app/helpers/xrd_helper.rb
+++ /dev/null
@@ -1,4 +0,0 @@
-# frozen_string_literal: true
-
-module XrdHelper
-end
diff --git a/app/lib/atom_serializer.rb b/app/lib/atom_serializer.rb
new file mode 100644
index 00000000..69e1f153
--- /dev/null
+++ b/app/lib/atom_serializer.rb
@@ -0,0 +1,351 @@
+# frozen_string_literal: true
+
+class AtomSerializer
+ include RoutingHelper
+
+ class << self
+ def render(element)
+ document = Ox::Document.new(version: '1.0')
+ document << element
+ ('' + Ox.dump(element)).force_encoding('UTF-8')
+ end
+ end
+
+ def author(account)
+ author = Ox::Element.new('author')
+
+ uri = TagManager.instance.uri_for(account)
+
+ append_element(author, 'id', uri)
+ append_element(author, 'activity:object-type', TagManager::TYPES[:person])
+ append_element(author, 'uri', uri)
+ append_element(author, 'name', account.username)
+ append_element(author, 'email', account.local? ? account.local_username_and_domain : account.acct)
+ append_element(author, 'summary', account.note)
+ append_element(author, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
+ append_element(author, 'link', nil, rel: :avatar, type: account.avatar_content_type, 'media:width': 120, 'media:height': 120, href: full_asset_url(account.avatar.url(:original)))
+ append_element(author, 'link', nil, rel: :header, type: account.header_content_type, 'media:width': 700, 'media:height': 335, href: full_asset_url(account.header.url(:original)))
+ append_element(author, 'poco:preferredUsername', account.username)
+ append_element(author, 'poco:displayName', account.display_name) if account.display_name?
+ append_element(author, 'poco:note', Formatter.instance.simplified_format(account).to_str) if account.note?
+ append_element(author, 'mastodon:scope', account.locked? ? :private : :public)
+
+ author
+ end
+
+ def feed(account, stream_entries)
+ feed = Ox::Element.new('feed')
+
+ add_namespaces(feed)
+
+ append_element(feed, 'id', account_url(account, format: 'atom'))
+ append_element(feed, 'title', account.display_name)
+ append_element(feed, 'subtitle', account.note)
+ append_element(feed, 'updated', account.updated_at.iso8601)
+ append_element(feed, 'logo', full_asset_url(account.avatar.url(:original)))
+
+ feed << author(account)
+
+ append_element(feed, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(account))
+ append_element(feed, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_url(account, format: 'atom'))
+ append_element(feed, 'link', nil, rel: :next, type: 'application/atom+xml', href: account_url(account, format: 'atom', max_id: stream_entries.last.id)) if stream_entries.size == 20
+ append_element(feed, 'link', nil, rel: :hub, href: api_push_url)
+ append_element(feed, 'link', nil, rel: :salmon, href: api_salmon_url(account.id))
+
+ stream_entries.each do |stream_entry|
+ feed << entry(stream_entry)
+ end
+
+ feed
+ end
+
+ def entry(stream_entry, root = false)
+ entry = Ox::Element.new('entry')
+
+ add_namespaces(entry) if root
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type))
+ append_element(entry, 'published', stream_entry.created_at.iso8601)
+ append_element(entry, 'updated', stream_entry.updated_at.iso8601)
+ append_element(entry, 'title', stream_entry&.status&.title || 'Delete')
+
+ entry << author(stream_entry.account) if root
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[stream_entry.object_type])
+ append_element(entry, 'activity:verb', TagManager::VERBS[stream_entry.verb])
+
+ entry << object(stream_entry.target) if stream_entry.targeted?
+
+ serialize_status_attributes(entry, stream_entry.status) unless stream_entry.status.nil?
+
+ append_element(entry, 'link', nil, rel: :alternate, type: 'text/html', href: account_stream_entry_url(stream_entry.account, stream_entry))
+ append_element(entry, 'link', nil, rel: :self, type: 'application/atom+xml', href: account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom'))
+ append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(stream_entry.thread), href: TagManager.instance.url_for(stream_entry.thread)) if stream_entry.threaded?
+
+ entry
+ end
+
+ def object(status)
+ object = Ox::Element.new('activity:object')
+
+ append_element(object, 'id', TagManager.instance.uri_for(status))
+ append_element(object, 'published', status.created_at.iso8601)
+ append_element(object, 'updated', status.updated_at.iso8601)
+ append_element(object, 'title', status.title)
+
+ object << author(status.account)
+
+ append_element(object, 'activity:object-type', TagManager::TYPES[status.object_type])
+ append_element(object, 'activity:verb', TagManager::VERBS[status.verb])
+
+ serialize_status_attributes(object, status)
+
+ append_element(object, 'link', nil, rel: :alternate, type: 'text/html', href: TagManager.instance.url_for(status))
+ append_element(object, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(status.thread), href: TagManager.instance.url_for(status.thread)) if status.reply? && !status.thread.nil?
+
+ object
+ end
+
+ def follow_salmon(follow)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ description = "#{follow.account.acct} started following #{follow.target_account.acct}"
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(follow.created_at, follow.id, 'Follow'))
+ append_element(entry, 'title', description)
+ append_element(entry, 'content', description, type: :html)
+
+ entry << author(follow.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:follow])
+
+ object = author(follow.target_account)
+ object.value = 'activity:object'
+
+ entry << object
+ entry
+ end
+
+ def follow_request_salmon(follow_request)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(follow_request.created_at, follow_request.id, 'FollowRequest'))
+ append_element(entry, 'title', "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}")
+
+ entry << author(follow_request.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:request_friend])
+
+ object = author(follow_request.target_account)
+ object.value = 'activity:object'
+
+ entry << object
+ entry
+ end
+
+ def authorize_follow_request_salmon(follow_request)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
+ append_element(entry, 'title', "#{follow_request.target_account.acct} authorizes follow request by #{follow_request.account.acct}")
+
+ entry << author(follow_request.target_account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:authorize])
+
+ object = Ox::Element.new('activity:object')
+ object << author(follow_request.account)
+
+ append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
+
+ inner_object = author(follow_request.target_account)
+ inner_object.value = 'activity:object'
+
+ object << inner_object
+ entry << object
+ entry
+ end
+
+ def reject_follow_request_salmon(follow_request)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow_request.id, 'FollowRequest'))
+ append_element(entry, 'title', "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}")
+
+ entry << author(follow_request.target_account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:reject])
+
+ object = Ox::Element.new('activity:object')
+ object << author(follow_request.account)
+
+ append_element(object, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(object, 'activity:verb', TagManager::VERBS[:request_friend])
+
+ inner_object = author(follow_request.target_account)
+ inner_object.value = 'activity:object'
+
+ object << inner_object
+ entry << object
+ entry
+ end
+
+ def unfollow_salmon(follow)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, follow.id, 'Follow'))
+ append_element(entry, 'title', description)
+ append_element(entry, 'content', description, type: :html)
+
+ entry << author(follow.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:unfollow])
+
+ object = author(follow.target_account)
+ object.value = 'activity:object'
+
+ entry << object
+ entry
+ end
+
+ def block_salmon(block)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ description = "#{block.account.acct} no longer wishes to interact with #{block.target_account.acct}"
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
+ append_element(entry, 'title', description)
+
+ entry << author(block.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:block])
+
+ object = author(block.target_account)
+ object.value = 'activity:object'
+
+ entry << object
+ entry
+ end
+
+ def unblock_salmon(block)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ description = "#{block.account.acct} no longer blocks #{block.target_account.acct}"
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, block.id, 'Block'))
+ append_element(entry, 'title', description)
+
+ entry << author(block.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:unblock])
+
+ object = author(block.target_account)
+ object.value = 'activity:object'
+
+ entry << object
+ entry
+ end
+
+ def favourite_salmon(favourite)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(favourite.created_at, favourite.id, 'Favourite'))
+ append_element(entry, 'title', description)
+ append_element(entry, 'content', description, type: :html)
+
+ entry << author(favourite.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:favorite])
+
+ entry << object(favourite.status)
+
+ append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
+
+ entry
+ end
+
+ def unfavourite_salmon(favourite)
+ entry = Ox::Element.new('entry')
+ add_namespaces(entry)
+
+ description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
+
+ append_element(entry, 'id', TagManager.instance.unique_tag(Time.now.utc, favourite.id, 'Favourite'))
+ append_element(entry, 'title', description)
+ append_element(entry, 'content', description, type: :html)
+
+ entry << author(favourite.account)
+
+ append_element(entry, 'activity:object-type', TagManager::TYPES[:activity])
+ append_element(entry, 'activity:verb', TagManager::VERBS[:unfavorite])
+
+ entry << object(favourite.status)
+
+ append_element(entry, 'thr:in-reply-to', nil, ref: TagManager.instance.uri_for(favourite.status), href: TagManager.instance.url_for(favourite.status))
+
+ entry
+ end
+
+ private
+
+ def append_element(parent, name, content = nil, attributes = {})
+ element = Ox::Element.new(name)
+ attributes.each { |k, v| element[k] = v.to_s }
+ element << content.to_s unless content.nil?
+ parent << element
+ end
+
+ def add_namespaces(parent)
+ parent['xmlns'] = TagManager::XMLNS
+ parent['xmlns:thr'] = TagManager::THR_XMLNS
+ parent['xmlns:activity'] = TagManager::AS_XMLNS
+ parent['xmlns:poco'] = TagManager::POCO_XMLNS
+ parent['xmlns:media'] = TagManager::MEDIA_XMLNS
+ parent['xmlns:ostatus'] = TagManager::OS_XMLNS
+ parent['xmlns:mastodon'] = TagManager::MTDN_XMLNS
+ end
+
+ def serialize_status_attributes(entry, status)
+ append_element(entry, 'summary', status.spoiler_text) if status.spoiler_text?
+ append_element(entry, 'content', Formatter.instance.format(status.proper).to_str, type: 'html')
+
+ status.mentions.each do |mentioned|
+ append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:person], href: TagManager.instance.uri_for(mentioned.account))
+ end
+
+ append_element(entry, 'link', nil, rel: :mentioned, 'ostatus:object-type': TagManager::TYPES[:collection], href: TagManager::COLLECTIONS[:public]) if status.public_visibility?
+
+ status.tags.each do |tag|
+ append_element(entry, 'category', nil, term: tag.name)
+ end
+
+ append_element(entry, 'category', nil, term: 'nsfw') if status.sensitive?
+
+ status.media_attachments.each do |media|
+ append_element(entry, 'link', nil, rel: :enclosure, type: media.file_content_type, length: media.file_file_size, href: full_asset_url(media.file.url(:original, false)))
+ end
+
+ append_element(entry, 'mastodon:scope', status.visibility)
+ end
+end
diff --git a/app/lib/exceptions.rb b/app/lib/exceptions.rb
index 200da9fe..9bc802c1 100644
--- a/app/lib/exceptions.rb
+++ b/app/lib/exceptions.rb
@@ -4,4 +4,5 @@ module Mastodon
class Error < StandardError; end
class NotPermittedError < Error; end
class ValidationError < Error; end
+ class RaceConditionError < Error; end
end
diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb
index cd6ca129..339a5c78 100644
--- a/app/lib/feed_manager.rb
+++ b/app/lib/feed_manager.rb
@@ -5,17 +5,17 @@ require 'singleton'
class FeedManager
include Singleton
- MAX_ITEMS = 800
+ MAX_ITEMS = 400
def key(type, id)
"feed:#{type}:#{id}"
end
- def filter?(timeline_type, status, receiver)
+ def filter?(timeline_type, status, receiver_id)
if timeline_type == :home
- filter_from_home?(status, receiver)
+ filter_from_home?(status, receiver_id)
elsif timeline_type == :mentions
- filter_from_mentions?(status, receiver)
+ filter_from_mentions?(status, receiver_id)
else
false
end
@@ -34,12 +34,7 @@ class FeedManager
trim(timeline_type, account.id)
end
- broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status))
- end
-
- def broadcast(timeline_id, options = {})
- options[:queued_at] = (Time.now.to_f * 1000.0).to_i
- ActionCable.server.broadcast("timeline:#{timeline_id}", options)
+ PushUpdateWorker.perform_async(account.id, status.id)
end
def trim(type, account_id)
@@ -50,10 +45,18 @@ class FeedManager
def merge_into_timeline(from_account, into_account)
timeline_key = key(:home, into_account.id)
+ query = from_account.statuses.limit(FeedManager::MAX_ITEMS / 4)
- from_account.statuses.limit(MAX_ITEMS).each do |status|
- next if status.direct_visibility? || filter?(:home, status, into_account)
- redis.zadd(timeline_key, status.id, status.id)
+ if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
+ oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
+ query = query.where('id > ?', oldest_home_score)
+ end
+
+ redis.pipelined do
+ query.each do |status|
+ next if status.direct_visibility? || filter?(:home, status, into_account)
+ redis.zadd(timeline_key, status.id, status.id)
+ end
end
trim(:home, into_account.id)
@@ -61,31 +64,16 @@ class FeedManager
def unmerge_from_timeline(from_account, into_account)
timeline_key = key(:home, into_account.id)
+ oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
- from_account.statuses.select('id').find_each do |status|
- redis.zrem(timeline_key, status.id)
- redis.zremrangebyscore(timeline_key, status.id, status.id)
- end
- end
-
- def inline_render(target_account, template, object)
- rabl_scope = Class.new do
- include RoutingHelper
-
- def initialize(account)
- @account = account
- end
-
- def current_user
- @account.try(:user)
- end
-
- def current_account
- @account
+ from_account.statuses.select('id').where('id > ?', oldest_home_score).reorder(nil).find_in_batches do |statuses|
+ redis.pipelined do
+ statuses.each do |status|
+ redis.zrem(timeline_key, status.id)
+ redis.zremrangebyscore(timeline_key, status.id, status.id)
+ end
end
end
-
- Rabl::Renderer.new(template, object, view_path: 'app/views', format: :json, scope: rabl_scope.new(target_account)).render
end
private
@@ -94,37 +82,39 @@ class FeedManager
Redis.current
end
- def filter_from_home?(status, receiver)
- return true if receiver.muting?(status.account)
+ def filter_from_home?(status, receiver_id)
+ return true if status.reply? && status.in_reply_to_id.nil?
- should_filter = false
+ check_for_mutes = [status.account_id]
+ check_for_mutes.concat([status.reblog.account_id]) if status.reblog?
- if status.reply? && status.in_reply_to_id.nil?
- should_filter = true
- elsif status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply
- should_filter = !receiver.following?(status.in_reply_to_account) # and I'm not following the person it's a reply to
- should_filter &&= !(receiver.id == status.in_reply_to_account_id) # and it's not a reply to me
- should_filter &&= !(status.account_id == status.in_reply_to_account_id) # and it's not a self-reply
- elsif status.reblog? # Filter out a reblog
- should_filter = receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person
- should_filter ||= receiver.muting?(status.reblog.account) # or muting that person
- should_filter ||= status.reblog.account.blocking?(receiver) # or if the author of the reblogged status is blocking me
+ return true if Mute.where(account_id: receiver_id, target_account_id: check_for_mutes).any?
+
+ check_for_blocks = status.mentions.map(&:account_id)
+ check_for_blocks.concat([status.reblog.account_id]) if status.reblog?
+
+ return true if Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any?
+
+ if status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply
+ should_filter = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists? # and I'm not following the person it's a reply to
+ should_filter &&= !(receiver_id == status.in_reply_to_account_id) # and it's not a reply to me
+ should_filter &&= !(status.account_id == status.in_reply_to_account_id) # and it's not a self-reply
+ return should_filter
+ elsif status.reblog? # Filter out a reblog
+ return Block.where(account_id: status.reblog.account_id, target_account_id: receiver_id).exists? # or if the author of the reblogged status is blocking me
end
- should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # or if it mentions someone I blocked
-
- should_filter
+ false
end
- def filter_from_mentions?(status, receiver)
- should_filter = receiver.id == status.account_id # Filter if I'm mentioning myself
- should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked
- should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked
- should_filter ||= (status.account.silenced? && !receiver.following?(status.account)) # of if the account is silenced and I'm not following them
+ def filter_from_mentions?(status, receiver_id)
+ check_for_blocks = [status.account_id]
+ check_for_blocks.concat(status.mentions.pluck(:account_id))
+ check_for_blocks.concat([status.in_reply_to_account]) if status.reply? && !status.in_reply_to_account_id.nil?
- if status.reply? && !status.in_reply_to_account_id.nil? # or it's a reply
- should_filter ||= receiver.blocking?(status.in_reply_to_account) # to a user I blocked
- end
+ should_filter = receiver_id == status.account_id # Filter if I'm mentioning myself
+ should_filter ||= Block.where(account_id: receiver_id, target_account_id: check_for_blocks).any? # or it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked
+ should_filter ||= (status.account.silenced? && !Follow.where(account_id: receiver_id, target_account_id: status.account_id).exists?) # of if the account is silenced and I'm not following them
should_filter
end
diff --git a/app/lib/formatter.rb b/app/lib/formatter.rb
index da7ad202..64b1f86d 100644
--- a/app/lib/formatter.rb
+++ b/app/lib/formatter.rb
@@ -96,6 +96,6 @@ class Formatter
end
def mention_html(match, account)
- "#{match.split('@').first}
@#{account.username}"
+ "#{match.split('@').first}
@#{account.username}"
end
end
diff --git a/app/lib/inline_rabl_scope.rb b/app/lib/inline_rabl_scope.rb
new file mode 100644
index 00000000..26adcb03
--- /dev/null
+++ b/app/lib/inline_rabl_scope.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class InlineRablScope
+ include RoutingHelper
+
+ def initialize(account)
+ @account = account
+ end
+
+ def current_user
+ @account.try(:user)
+ end
+
+ def current_account
+ @account
+ end
+end
diff --git a/app/lib/inline_renderer.rb b/app/lib/inline_renderer.rb
new file mode 100644
index 00000000..8e04ad1d
--- /dev/null
+++ b/app/lib/inline_renderer.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class InlineRenderer
+ def self.render(status, current_account, template)
+ Rabl::Renderer.new(
+ template,
+ status,
+ view_path: 'app/views',
+ format: :json,
+ scope: InlineRablScope.new(current_account)
+ ).render
+ end
+end
diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb
index 2a5e7a40..07b2fb91 100644
--- a/app/lib/tag_manager.rb
+++ b/app/lib/tag_manager.rb
@@ -78,6 +78,8 @@ class TagManager
case target.object_type
when :person
account_url(target)
+ when :note, :comment, :activity
+ unique_tag(target.created_at, target.id, 'Status')
else
unique_tag(target.stream_entry.created_at, target.stream_entry.activity_id, target.stream_entry.activity_type)
end
diff --git a/app/lib/webfinger_resource.rb b/app/lib/webfinger_resource.rb
new file mode 100644
index 00000000..8c5db795
--- /dev/null
+++ b/app/lib/webfinger_resource.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+class WebfingerResource
+ attr_reader :resource
+
+ def initialize(resource)
+ @resource = resource
+ end
+
+ def username
+ case resource
+ when /\Ahttps?/i
+ username_from_url
+ when /\@/
+ username_from_acct
+ else
+ raise(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ private
+
+ def username_from_url
+ if account_show_page?
+ path_params[:username]
+ else
+ raise ActiveRecord::RecordNotFound
+ end
+ end
+
+ def account_show_page?
+ path_params[:controller] == 'accounts' && path_params[:action] == 'show'
+ end
+
+ def path_params
+ Rails.application.routes.recognize_path(resource)
+ end
+
+ def username_from_acct
+ if domain_matches_local?
+ local_username
+ else
+ raise ActiveRecord::RecordNotFound
+ end
+ end
+
+ def split_acct
+ resource_without_acct_string.split('@')
+ end
+
+ def resource_without_acct_string
+ resource.gsub(/\Aacct:/, '')
+ end
+
+ def local_username
+ split_acct.first
+ end
+
+ def local_domain
+ split_acct.last
+ end
+
+ def domain_matches_local?
+ TagManager.instance.local_domain?(local_domain)
+ end
+end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 3b4d4d07..64ca92a3 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -7,9 +7,10 @@ class UserMailer < Devise::Mailer
def confirmation_instructions(user, token, _opts = {})
@resource = user
@token = token
+ @instance = Rails.configuration.x.local_domain
I18n.with_locale(@resource.locale || I18n.default_locale) do
- mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email
+ mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance)
end
end
@@ -18,7 +19,7 @@ class UserMailer < Devise::Mailer
@token = token
I18n.with_locale(@resource.locale || I18n.default_locale) do
- mail to: @resource.email
+ mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
end
end
@@ -26,7 +27,7 @@ class UserMailer < Devise::Mailer
@resource = user
I18n.with_locale(@resource.locale || I18n.default_locale) do
- mail to: @resource.email
+ mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
end
end
end
diff --git a/app/models/account.rb b/app/models/account.rb
index 6968607a..8ceda7f9 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -12,12 +12,12 @@ class Account < ApplicationRecord
validates :username, presence: true, uniqueness: { scope: :domain, case_sensitive: true }, unless: 'local?'
# Avatar upload
- has_attached_file :avatar, styles: { original: '120x120#' }, convert_options: { all: '-quality 80 -strip' }
+ has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-quality 80 -strip' }
validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
validates_attachment_size :avatar, less_than: 2.megabytes
# Header upload
- has_attached_file :header, styles: { original: '700x335#' }, convert_options: { all: '-quality 80 -strip' }
+ has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-quality 80 -strip' }
validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
validates_attachment_size :header, less_than: 2.megabytes
@@ -120,16 +120,24 @@ class Account < ApplicationRecord
local? ? username : "#{username}@#{domain}"
end
+ def local_username_and_domain
+ "#{username}@#{Rails.configuration.x.local_domain}"
+ end
+
+ def to_webfinger_s
+ "acct:#{local_username_and_domain}"
+ end
+
def subscribed?
!subscription_expires_at.blank?
end
def favourited?(status)
- (status.reblog? ? status.reblog : status).favourites.where(account: self).count.positive?
+ status.proper.favourites.where(account: self).count.positive?
end
def reblogged?(status)
- (status.reblog? ? status.reblog : status).reblogs.where(account: self).count.positive?
+ status.proper.reblogs.where(account: self).count.positive?
end
def keypair
@@ -150,6 +158,22 @@ class Account < ApplicationRecord
save!
end
+ def avatar_original_url
+ avatar.url(:original)
+ end
+
+ def avatar_static_url
+ avatar_content_type == 'image/gif' ? avatar.url(:static) : avatar_original_url
+ end
+
+ def header_original_url
+ header.url(:original)
+ end
+
+ def header_static_url
+ header_content_type == 'image/gif' ? header.url(:static) : header_original_url
+ end
+
def avatar_remote_url=(url)
parsed_url = URI.parse(url)
@@ -203,7 +227,7 @@ class Account < ApplicationRecord
end
def triadic_closures(account, limit = 5)
- sql = <
{ select(:id, :updated_at, :activity_type, :activity_id) }
- scope :browserable, -> { where.not(activity_type: ['FollowRequest']) }
cache_associated :from_account, status: STATUS_INCLUDES, mention: [status: STATUS_INCLUDES], favourite: [:account, status: STATUS_INCLUDES], follow: :account
@@ -28,12 +35,7 @@ class Notification < ApplicationRecord
end
def type
- case activity_type
- when 'Status'
- :reblog
- else
- activity_type.underscore.to_sym
- end
+ @type ||= TYPE_CLASS_MAP.invert[activity_type].to_sym
end
def target_status
@@ -50,6 +52,11 @@ class Notification < ApplicationRecord
end
class << self
+ def browserable(types = [])
+ types.concat([:follow_request])
+ where.not(activity_type: activity_types_from_types(types))
+ end
+
def reload_stale_associations!(cached_items)
account_ids = cached_items.map(&:from_account_id).uniq
accounts = Account.where(id: account_ids).map { |a| [a.id, a] }.to_h
@@ -58,6 +65,12 @@ class Notification < ApplicationRecord
item.from_account = accounts[item.from_account_id]
end
end
+
+ private
+
+ def activity_types_from_types(types)
+ types.map { |type| TYPE_CLASS_MAP[type.to_sym] }.compact
+ end
end
after_initialize :set_from_account
diff --git a/app/models/report.rb b/app/models/report.rb
index 05dc8cff..fd8e46aa 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -3,6 +3,7 @@
class Report < ApplicationRecord
belongs_to :account
belongs_to :target_account, class_name: 'Account'
+ belongs_to :action_taken_by_account, class_name: 'Account'
scope :unresolved, -> { where(action_taken: false) }
scope :resolved, -> { where(action_taken: true) }
diff --git a/app/models/status.rb b/app/models/status.rb
index 81b26fd1..16cd4383 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -62,8 +62,12 @@ class Status < ApplicationRecord
reply? ? :comment : :note
end
+ def proper
+ reblog? ? reblog : self
+ end
+
def content
- reblog? ? reblog.text : text
+ proper.text
end
def target
@@ -71,7 +75,7 @@ class Status < ApplicationRecord
end
def title
- content
+ reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
end
def hidden?
@@ -161,9 +165,9 @@ class Status < ApplicationRecord
return where.not(visibility: [:private, :direct]) if account.nil?
if target_account.blocking?(account) # get rid of blocked peeps
- where('1 = 0')
+ none
elsif account.id == target_account.id # author can see own stuff
- where('1 = 1')
+ all
elsif account.following?(target_account) # followers can see followers-only stuff, but also things they are mentioned in
joins('LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id AND mentions.account_id = ' + account.id.to_s)
.where('statuses.visibility != ? OR mentions.id IS NOT NULL', Status.visibilities[:direct])
@@ -188,7 +192,7 @@ class Status < ApplicationRecord
end
before_validation do
- text.strip!
+ text&.strip!
spoiler_text&.strip!
self.reply = !(in_reply_to_id.nil? && thread.nil?) unless reply
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
index ae7ae446..8aff5ae0 100644
--- a/app/models/stream_entry.rb
+++ b/app/models/stream_entry.rb
@@ -5,25 +5,21 @@ class StreamEntry < ApplicationRecord
belongs_to :account, inverse_of: :stream_entries
belongs_to :activity, polymorphic: true
-
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', inverse_of: :stream_entry
validates :account, :activity, presence: true
- STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, mentions: :account], thread: [:stream_entry, :account]].freeze
+ STATUS_INCLUDES = [:account, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :media_attachments, :tags, mentions: :account], thread: [:stream_entry, :account]].freeze
+ default_scope { where(activity_type: 'Status') }
scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) }
def object_type
- if orphaned?
- :activity
- else
- targeted? ? :activity : activity.object_type
- end
+ orphaned? || targeted? ? :activity : status.object_type
end
def verb
- orphaned? ? :delete : activity.verb
+ orphaned? ? :delete : status.verb
end
def targeted?
@@ -31,15 +27,15 @@ class StreamEntry < ApplicationRecord
end
def target
- orphaned? ? nil : activity.target
+ orphaned? ? nil : status.target
end
def title
- orphaned? ? nil : activity.title
+ orphaned? ? nil : status.title
end
def content
- orphaned? ? nil : activity.content
+ orphaned? ? nil : status.content
end
def threaded?
@@ -47,20 +43,16 @@ class StreamEntry < ApplicationRecord
end
def thread
- orphaned? ? nil : activity.thread
+ orphaned? ? nil : status.thread
end
def mentions
- activity.respond_to?(:mentions) ? activity.mentions.map(&:account) : []
- end
-
- def activity
- !new_record? ? send(activity_type.underscore) || super : super
+ orphaned? ? [] : status.mentions.map(&:account)
end
private
def orphaned?
- activity.nil?
+ status.nil?
end
end
diff --git a/app/models/tag.rb b/app/models/tag.rb
index 15625ca4..6209d7da 100644
--- a/app/models/tag.rb
+++ b/app/models/tag.rb
@@ -17,7 +17,7 @@ class Tag < ApplicationRecord
textsearch = 'to_tsvector(\'simple\', tags.name)'
query = 'to_tsquery(\'simple\', \'\'\' \' || ' + terms + ' || \' \'\'\' || \':*\')'
- sql = < ?', 14.days.ago).find_each do |follower|
- next if FeedManager.instance.filter?(:home, status, follower)
- FeedManager.instance.push(:home, follower, status)
+ status.account.followers.where(domain: nil).joins(:user).where('users.current_sign_in_at > ?', 14.days.ago).select(:id).reorder(nil).find_each do |follower|
+ FeedInsertWorker.perform_async(status.id, follower.id)
end
end
@@ -42,28 +44,29 @@ class FanOutOnWriteService < BaseService
status.mentions.includes(:account).each do |mention|
mentioned_account = mention.account
- next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mentioned_account)
+ next if !mentioned_account.local? || !mentioned_account.following?(status.account) || FeedManager.instance.filter?(:home, status, mention.account_id)
FeedManager.instance.push(:home, mentioned_account, status)
end
end
+ def render_anonymous_payload(status)
+ @payload = InlineRenderer.render(status, nil, 'api/v1/statuses/show')
+ @payload = Oj.dump(event: :update, payload: @payload)
+ end
+
def deliver_to_hashtags(status)
Rails.logger.debug "Delivering status #{status.id} to hashtags"
- payload = FeedManager.instance.inline_render(nil, 'api/v1/statuses/show', status)
-
- status.tags.find_each do |tag|
- FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'update', payload: payload)
- FeedManager.instance.broadcast("hashtag:#{tag.name}:local", event: 'update', payload: payload) if status.account.local?
+ status.tags.pluck(:name).each do |hashtag|
+ Redis.current.publish("timeline:hashtag:#{hashtag}", @payload)
+ Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local?
end
end
def deliver_to_public(status)
Rails.logger.debug "Delivering status #{status.id} to public timeline"
- payload = FeedManager.instance.inline_render(nil, 'api/v1/statuses/show', status)
-
- FeedManager.instance.broadcast(:public, event: 'update', payload: payload)
- FeedManager.instance.broadcast('public:local', event: 'update', payload: payload) if status.account.local?
+ Redis.current.publish('timeline:public', @payload)
+ Redis.current.publish('timeline:public:local', @payload) if status.local?
end
end
diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb
index 5cc96403..e92aada6 100644
--- a/app/services/favourite_service.rb
+++ b/app/services/favourite_service.rb
@@ -22,26 +22,6 @@ class FavouriteService < BaseService
private
def build_xml(favourite)
- description = "#{favourite.account.acct} favourited a status by #{favourite.status.account.acct}"
-
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, favourite.created_at, favourite.id, 'Favourite'
- title xml, description
- content xml, description
-
- author(xml) do
- include_author xml, favourite.account
- end
-
- object_type xml, :activity
- verb xml, :favorite
- in_reply_to xml, TagManager.instance.uri_for(favourite.status), TagManager.instance.url_for(favourite.status)
-
- target(xml) do
- include_target xml, favourite.status
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.favourite_salmon(favourite))
end
end
diff --git a/app/services/fetch_remote_account_service.rb b/app/services/fetch_remote_account_service.rb
index 6a6a696d..50ffc47c 100644
--- a/app/services/fetch_remote_account_service.rb
+++ b/app/services/fetch_remote_account_service.rb
@@ -19,11 +19,16 @@ class FetchRemoteAccountService < BaseService
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
- url_parts = Addressable::URI.parse(url)
- username = xml.at_xpath('//xmlns:author/xmlns:name').try(:content)
- domain = url_parts.host
+ email = xml.at_xpath('//xmlns:author/xmlns:email').try(:content)
+ if email.nil?
+ url_parts = Addressable::URI.parse(url)
+ username = xml.at_xpath('//xmlns:author/xmlns:name').try(:content)
+ domain = url_parts.host
+ else
+ username, domain = email.split('@')
+ end
- return nil if username.nil?
+ return nil if username.nil? || domain.nil?
Rails.logger.debug "Going to webfinger #{username}@#{domain}"
diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb
index b39eafc7..443c9c70 100644
--- a/app/services/follow_remote_account_service.rb
+++ b/app/services/follow_remote_account_service.rb
@@ -20,8 +20,6 @@ class FollowRemoteAccountService < BaseService
Rails.logger.debug "Looking up webfinger for #{uri}"
- account = Account.new(username: username, domain: domain)
-
data = Goldfinger.finger("acct:#{uri}")
raise Goldfinger::Error, 'Missing resource links' if data.link('http://schemas.google.com/g/2010#updates-from').nil? || data.link('salmon').nil? || data.link('http://webfinger.net/rel/profile-page').nil? || data.link('magic-public-key').nil?
@@ -37,6 +35,7 @@ class FollowRemoteAccountService < BaseService
domain_block = DomainBlock.find_by(domain: domain)
+ 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
@@ -45,14 +44,14 @@ class FollowRemoteAccountService < BaseService
account.suspended = true if domain_block && domain_block.suspend?
account.silenced = true if domain_block && domain_block.silence?
- xml = get_feed(account.remote_url)
- hubs = get_hubs(xml)
+ body, xml = get_feed(account.remote_url)
+ hubs = get_hubs(xml)
account.uri = get_account_uri(xml)
account.hub_url = hubs.first.attribute('href').value
- get_profile(xml, account)
account.save!
+ get_profile(body, account)
account
end
@@ -61,7 +60,7 @@ class FollowRemoteAccountService < BaseService
def get_feed(url)
response = http_client.get(Addressable::URI.parse(url))
- Nokogiri::XML(response)
+ [response.to_s, Nokogiri::XML(response)]
end
def get_hubs(xml)
@@ -82,12 +81,8 @@ class FollowRemoteAccountService < BaseService
author_uri.content
end
- def get_profile(xml, account)
- update_remote_profile_service.call(xml.at_xpath('/xmlns:feed'), account)
- end
-
- def update_remote_profile_service
- @update_remote_profile_service ||= UpdateRemoteProfileService.new
+ def get_profile(body, account)
+ RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), false)
end
def http_client
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 17b3b254..844f5282 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -10,7 +10,7 @@ class FollowService < BaseService
target_account = FollowRemoteAccountService.new.call(uri)
raise ActiveRecord::RecordNotFound if target_account.nil? || target_account.id == source_account.id || target_account.suspended?
- raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account)
+ raise Mastodon::NotPermittedError if target_account.blocking?(source_account) || source_account.blocking?(target_account)
if target_account.locked?
request_follow(source_account, target_account)
@@ -55,48 +55,10 @@ class FollowService < BaseService
end
def build_follow_request_xml(follow_request)
- description = "#{follow_request.account.acct} requested to follow #{follow_request.target_account.acct}"
-
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, follow_request.created_at, follow_request.id, 'FollowRequest'
- title xml, description
- content xml, description
-
- author(xml) do
- include_author xml, follow_request.account
- end
-
- object_type xml, :activity
- verb xml, :request_friend
-
- target(xml) do
- include_author xml, follow_request.target_account
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.follow_request_salmon(follow_request))
end
def build_follow_xml(follow)
- description = "#{follow.account.acct} started following #{follow.target_account.acct}"
-
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, follow.created_at, follow.id, 'Follow'
- title xml, description
- content xml, description
-
- author(xml) do
- include_author xml, follow.account
- end
-
- object_type xml, :activity
- verb xml, :follow
-
- target(xml) do
- include_author xml, follow.target_account
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.follow_salmon(follow))
end
end
diff --git a/app/services/mute_service.rb b/app/services/mute_service.rb
index 0050cfc8..1a650ed2 100644
--- a/app/services/mute_service.rb
+++ b/app/services/mute_service.rb
@@ -12,7 +12,7 @@ class MuteService < BaseService
def clear_home_timeline(account, target_account)
home_key = FeedManager.instance.key(:home, account.id)
- target_account.statuses.select('id').find_each do |status|
+ target_account.statuses.select('id').reorder(nil).find_each do |status|
redis.zrem(home_key, status.id)
end
end
diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb
index 942cd9d2..ffeee5fc 100644
--- a/app/services/notify_service.rb
+++ b/app/services/notify_service.rb
@@ -17,7 +17,7 @@ class NotifyService < BaseService
private
def blocked_mention?
- FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient)
+ FeedManager.instance.filter?(:mentions, @notification.mention.status, @recipient.id)
end
def blocked_favourite?
@@ -50,7 +50,7 @@ class NotifyService < BaseService
def create_notification
@notification.save!
return unless @notification.browserable?
- FeedManager.instance.broadcast(@recipient.id, event: 'notification', payload: FeedManager.instance.inline_render(@recipient, 'api/v1/notifications/show', @notification))
+ Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, 'api/v1/notifications/show')))
end
def send_email
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index b8179f7d..221aa42a 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -37,11 +37,11 @@ class PostStatusService < BaseService
def validate_media!(media_ids)
return if media_ids.nil? || !media_ids.is_a?(Enumerable)
- raise Mastodon::ValidationError, 'Cannot attach more than 4 files' if media_ids.size > 4
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if media_ids.size > 4
media = MediaAttachment.where(status_id: nil).where(id: media_ids.take(4).map(&:to_i))
- raise Mastodon::ValidationError, 'Cannot attach a video to a toot that already contains images' if media.size > 1 && media.find(&:video?)
+ raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media.size > 1 && media.find(&:video?)
media
end
diff --git a/app/services/precompute_feed_service.rb b/app/services/precompute_feed_service.rb
index e1ec56e8..07dcb81d 100644
--- a/app/services/precompute_feed_service.rb
+++ b/app/services/precompute_feed_service.rb
@@ -5,9 +5,11 @@ class PrecomputeFeedService < BaseService
# @param [Symbol] type :home or :mentions
# @param [Account] account
def call(_, account)
- Status.as_home_timeline(account).limit(FeedManager::MAX_ITEMS).each do |status|
- next if status.direct_visibility? || FeedManager.instance.filter?(:home, status, account)
- redis.zadd(FeedManager.instance.key(:home, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id)
+ redis.pipelined do
+ Status.as_home_timeline(account).limit(FeedManager::MAX_ITEMS / 4).each do |status|
+ next if status.direct_visibility? || FeedManager.instance.filter?(:home, status, account.id)
+ redis.zadd(FeedManager.instance.key(:home, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id)
+ end
end
end
diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb
index 69911abc..a2def453 100644
--- a/app/services/process_feed_service.rb
+++ b/app/services/process_feed_service.rb
@@ -5,15 +5,14 @@ class ProcessFeedService < BaseService
xml = Nokogiri::XML(body)
xml.encoding = 'utf-8'
- update_author(xml, account)
+ update_author(body, account)
process_entries(xml, account)
end
private
- def update_author(xml, account)
- return if xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS).nil?
- UpdateRemoteProfileService.new.call(xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS), account, true)
+ def update_author(body, account)
+ RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
end
def process_entries(xml, account)
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
index d5f7b4b3..805ca5a2 100644
--- a/app/services/process_interaction_service.rb
+++ b/app/services/process_interaction_service.rb
@@ -24,7 +24,7 @@ class ProcessInteractionService < BaseService
return if account.suspended?
if salmon.verify(envelope, account.keypair)
- update_remote_profile_service.call(xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS), account, true)
+ RemoteProfileUpdateWorker.perform_async(account.id, body.force_encoding('UTF-8'), true)
case verb(xml)
when :follow
@@ -114,7 +114,7 @@ class ProcessInteractionService < BaseService
return if status.nil?
- remove_status_service.call(status) if account.id == status.account_id
+ RemovalWorker.perform_async(status.id) if account.id == status.account_id
end
def favourite!(xml, from_account)
@@ -130,7 +130,7 @@ class ProcessInteractionService < BaseService
end
def add_post!(body, account)
- process_feed_service.call(body, account)
+ ProcessingWorker.perform_async(account.id, body.force_encoding('UTF-8'))
end
def status(xml)
@@ -153,10 +153,6 @@ class ProcessInteractionService < BaseService
@process_feed_service ||= ProcessFeedService.new
end
- def update_remote_profile_service
- @update_remote_profile_service ||= UpdateRemoteProfileService.new
- end
-
def remove_status_service
@remove_status_service ||= RemoveStatusService.new
end
diff --git a/app/services/reject_follow_service.rb b/app/services/reject_follow_service.rb
index 1b03d62e..67500793 100644
--- a/app/services/reject_follow_service.rb
+++ b/app/services/reject_follow_service.rb
@@ -10,31 +10,6 @@ class RejectFollowService < BaseService
private
def build_xml(follow_request)
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, Time.now.utc, follow_request.id, 'FollowRequest'
- title xml, "#{follow_request.target_account.acct} rejects follow request by #{follow_request.account.acct}"
-
- author(xml) do
- include_author xml, follow_request.target_account
- end
-
- object_type xml, :activity
- verb xml, :reject
-
- target(xml) do
- author(xml) do
- include_author xml, follow_request.account
- end
-
- object_type xml, :activity
- verb xml, :request_friend
-
- target(xml) do
- include_author xml, follow_request.target_account
- end
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.reject_follow_request_salmon(follow_request))
end
end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index cf1f432e..50bb7fc9 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -4,6 +4,8 @@ class RemoveStatusService < BaseService
include StreamEntryRenderer
def call(status)
+ @payload = Oj.dump(event: :delete, payload: status.id)
+
remove_from_self(status) if status.account.local?
remove_from_followers(status)
remove_from_mentioned(status)
@@ -25,25 +27,23 @@ class RemoveStatusService < BaseService
end
def remove_from_followers(status)
- status.account.followers.each do |follower|
- next unless follower.local?
+ status.account.followers.where(domain: nil).each do |follower|
unpush(:home, follower, status)
end
end
def remove_from_mentioned(status)
+ return unless status.local?
notified_domains = []
status.mentions.each do |mention|
mentioned_account = mention.account
- if mentioned_account.local?
- unpush(:mentions, mentioned_account, status)
- else
- next if notified_domains.include?(mentioned_account.domain)
- notified_domains << mentioned_account.domain
- send_delete_salmon(mentioned_account, status)
- end
+ next if mentioned_account.local?
+ next if notified_domains.include?(mentioned_account.domain)
+
+ notified_domains << mentioned_account.domain
+ send_delete_salmon(mentioned_account, status)
end
end
@@ -65,17 +65,19 @@ class RemoveStatusService < BaseService
redis.zremrangebyscore(FeedManager.instance.key(type, receiver.id), status.id, status.id)
end
- FeedManager.instance.broadcast(receiver.id, event: 'delete', payload: status.id)
+ Redis.current.publish("timeline:#{receiver.id}", @payload)
end
def remove_from_hashtags(status)
- status.tags.each do |tag|
- FeedManager.instance.broadcast("hashtag:#{tag.name}", event: 'delete', payload: status.id)
+ status.tags.pluck(:name) do |hashtag|
+ Redis.current.publish("timeline:hashtag:#{hashtag}", @payload)
+ Redis.current.publish("timeline:hashtag:#{hashtag}:local", @payload) if status.local?
end
end
def remove_from_public(status)
- FeedManager.instance.broadcast(:public, event: 'delete', payload: status.id)
+ Redis.current.publish('timeline:public', @payload)
+ Redis.current.publish('timeline:public:local', @payload) if status.local?
end
def redis
diff --git a/app/services/suspend_account_service.rb b/app/services/suspend_account_service.rb
index 8528ef62..42ff4dcb 100644
--- a/app/services/suspend_account_service.rb
+++ b/app/services/suspend_account_service.rb
@@ -12,7 +12,7 @@ class SuspendAccountService < BaseService
private
def purge_content
- @account.statuses.find_each do |status|
+ @account.statuses.reorder(nil).find_each do |status|
RemoveStatusService.new.call(status)
end
diff --git a/app/services/unblock_service.rb b/app/services/unblock_service.rb
index c4f789f7..3a3fd2d8 100644
--- a/app/services/unblock_service.rb
+++ b/app/services/unblock_service.rb
@@ -11,22 +11,6 @@ class UnblockService < BaseService
private
def build_xml(block)
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, Time.now.utc, block.id, 'Block'
- title xml, "#{block.account.acct} no longer blocks #{block.target_account.acct}"
-
- author(xml) do
- include_author xml, block.account
- end
-
- object_type xml, :activity
- verb xml, :unblock
-
- target(xml) do
- include_author xml, block.target_account
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.unblock_salmon(block))
end
end
diff --git a/app/services/unfavourite_service.rb b/app/services/unfavourite_service.rb
index 5f0ba425..a32e87bf 100644
--- a/app/services/unfavourite_service.rb
+++ b/app/services/unfavourite_service.rb
@@ -13,26 +13,6 @@ class UnfavouriteService < BaseService
private
def build_xml(favourite)
- description = "#{favourite.account.acct} no longer favourites a status by #{favourite.status.account.acct}"
-
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, Time.now.utc, favourite.id, 'Favourite'
- title xml, description
- content xml, description
-
- author(xml) do
- include_author xml, favourite.account
- end
-
- object_type xml, :activity
- verb xml, :unfavorite
- in_reply_to xml, TagManager.instance.uri_for(favourite.status), TagManager.instance.url_for(favourite.status)
-
- target(xml) do
- include_target xml, favourite.status
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.unfavourite_salmon(favourite))
end
end
diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb
index 3440da36..244c9b52 100644
--- a/app/services/unfollow_service.rb
+++ b/app/services/unfollow_service.rb
@@ -13,25 +13,6 @@ class UnfollowService < BaseService
private
def build_xml(follow)
- description = "#{follow.account.acct} is no longer following #{follow.target_account.acct}"
-
- Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- unique_id xml, Time.now.utc, follow.id, 'Follow'
- title xml, description
- content xml, description
-
- author(xml) do
- include_author xml, follow.account
- end
-
- object_type xml, :activity
- verb xml, :unfollow
-
- target(xml) do
- include_author xml, follow.target_account
- end
- end
- end.to_xml
+ AtomSerializer.render(AtomSerializer.new.unfollow_salmon(follow))
end
end
diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb
index 74baa1cc..8f0d5d4b 100644
--- a/app/services/update_remote_profile_service.rb
+++ b/app/services/update_remote_profile_service.rb
@@ -1,7 +1,12 @@
# frozen_string_literal: true
class UpdateRemoteProfileService < BaseService
- def call(xml, account, resubscribe = false)
+ def call(body, account, resubscribe = false)
+ xml = Nokogiri::XML(body)
+ xml.encoding = 'utf-8'
+
+ xml = xml.at_xpath('/xmlns:feed', xmlns: TagManager::XMLNS) || xml.at_xpath('/xmlns:entry', xmlns: TagManager::XMLNS)
+
return if xml.nil?
author_xml = xml.at_xpath('./xmlns:author', xmlns: TagManager::XMLNS) || xml.at_xpath('./dfrn:owner', dfrn: TagManager::DFRN_XMLNS)
@@ -12,9 +17,9 @@ class UpdateRemoteProfileService < BaseService
account.note = author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).nil?
account.locked = author_xml.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content == 'private'
- unless account.suspended? || DomainBlock.find_by(domain: account.domain)&.reject_media?
- account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'].blank?
- account.header_remote_url = author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="header"]', xmlns: TagManager::XMLNS)['href'].blank?
+ if !account.suspended? && !DomainBlock.find_by(domain: account.domain)&.reject_media?
+ account.avatar_remote_url = link_href_from_xml(author_xml, 'avatar') if link_has_href?(author_xml, 'avatar')
+ account.header_remote_url = link_href_from_xml(author_xml, 'header') if link_has_href?(author_xml, 'header')
end
end
@@ -25,4 +30,14 @@ class UpdateRemoteProfileService < BaseService
SubscribeService.new.call(account) if resubscribe && (account.hub_url != old_hub_url)
end
+
+ private
+
+ def link_href_from_xml(xml, type)
+ xml.at_xpath('./xmlns:link[@rel="' + type + '"]', xmlns: TagManager::XMLNS)['href']
+ end
+
+ def link_has_href?(xml, type)
+ !(xml.at_xpath('./xmlns:link[@rel="' + type + '"]', xmlns: TagManager::XMLNS).nil? || xml.at_xpath('./xmlns:link[@rel="' + type + '"]', xmlns: TagManager::XMLNS)['href'].blank?)
+ end
end
diff --git a/app/lib/email_validator.rb b/app/validators/email_validator.rb
similarity index 50%
rename from app/lib/email_validator.rb
rename to app/validators/email_validator.rb
index 856b8b1f..06e9375f 100644
--- a/app/lib/email_validator.rb
+++ b/app/validators/email_validator.rb
@@ -2,17 +2,30 @@
class EmailValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- return if Rails.configuration.x.email_domains_blacklist.empty?
-
record.errors.add(attribute, I18n.t('users.invalid_email')) if blocked_email?(value)
end
private
def blocked_email?(value)
+ on_blacklist?(value) || not_on_whitelist?(value)
+ end
+
+ def on_blacklist?(value)
+ return false if Rails.configuration.x.email_domains_blacklist.blank?
+
domains = Rails.configuration.x.email_domains_blacklist.gsub('.', '\.')
regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
value =~ regexp
end
+
+ def not_on_whitelist?(value)
+ return false if Rails.configuration.x.email_domains_whitelist.blank?
+
+ domains = Rails.configuration.x.email_domains_whitelist.gsub('.', '\.')
+ regexp = Regexp.new("@(.+\\.)?(#{domains})", true)
+
+ value !~ regexp
+ end
end
diff --git a/app/lib/status_length_validator.rb b/app/validators/status_length_validator.rb
similarity index 100%
rename from app/lib/status_length_validator.rb
rename to app/validators/status_length_validator.rb
diff --git a/app/lib/url_validator.rb b/app/validators/url_validator.rb
similarity index 100%
rename from app/lib/url_validator.rb
rename to app/validators/url_validator.rb
diff --git a/app/views/about/_registration.html.haml b/app/views/about/_registration.html.haml
new file mode 100644
index 00000000..c7a9a488
--- /dev/null
+++ b/app/views/about/_registration.html.haml
@@ -0,0 +1,30 @@
+= simple_form_for(new_user, url: user_registration_path) do |f|
+ = f.simple_fields_for :account do |account_fields|
+ = account_fields.input :username,
+ autofocus: true,
+ placeholder: t('simple_form.labels.defaults.username'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
+
+ = f.input :email,
+ placeholder: t('simple_form.labels.defaults.email'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
+ = f.input :password,
+ autocomplete: "off",
+ placeholder: t('simple_form.labels.defaults.password'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
+ = f.input :password_confirmation,
+ autocomplete: "off",
+ placeholder: t('simple_form.labels.defaults.confirm_password'),
+ required: true,
+ input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
+
+ .actions
+ = f.button :button, t('about.get_started'), type: :submit
+
+ .info
+ = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
+ ·
+ = link_to t('about.about_this'), about_more_path
diff --git a/app/views/about/more.html.haml b/app/views/about/more.html.haml
index 2de3bf98..8c12f57c 100644
--- a/app/views/about/more.html.haml
+++ b/app/views/about/more.html.haml
@@ -7,42 +7,42 @@
.panel
%h2= Rails.configuration.x.local_domain
- - unless @description.blank?
- %p= @description.html_safe
+ - unless @instance_presenter.site_description.blank?
+ %p= @instance_presenter.site_description.html_safe
.information-board
.section
%span= t 'about.user_count_before'
- %strong= number_with_delimiter @user_count
+ %strong= number_with_delimiter @instance_presenter.user_count
%span= t 'about.user_count_after'
.section
%span= t 'about.status_count_before'
- %strong= number_with_delimiter @status_count
+ %strong= number_with_delimiter @instance_presenter.status_count
%span= t 'about.status_count_after'
.section
%span= t 'about.domain_count_before'
- %strong= number_with_delimiter @domain_count
+ %strong= number_with_delimiter @instance_presenter.domain_count
%span= t 'about.domain_count_after'
- - unless @extended_description.blank?
- .panel= @extended_description.html_safe
+ - unless @instance_presenter.site_extended_description.blank?
+ .panel= @instance_presenter.site_extended_description.html_safe
.sidebar
.panel
.panel-header= t 'about.contact'
.panel-body
- - if @contact_account
+ - if @instance_presenter.contact_account
.owner
- .avatar= image_tag @contact_account.avatar.url
+ .avatar= image_tag @instance_presenter.contact_account.avatar.url
.name
- = link_to TagManager.instance.url_for(@contact_account) do
- %span.display_name.emojify= display_name(@contact_account)
- %span.username= "@#{@contact_account.acct}"
+ = 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 @contact_email.blank?
+ - unless @instance_presenter.contact_email.blank?
.contact-email
= t 'about.business_email'
- %strong= @contact_email
+ %strong= @instance_presenter.contact_email
.panel
.panel-header= t 'about.links'
.panel-list
diff --git a/app/views/about/index.html.haml b/app/views/about/show.html.haml
similarity index 52%
rename from app/views/about/index.html.haml
rename to app/views/about/show.html.haml
index fdfb2b91..0c066962 100644
--- a/app/views/about/index.html.haml
+++ b/app/views/about/show.html.haml
@@ -5,10 +5,10 @@
= Rails.configuration.x.local_domain
- content_for :header_tags do
- %meta{ property: 'og:site_name', content: 'Mastodon' }/
+ %meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'website' }/
%meta{ property: 'og:title', content: Rails.configuration.x.local_domain }/
- %meta{ property: 'og:description', content: @description.blank? ? "Mastodon is a free, open-source social network server. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly" : strip_tags(@description) }/
+ %meta{ property: 'og:description', content: strip_tags(@instance_presenter.site_description.blank? ? t('about.about_mastodon') : @instance_presenter.site_description) }/
%meta{ property: 'og:image', content: asset_url('mastodon_small.jpg') }/
%meta{ property: 'og:image:width', content: '400' }/
%meta{ property: 'og:image:height', content: '400' }/
@@ -24,21 +24,20 @@
.screenshot-with-signup
.mascot= image_tag 'fluffy-elephant-friend.png'
- = simple_form_for(@user, url: user_registration_path) do |f|
- = f.simple_fields_for :account do |ff|
- = ff.input :username, autofocus: true, placeholder: t('simple_form.labels.defaults.username'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username') }
-
- = f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }
- = f.input :password, autocomplete: "off", placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password') }
- = f.input :password_confirmation, autocomplete: "off", placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password') }
-
- .actions
- = f.button :button, t('about.get_started'), type: :submit
-
- .info
- = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
- ·
- = link_to t('about.about_this'), about_more_path
+ - if @instance_presenter.open_registrations
+ = render 'registration'
+ - else
+ .closed-registrations-message
+ - if @instance_presenter.closed_registrations_message.blank?
+ %p= t('about.closed_registrations')
+ - else
+ = @instance_presenter.closed_registrations_message.html_safe
+ .info
+ = link_to t('auth.login'), new_user_session_path, class: 'webapp-btn'
+ ·
+ = link_to t('about.other_instances'), 'https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md'
+ ·
+ = link_to t('about.about_this'), about_more_path
%h3= t('about.features_headline')
@@ -72,16 +71,16 @@
= fa_icon('li check-square')
= t 'about.features.api'
- - unless @description.blank?
+ - unless @instance_presenter.site_description.blank?
%h3= t('about.description_headline', domain: Rails.configuration.x.local_domain)
- %p= @description.html_safe
+ %p= @instance_presenter.site_description.html_safe
.actions
.info
= link_to t('about.terms'), terms_path
·
- = link_to t('about.apps'), 'https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/Apps.md'
+ = link_to t('about.apps'), 'https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md'
·
= link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon'
·
- = link_to t('about.other_instances'), 'https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md'
+ = link_to t('about.other_instances'), 'https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md'
diff --git a/app/views/about/terms.en.html.haml b/app/views/about/terms.en.html.haml
index 9fb31805..e1766ca1 100644
--- a/app/views/about/terms.en.html.haml
+++ b/app/views/about/terms.en.html.haml
@@ -51,7 +51,7 @@
%h3#coppa Children's Online Privacy Protection Act Compliance
%p
- Our site, products and services are all directed to people who are at least 13 years old or older. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA
+ Our site, products and services are all directed to people who are at least 13 years old. If this server is in the USA, and you are under the age of 13, per the requirements of COPPA
= surround '(', '),' do
= link_to 'Children\'s Online Privacy Protection Act', 'https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act'
do not use this site.
diff --git a/app/views/about/terms.no.html.haml b/app/views/about/terms.no.html.haml
new file mode 100644
index 00000000..5506cd86
--- /dev/null
+++ b/app/views/about/terms.no.html.haml
@@ -0,0 +1,76 @@
+- content_for :page_title do
+ #{Rails.configuration.x.local_domain} Personvern og villkår for bruk av nettstedet
+
+.wrapper
+ %h2 Personvernserklæring
+
+ %h3#collect Hvilke opplysninger samler vi?
+
+ %p Vi samler opplysninger fra deg når du registrerer deg på nettstedet vårt, og vi samler data når du deltar på forumet ved å lese, skrive og evaluere innholdet som deles her.
+
+ %p Når du registrerer deg på nettstedet vårt, kan du bli bedt om å oppgi navnet og e-postadressen din. Imidlertid kan du besøke nettstedet vårt uten å registrere deg. E-postadressen din vil bli bekreftet med en e-post som inneholder en unik lenke. Hvis siden den lenker til, blir besøkt, vet vi at du har kontroll over e-postadressen.
+
+ %p Når du registrerer deg og skriver innlegg, registrerer vi IP-adressen som innlegget stammer fra. Vi kan også oppbevare logger som inkluderer IP-adressen til alle forespørslene sendt til tjeneren vår.
+
+ %h3#use Hva bruker vi opplysningene dine til?
+
+ %p Alle opplysningene vi samler fra deg, kan bli brukt på en av følgende måter:
+
+ %ul
+ %li For å gjøre opplevelsen din mer personlig. Opplysningene dine hjelper oss å svare bedre på dine individuelle behov.
+ %li For å forbedre nettstedet vårt. Vi jobber konstant for å forbedre nettstedets tilbud basert på opplysningene og tilbakemeldingene vi mottar fra deg.
+ %li For å forbedre vår kundeservice. Dine opplysninger hjelper oss å svare mer effektivt på dine forespørsler sendt til kundeservice eller behov om støtte.
+ %li For å sende periodiske e-poster. E-postadressen du oppgir, kan bli brukt til å sende deg informasjon, påminnelser som du ber om ved endringer av emner eller ved svar til brukernavnet ditt, til henvendelser, og/eller andre forspørsler eller andre spørsmål.
+
+ %h3#protect Hvordan sikrer vi opplysningene?
+
+ %p Vi gjennomfører flere sikkerhetstiltak for å holde personopplysningene dine sikre når du skriver inn, lagrer eller henter dem.
+
+ %h3#data-retention Hva er retningslinjene deres for lagring av data?
+
+ %p Vi vil forsøke i god tro å:
+
+ %ul
+ %li Ikke oppbevare tjener-logger som inneholder IP-adressen til alle forespørslene til denne tjeneren i lenger enn i 90 dager.
+ %li Ikke oppbevare IP-adressene forbundet med registrerte brukere og deres innlegg lenger enn i 5 år.
+
+ %h3#cookies Bruker vi informasjonskapsler?
+
+ %p Ja. Informasjonskapsler er små filer som et nettsted eller dets tjenesteleverandør overfører til harddisken på datamaskinen din gjennom nettleseren din (dersom du tillater det). Disse informasjonskapslene gjør det mulig for nettstedet å gjenkjenne nettleseren din og, dersom du har en konto, knytte nettleseren til den.
+
+ %p Vi bruker informasjonskapsler for å forstå og lagre preferansene dine for fremtidige besøk og for å samle aggregatdata om trafikk på og samhandling med nettstedet slik at vi kan tilby bedre opplevelser og verktøy på nettstedet i fremtiden. Vi kan inngå avtaler med tredjeparts tjenesteleverandører for å bistå oss i å forstå besøkerne våres bedre. Disse tjenesteleverandørene har ikke lov til å bruke opplysningene samlet på våres vegne unntatt til å hjelpe oss å gjennomføre og forbedre anliggendet vårt.
+
+ %h3#disclose Gir vi noen opplysninger videre til andre parter?
+
+ %p Vi verken selger, handler med eller overfører på noen annen måte til andre parter dine identifiserbare personopplysninger. Dette inkluderer ikke tredjeparter som har vår tillit og bistår oss i å drive nettstedet, utføre våre anliggender eller yter tjenester til deg, så lenge disse partene samtykker til å behandle disse opplysningene fortrolig. Vi kan også frigi opplysningene dine dersom vi tror at å frigi dem er hensiktsmessig for å overholde loven, håndheve nettstedet retningslinjer eller beskytte våre og andres rettigheter. Imidlertid kan opplysninger som ikke er personlig identifiserbare, bli delt med andre parter for markedsføring, reklame eller annet bruk.
+
+ %h3#third-party Tredjeparts lenker
+
+ %p Av og til, etter skjønn, kan vil inkludere eller tilby tredjeparts produkter eller tjenester på nettstedet vårt. Disse tredjeparts nettstedene har separate og selvstendige personvernerklæringer. Vi bærer derfor intet ansvar eller forpliktelser for innholdet eller aktivitetene til disse nettstedene det lenkes til. Ikke mindre prøver vi å bevare vår eget nettsteds integritet og ønsker enhver tilbakemelding om disse nettstedene velkomne.
+
+ %h3#coppa Overensstemmelse med Children's Online Privacy Protection Act
+
+ %p
+ Nettstedet er rettet mot folk som er minst 13 år gamle. Dersom denne tjeneren er i USA, og du er under 13 år i henhold til kravene i COPPA
+ = surround '(', '),' do
+ = link_to 'Children\'s Online Privacy Protection Act', 'https://en.wikipedia.org/wiki/Children%27s_Online_Privacy_Protection_Act'
+ ikke bruk dette nettstedet.
+
+ %h3#online Personvernerklæring bare for nettet
+
+ %p Denne nett-personvernerklæringen gjelder bare for informasjon samlet gjennom nettstedet vårt og ikke for opplysninger samlet når en er frakoblet.
+
+ %h3#consent Ditt samtykke
+
+ %p Ved å bruke dette nettstedet samtykker du til nettstedets personvernerklæring.
+
+ %h3#changes Endringer i vår personvernerklæring
+
+ %p Dersom vi beslutter å endre personvernerklæringen vår, vil vi publisere disse endringene på denne siden.
+
+ %p Dette dokumentet er lisensiert under CC-BY-SA. De ble sist oppdatert 12. april 2017.
+
+ %p
+ Dokumentet er en adoptert og endret versjon fra
+ = succeed '.' do
+ = link_to 'Discourse privacy policy', 'https://github.com/discourse/discourse'
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index 0d43fba3..beee96cd 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -13,7 +13,7 @@
%h1.name
%span.p-name.emojify= display_name(@account)
%small
- %span.p-nickname= "@#{@account.username}"
+ %span= "@#{@account.username}"
= fa_icon('lock') if @account.locked?
.details
.bio
diff --git a/app/views/accounts/followers.html.haml b/app/views/accounts/followers.html.haml
index 49349102..fa5071f3 100644
--- a/app/views/accounts/followers.html.haml
+++ b/app/views/accounts/followers.html.haml
@@ -9,4 +9,4 @@
- else
= render partial: 'grid_card', collection: @followers, as: :account, cached: true
-= will_paginate @followers, pagination_options
+= paginate @followers
diff --git a/app/views/accounts/following.html.haml b/app/views/accounts/following.html.haml
index 370cd6c4..987dcba1 100644
--- a/app/views/accounts/following.html.haml
+++ b/app/views/accounts/following.html.haml
@@ -9,4 +9,4 @@
- else
= render partial: 'grid_card', collection: @following, as: :account, cached: true
-= will_paginate @following, pagination_options
+= paginate @following
diff --git a/app/views/accounts/show.atom.ruby b/app/views/accounts/show.atom.ruby
deleted file mode 100644
index e1502117..00000000
--- a/app/views/accounts/show.atom.ruby
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-Nokogiri::XML::Builder.new do |xml|
- feed(xml) do
- simple_id xml, account_url(@account, format: 'atom')
- title xml, @account.display_name
- subtitle xml, @account.note
- updated_at xml, stream_updated_at
- logo xml, full_asset_url(@account.avatar.url(:original))
-
- author(xml) do
- include_author xml, @account
- end
-
- link_alternate xml, TagManager.instance.url_for(@account)
- link_self xml, account_url(@account, format: 'atom')
- link_next xml, account_url(@account, format: 'atom', max_id: @entries.last.id) if @entries.size == 20
- link_hub xml, api_push_url
- link_salmon xml, api_salmon_url(@account.id)
-
- @entries.each do |stream_entry|
- entry(xml, false) do
- include_entry xml, stream_entry
- end
- end
- end
-end.to_xml
diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml
index 3b8c67b4..3b0d69dc 100644
--- a/app/views/accounts/show.html.haml
+++ b/app/views/accounts/show.html.haml
@@ -5,7 +5,7 @@
%link{ rel: 'salmon', href: api_salmon_url(@account.id) }/
%link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/
- %meta{ property: 'og:site_name', content: 'Mastodon' }/
+ %meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'profile' }/
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
%meta{ property: 'og:description', content: @account.note }/
@@ -31,4 +31,4 @@
.pagination
- if @statuses.size == 20
- = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), short_account_url(@account, max_id: @statuses.last.id), class: 'next_page', rel: 'next'
+ = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), short_account_url(@account, max_id: @statuses.last.id), class: 'next', rel: 'next'
diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml
index f8ed4ef9..4d636601 100644
--- a/app/views/admin/accounts/index.html.haml
+++ b/app/views/admin/accounts/index.html.haml
@@ -46,4 +46,4 @@
= table_link_to 'globe', 'Public', TagManager.instance.url_for(account)
= table_link_to 'pencil', 'Edit', admin_account_path(account.id)
-= will_paginate @accounts, pagination_options
+= paginate @accounts
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index ba1c3bae..22901aed 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -62,11 +62,11 @@
= number_to_human_size @account.media_attachments.sum('file_file_size')
- if @account.silenced?
- = link_to 'Undo silence', unsilence_admin_account_path(@account.id), method: :post, class: 'button'
+ = link_to 'Undo silence', admin_account_silence_path(@account.id), method: :delete, class: 'button'
- else
- = link_to 'Silence', silence_admin_account_path(@account.id), method: :post, class: 'button'
+ = link_to 'Silence', admin_account_silence_path(@account.id), method: :post, class: 'button'
- if @account.suspended?
- = link_to 'Undo suspension', unsuspend_admin_account_path(@account.id), method: :post, class: 'button'
+ = link_to 'Undo suspension', admin_account_suspension_path(@account.id), method: :delete, class: 'button'
- else
- = link_to 'Perform full suspension', suspend_admin_account_path(@account.id), method: :post, data: { confirm: 'Are you sure?' }, class: 'button'
+ = link_to 'Perform full suspension', admin_account_suspension_path(@account.id), method: :post, data: { confirm: 'Are you sure?' }, class: 'button'
diff --git a/app/views/admin/domain_blocks/index.html.haml b/app/views/admin/domain_blocks/index.html.haml
index dbaeb471..fe6ff683 100644
--- a/app/views/admin/domain_blocks/index.html.haml
+++ b/app/views/admin/domain_blocks/index.html.haml
@@ -13,4 +13,5 @@
%samp= block.domain
%td= block.severity
-= will_paginate @blocks, pagination_options
+= paginate @blocks
+= link_to 'Add new', new_admin_domain_block_path, class: 'button'
diff --git a/app/views/admin/domain_blocks/new.html.haml b/app/views/admin/domain_blocks/new.html.haml
new file mode 100644
index 00000000..fbd39d6c
--- /dev/null
+++ b/app/views/admin/domain_blocks/new.html.haml
@@ -0,0 +1,18 @@
+- content_for :page_title do
+ New domain block
+
+= simple_form_for @domain_block, url: admin_domain_blocks_path do |f|
+ = render 'shared/error_messages', object: @domain_block
+
+ %p.hint The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
+
+ = f.input :domain, placeholder: 'Domain'
+ = f.input :severity, collection: DomainBlock.severities.keys, wrapper: :with_label, include_blank: false
+
+ %p.hint
+ %strong Silence
+ will make the account's posts invisible to anyone who isn't following them.
+ %strong Suspend
+ will remove all of the account's content, media, and profile data.
+ .actions
+ = f.button :button, 'Create block', type: :submit
diff --git a/app/views/admin/pubsubhubbub/index.html.haml b/app/views/admin/pubsubhubbub/index.html.haml
index cb11a502..dcbb11c1 100644
--- a/app/views/admin/pubsubhubbub/index.html.haml
+++ b/app/views/admin/pubsubhubbub/index.html.haml
@@ -21,9 +21,9 @@
%i.fa.fa-check
%td= distance_of_time_in_words(Time.now, subscription.expires_at)
%td
- - if subscription.last_successful_delivery_at.nil?
- %i.fa.fa-times
- - else
+ - if subscription.last_successful_delivery_at?
= l subscription.last_successful_delivery_at
+ - else
+ %i.fa.fa-times
-= will_paginate @subscriptions, pagination_options
+= paginate @subscriptions
diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml
index 8a5414ce..68dc0701 100644
--- a/app/views/admin/reports/index.html.haml
+++ b/app/views/admin/reports/index.html.haml
@@ -1,27 +1,32 @@
- content_for :page_title do
- Reports
+ = t('reports.reports')
.filters
.filter-subset
- %strong Status
+ %strong= t('reports.status')
%ul
- %li= filter_link_to 'Unresolved', action_taken: nil
- %li= filter_link_to 'Resolved', action_taken: '1'
+ %li= filter_link_to t('reports.unresolved'), action_taken: nil
+ %li= filter_link_to t('reports.resolved'), action_taken: '1'
-%table.table
- %thead
- %tr
- %th ID
- %th Target
- %th Reported by
- %th Comment
- %th
- %tbody
- - @reports.each do |report|
+= form_tag do
+
+ %table.table
+ %thead
%tr
- %td= "##{report.id}"
- %td= link_to report.target_account.acct, admin_account_path(report.target_account.id)
- %td= link_to report.account.acct, admin_account_path(report.account.id)
- %td= truncate(report.comment, length: 30, separator: ' ')
- %td= table_link_to 'circle', 'View', admin_report_path(report)
-= will_paginate @reports, pagination_options
+ %th
+ %th= t('reports.id')
+ %th= t('reports.target')
+ %th= t('reports.reported_by')
+ %th= t('reports.comment.label')
+ %th
+ %tbody
+ - @reports.each do |report|
+ %tr
+ %td= check_box_tag 'select', report.id
+ %td= "##{report.id}"
+ %td= link_to report.target_account.acct, admin_account_path(report.target_account.id)
+ %td= link_to report.account.acct, admin_account_path(report.account.id)
+ %td= truncate(report.comment, length: 30, separator: ' ')
+ %td= table_link_to 'circle', t('reports.view'), admin_report_path(report)
+
+= paginate @reports
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 74cac016..ecbb9848 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -1,20 +1,18 @@
- content_for :page_title do
- = "Report ##{@report.id}"
+ = t('reports.report', id: @report.id)
.report-accounts
.report-accounts__item
- %strong Reported account:
+ %strong= t('reports.reported_account')
= render partial: 'authorize_follow/card', locals: { account: @report.target_account }
.report-accounts__item
- %strong Reported by:
+ %strong= t('reports.reported_by')
= render partial: 'authorize_follow/card', locals: { account: @report.account }
%p
- %strong Comment:
- - if @report.comment.blank?
- None
- - else
- = @report.comment
+ %strong= t('reports.comment.label')
+ \:
+ = @report.comment.presence || t('reports.comment.none')
- unless @statuses.empty?
%hr/
@@ -24,15 +22,21 @@
.activity-stream.activity-stream-headless
.entry= render partial: 'stream_entries/simple_status', locals: { status: status }
.report-status__actions
- = link_to remove_admin_report_path(@report, status_id: status.id), method: :post, class: 'icon-button', style: 'font-size: 24px; width: 24px; height: 24px', title: 'Delete' do
+ = link_to remove_admin_report_path(@report, status_id: status.id), method: :post, class: 'icon-button', style: 'font-size: 24px; width: 24px; height: 24px', title: t('reports.delete') do
= fa_icon 'trash'
-- unless @report.action_taken?
+- if !@report.action_taken?
%hr/
%div{ style: 'overflow: hidden' }
%div{ style: 'float: right' }
- = link_to 'Silence account', silence_admin_report_path(@report), method: :post, class: 'button'
- = link_to 'Suspend account', suspend_admin_report_path(@report), method: :post, class: 'button'
+ = link_to t('reports.silence_account'), silence_admin_report_path(@report), method: :post, class: 'button'
+ = link_to t('reports.suspend_account'), suspend_admin_report_path(@report), method: :post, class: 'button'
%div{ style: 'float: left' }
- = link_to 'Mark as resolved', resolve_admin_report_path(@report), method: :post, class: 'button'
+ = link_to t('reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
+- elsif !@report.action_taken_by_account.nil?
+ %hr/
+
+ %p
+ %strong Action taken by:
+ = @report.action_taken_by_account.acct
diff --git a/app/views/admin/settings/index.html.haml b/app/views/admin/settings/index.html.haml
index 1429dbd9..b00e75a1 100644
--- a/app/views/admin/settings/index.html.haml
+++ b/app/views/admin/settings/index.html.haml
@@ -1,40 +1,40 @@
- content_for :page_title do
- Site Settings
+ = t('admin.settings.title')
%table.table
%colgroup
%col{ width: '35%' }/
%thead
%tr
- %th Setting
- %th Click to edit
+ %th= t('admin.settings.setting')
+ %th= t('admin.settings.click_to_edit')
%tbody
%tr
%td{ rowspan: 2 }
- %strong Contact information
- %td= best_in_place @settings['site_contact_username'], :value, url: admin_setting_path(@settings['site_contact_username']), place_holder: 'Enter a username'
+ %strong= t('admin.settings.contact_information.label')
+ %td= best_in_place @settings['site_contact_username'], :value, url: admin_setting_path(@settings['site_contact_username']), place_holder: t('admin.settings.contact_information.username')
%tr
- %td= best_in_place @settings['site_contact_email'], :value, url: admin_setting_path(@settings['site_contact_email']), place_holder: 'Enter a public e-mail address'
+ %td= best_in_place @settings['site_contact_email'], :value, url: admin_setting_path(@settings['site_contact_email']), place_holder: t('admin.settings.contact_information.email')
%tr
%td
- %strong Site title
+ %strong= t('admin.settings.site_title')
%td= best_in_place @settings['site_title'], :value, url: admin_setting_path(@settings['site_title'])
%tr
%td
- %strong Site description
- %br/
- Displayed as a paragraph on the frontpage and used as a meta tag.
- %br/
- You can use HTML tags, in particular
- %code= ''
- and
- %code= ''
+ %strong= t('admin.settings.site_description.title')
+ %p= t('admin.settings.site_description.desc_html')
%td= best_in_place @settings['site_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_description'])
%tr
%td
- %strong Extended site description
- %br/
- Displayed on extended information page
- %br/
- You can use HTML tags
+ %strong= t('admin.settings.site_description_extended.title')
+ %p= t('admin.settings.site_description_extended.desc_html')
%td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description'])
+ %tr
+ %td
+ %strong= t('admin.settings.registrations.open.title')
+ %td= best_in_place @settings['open_registrations'], :value, as: :checkbox, collection: { false: t('admin.settings.registrations.open.disabled'), true: t('admin.settings.registrations.open.enabled')}, url: admin_setting_path(@settings['open_registrations'])
+ %tr
+ %td
+ %strong= t('admin.settings.registrations.closed_message.title')
+ %p= t('admin.settings.registrations.closed_message.desc_html')
+ %td= best_in_place @settings['closed_registrations_message'], :value, as: :textarea, url: admin_setting_path(@settings['closed_registrations_message'])
diff --git a/app/views/api/v1/accounts/show.rabl b/app/views/api/v1/accounts/show.rabl
index 32df0457..8826aa22 100644
--- a/app/views/api/v1/accounts/show.rabl
+++ b/app/views/api/v1/accounts/show.rabl
@@ -4,8 +4,9 @@ attributes :id, :username, :acct, :display_name, :locked, :created_at
node(:note) { |account| Formatter.instance.simplified_format(account) }
node(:url) { |account| TagManager.instance.url_for(account) }
-node(:avatar) { |account| full_asset_url(account.avatar.url(:original)) }
-node(:header) { |account| full_asset_url(account.header.url(:original)) }
-node(:followers_count) { |account| defined?(@followers_counts_map) ? (@followers_counts_map[account.id] || 0) : account.followers_count }
-node(:following_count) { |account| defined?(@following_counts_map) ? (@following_counts_map[account.id] || 0) : account.following_count }
-node(:statuses_count) { |account| defined?(@statuses_counts_map) ? (@statuses_counts_map[account.id] || 0) : account.statuses_count }
+node(:avatar) { |account| full_asset_url(account.avatar_original_url) }
+node(:avatar_static) { |account| full_asset_url(account.avatar_static_url) }
+node(:header) { |account| full_asset_url(account.header_original_url) }
+node(:header_static) { |account| full_asset_url(account.header_static_url) }
+
+attributes :followers_count, :following_count, :statuses_count
diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml
index 8bf99855..1deff82b 100644
--- a/app/views/auth/sessions/two_factor.html.haml
+++ b/app/views/auth/sessions/two_factor.html.haml
@@ -2,7 +2,7 @@
= t('auth.login')
= simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
- = f.input :otp_attempt, 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'
.actions
= f.button :button, t('auth.login'), type: :submit
diff --git a/app/views/authorize_follow/_card.html.haml b/app/views/authorize_follow/_card.html.haml
index eef0bec0..16af9220 100644
--- a/app/views/authorize_follow/_card.html.haml
+++ b/app/views/authorize_follow/_card.html.haml
@@ -7,5 +7,5 @@
%strong.emojify= display_name(account)
%span= "@#{account.acct}"
- - unless account.note.blank?
+ - if account.note?
.account__header__content.emojify= Formatter.instance.simplified_format(account)
diff --git a/app/views/errors/404.html.haml b/app/views/errors/404.html.haml
index ba1d5f72..5ffba3ca 100644
--- a/app/views/errors/404.html.haml
+++ b/app/views/errors/404.html.haml
@@ -1,5 +1,5 @@
- content_for :page_title do
- The page you were looking for doesn't exist
+ = t('errors.404')
- content_for :content do
- The page you were looking for doesn't exist
+ = t('errors.404')
\ No newline at end of file
diff --git a/app/views/errors/410.html.haml b/app/views/errors/410.html.haml
index 07cf3742..627c6558 100644
--- a/app/views/errors/410.html.haml
+++ b/app/views/errors/410.html.haml
@@ -1,5 +1,5 @@
- content_for :page_title do
- The page you were looking for doesn't exist anymore
+ = t('errors.410')
- content_for :content do
- The page you were looking for doesn't exist anymore
+ = t('errors.410')
diff --git a/app/views/errors/422.html.haml b/app/views/errors/422.html.haml
index e369cded..e710fecb 100644
--- a/app/views/errors/422.html.haml
+++ b/app/views/errors/422.html.haml
@@ -1,5 +1,5 @@
- content_for :page_title do
- Security verification failed
+ = t('errors.422.title')
- content_for :content do
- Security verification failed. Are you blocking cookies?
+ = t('errors.422.content')
diff --git a/app/views/home/initial_state.json.rabl b/app/views/home/initial_state.json.rabl
index bf559039..ce0cd82a 100644
--- a/app/views/home/initial_state.json.rabl
+++ b/app/views/home/initial_state.json.rabl
@@ -7,6 +7,7 @@ node(:meta) do
domain: Rails.configuration.x.local_domain,
me: current_account.id,
admin: @admin.try(:id),
+ boost_modal: current_account.user.setting_boost_modal,
}
end
diff --git a/app/views/kaminari/_next_page.html.haml b/app/views/kaminari/_next_page.html.haml
new file mode 100644
index 00000000..30a3643d
--- /dev/null
+++ b/app/views/kaminari/_next_page.html.haml
@@ -0,0 +1,9 @@
+-# Link to the "Next" page
+-# available local variables
+-# url: url to the next page
+-# current_page: a page object for the currently displayed page
+-# total_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.next
+ = link_to_unless current_page.last?, safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), url, rel: 'next', remote: remote
diff --git a/app/views/kaminari/_paginator.html.haml b/app/views/kaminari/_paginator.html.haml
new file mode 100644
index 00000000..b1da236d
--- /dev/null
+++ b/app/views/kaminari/_paginator.html.haml
@@ -0,0 +1,16 @@
+-# The container tag
+-# available local variables
+-# current_page: a page object for the currently displayed page
+-# total_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+-# paginator: the paginator that renders the pagination tags inside
+= paginator.render do
+ %nav.pagination
+ = prev_page_tag unless current_page.first?
+ - each_page do |page|
+ - if page.display_tag?
+ = page_tag page
+ - elsif !page.was_truncated?
+ = gap_tag
+ = next_page_tag unless current_page.last?
diff --git a/app/views/kaminari/_prev_page.html.haml b/app/views/kaminari/_prev_page.html.haml
new file mode 100644
index 00000000..1089e356
--- /dev/null
+++ b/app/views/kaminari/_prev_page.html.haml
@@ -0,0 +1,9 @@
+-# Link to the "Previous" page
+-# available local variables
+-# url: url to the previous page
+-# current_page: a page object for the currently displayed page
+-# total_pages: total number of pages
+-# per_page: number of items to fetch per page
+-# remote: data-remote
+%span.prev
+ = link_to_unless current_page.first?, safe_join([fa_icon('chevron-left'), t('pagination.prev')], ' '), url, rel: 'prev', remote: remote
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 7eae6982..e8253bd6 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -11,9 +11,11 @@
%meta{:name => "theme-color", :content => "#282c37"}/
%meta{:name => "apple-mobile-web-app-capable", :content => "yes"}/
- %title
- = "#{yield(:page_title)} - " if content_for?(:page_title)
- = Setting.site_title
+ %title<
+ - if content_for?(:page_title)
+ = yield(:page_title)
+ = ' - '
+ = site_title
= stylesheet_link_tag 'application', media: 'all'
= csrf_meta_tags
diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml
similarity index 100%
rename from app/views/doorkeeper/authorized_applications/index.html.haml
rename to app/views/oauth/authorized_applications/index.html.haml
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index 0a0ff863..f2f6f955 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -5,13 +5,17 @@
%tbody
%tr
%th= t('exports.storage')
- %td= number_to_human_size @total_storage
+ %td= number_to_human_size @export.total_storage
%td
%tr
%th= t('exports.follows')
- %td= @total_follows
- %td= table_link_to 'download', t('exports.csv'), follows_settings_export_path(format: :csv)
+ %td= @export.total_follows
+ %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
%tr
%th= t('exports.blocks')
- %td= @total_blocks
- %td= table_link_to 'download', t('exports.csv'), blocks_settings_export_path(format: :csv)
+ %td= @export.total_blocks
+ %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
+ %tr
+ %th= t('exports.mutes')
+ %td= @export.total_mutes
+ %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
diff --git a/app/views/settings/preferences/show.html.haml b/app/views/settings/preferences/show.html.haml
index 64cf32c3..e819429b 100644
--- a/app/views/settings/preferences/show.html.haml
+++ b/app/views/settings/preferences/show.html.haml
@@ -22,5 +22,8 @@
= ff.input :must_be_follower, as: :boolean, wrapper: :with_label
= ff.input :must_be_following, as: :boolean, wrapper: :with_label
+ .fields-group
+ = f.input :setting_boost_modal, as: :boolean, wrapper: :with_label
+
.actions
= f.button :button, t('generic.save_changes'), type: :submit
diff --git a/app/views/settings/two_factor_auths/new.html.haml b/app/views/settings/two_factor_auths/new.html.haml
new file mode 100644
index 00000000..5bae743e
--- /dev/null
+++ b/app/views/settings/two_factor_auths/new.html.haml
@@ -0,0 +1,17 @@
+- content_for :page_title do
+ = t('settings.two_factor_auth')
+
+= simple_form_for @confirmation, url: settings_two_factor_auth_path, method: :post do |f|
+ %p.hint= t('two_factor_auth.instructions_html')
+
+ .qr-wrapper
+ .qr-code= raw @qrcode.as_svg(padding: 0, module_size: 4)
+
+ .qr-alternative
+ %p.hint= t('two_factor_auth.manual_instructions')
+ %samp.qr-alternative__code= current_user.otp_secret.scan(/.{4}/).join(' ')
+
+ = f.input :code, hint: t('two_factor_auth.code_hint'), placeholder: t('simple_form.labels.defaults.otp_attempt')
+
+ .actions
+ = f.button :button, t('two_factor_auth.enable'), type: :submit
diff --git a/app/views/settings/two_factor_auths/show.html.haml b/app/views/settings/two_factor_auths/show.html.haml
index 87bfadc6..047fe0c5 100644
--- a/app/views/settings/two_factor_auths/show.html.haml
+++ b/app/views/settings/two_factor_auths/show.html.haml
@@ -2,16 +2,9 @@
= t('settings.two_factor_auth')
.simple_form
+ %p.hint= t('two_factor_auth.description_html')
+
- if current_user.otp_required_for_login
- %p.hint= t('two_factor_auth.instructions_html')
-
- .qr-code= raw @qrcode.as_svg(padding: 0, module_size: 5)
-
- %p.hint= t('two_factor_auth.plaintext_secret_html', secret: current_user.otp_secret)
-
- %p.hint= t('two_factor_auth.warning')
-
= link_to t('two_factor_auth.disable'), disable_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button'
- else
- %p.hint= t('two_factor_auth.description_html')
- = link_to t('two_factor_auth.enable'), enable_settings_two_factor_auth_path, data: { method: 'POST' }, class: 'block-button'
+ = link_to t('two_factor_auth.setup'), new_settings_two_factor_auth_path, class: 'block-button'
diff --git a/app/views/shared/_landing_strip.html.haml b/app/views/shared/_landing_strip.html.haml
index bb081e54..3536c5ca 100644
--- a/app/views/shared/_landing_strip.html.haml
+++ b/app/views/shared/_landing_strip.html.haml
@@ -1,2 +1,5 @@
.landing-strip
- = t('landing_strip_html', name: display_name(account), domain: Rails.configuration.x.local_domain, sign_up_path: new_user_registration_path)
+ = t('landing_strip_html',
+ name: content_tag(:span, display_name(account), class: :emojify),
+ domain: Rails.configuration.x.local_domain,
+ sign_up_path: new_user_registration_path)
diff --git a/app/views/stream_entries/_detailed_status.html.haml b/app/views/stream_entries/_detailed_status.html.haml
index 8495f28b..e3cc522b 100644
--- a/app/views/stream_entries/_detailed_status.html.haml
+++ b/app/views/stream_entries/_detailed_status.html.haml
@@ -1,18 +1,19 @@
.detailed-status.light
- = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: @external_links ? '_blank' : nil, rel: 'noopener' do
+ = link_to TagManager.instance.url_for(status.account), class: 'detailed-status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do
%div
%div.avatar
= image_tag status.account.avatar.url(:original), width: 48, height: 48, alt: '', class: 'u-photo'
%span.display-name
%strong.p-name.emojify= display_name(status.account)
- %span.p-nickname= acct(status.account)
+ %span= acct(status.account)
- .status__content.e-content.p-name.emojify<
- - unless status.spoiler_text.blank?
+ .status__content.p-name.emojify<
+ - if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }<
- %span>= "#{status.spoiler_text} "
+ %span.p-summary>= "#{status.spoiler_text} "
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
- %div{ style: "display: #{status.spoiler_text.blank? ? 'block' : 'none'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+ %div.e-content{ style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+
- unless status.media_attachments.empty?
- if status.media_attachments.first.video?
@@ -30,7 +31,7 @@
%div.detailed-status__meta
%data.dt-published{ value: status.created_at.to_time.iso8601 }
- = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: @external_links ? '_blank' : nil, rel: 'noopener' do
+ = link_to TagManager.instance.url_for(status), class: 'detailed-status__datetime u-url u-uid', target: stream_link_target, rel: 'noopener' do
%span= l(status.created_at)
·
- if status.application
@@ -49,4 +50,4 @@
- if user_signed_in?
·
- = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'open-in-web-link'
+ = link_to t('statuses.open_in_web'), web_url("statuses/#{status.id}"), class: 'open-in-web-link', target: '_blank'
diff --git a/app/views/stream_entries/_simple_status.html.haml b/app/views/stream_entries/_simple_status.html.haml
index 2eb9bf16..52905ff5 100644
--- a/app/views/stream_entries/_simple_status.html.haml
+++ b/app/views/stream_entries/_simple_status.html.haml
@@ -1,23 +1,23 @@
.status.light
.status__header
.status__meta
- = link_to time_ago_in_words(status.created_at), TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', title: l(status.created_at), target: @external_links ? '_blank' : nil, rel: 'noopener'
+ = link_to time_ago_in_words(status.created_at), TagManager.instance.url_for(status), class: 'status__relative-time u-url u-uid', title: l(status.created_at), target: stream_link_target, rel: 'noopener'
%data.dt-published{ value: status.created_at.to_time.iso8601 }
- = link_to TagManager.instance.url_for(status.account), class: 'status__display-name p-author h-card', target: @external_links ? '_blank' : nil, rel: 'noopener' do
+ = link_to TagManager.instance.url_for(status.account), class: 'status__display-name p-author h-card', target: stream_link_target, rel: 'noopener' do
.status__avatar
%div
= image_tag status.account.avatar(:original), width: 48, height: 48, alt: '', class: 'u-photo'
%span.display-name
%strong.p-name.emojify= display_name(status.account)
- %span.p-nickname= acct(status.account)
+ %span= acct(status.account)
- .status__content.e-content.p-name.emojify<
- - unless status.spoiler_text.blank?
+ .status__content.p-name.emojify<
+ - if status.spoiler_text?
%p{ style: 'margin-bottom: 0' }<
- %span>= "#{status.spoiler_text} "
+ %span.p-summary>= "#{status.spoiler_text} "
%a.status__content__spoiler-link{ href: '#' }= t('statuses.show_more')
- %div{ style: "display: #{status.spoiler_text.blank? ? 'block' : 'none'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
+ %div.e-content{ style: "display: #{status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl?(status.content) ? 'rtl' : 'ltr'}" }= Formatter.instance.format(status)
- unless status.media_attachments.empty?
.status__attachments
diff --git a/app/views/stream_entries/_status.html.haml b/app/views/stream_entries/_status.html.haml
index cdd0dde3..f389a8df 100644
--- a/app/views/stream_entries/_status.html.haml
+++ b/app/views/stream_entries/_status.html.haml
@@ -1,22 +1,32 @@
- include_threads ||= false
- is_predecessor ||= false
- is_successor ||= false
+- direct_reply_id ||= false
+- parent_id ||= false
+- is_direct_parent = direct_reply_id == status.id
+- is_direct_child = parent_id == status.in_reply_to_id
+- parent_id ||= false
- centered ||= include_threads && !is_predecessor && !is_successor
+- h_class = microformats_h_class(status, is_predecessor, is_successor, include_threads)
+- style_classes = style_classes(status, is_predecessor, is_successor, include_threads)
+- mf_classes = microformats_classes(status, is_direct_parent, is_direct_child)
+- entry_classes = h_class + ' ' + mf_classes + ' ' + style_classes
- if status.reply? && include_threads
- = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true }
+ = render partial: 'stream_entries/status', collection: @ancestors, as: :status, locals: { is_predecessor: true, direct_reply_id: status.in_reply_to_id}
+
+.entry{ class: entry_classes }
-.entry{ class: entry_classes(status, is_predecessor, is_successor, include_threads) }
- if status.reblog?
.pre-header
%div.pre-header__icon
= fa_icon('retweet fw')
%span
= link_to TagManager.instance.url_for(status.account), class: 'status__display-name muted' do
- %strong= display_name(status.account)
+ %strong.emojify= display_name(status.account)
= t('stream_entries.reblogged')
- = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: proper_status(status) }
+ = render partial: centered ? 'stream_entries/detailed_status' : 'stream_entries/simple_status', locals: { status: status.proper }
- if include_threads
- = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true }
+ = render partial: 'stream_entries/status', collection: @descendants, as: :status, locals: { is_successor: true, parent_id: status.id}
diff --git a/app/views/stream_entries/show.atom.ruby b/app/views/stream_entries/show.atom.ruby
deleted file mode 100644
index a298f326..00000000
--- a/app/views/stream_entries/show.atom.ruby
+++ /dev/null
@@ -1,9 +0,0 @@
-Nokogiri::XML::Builder.new do |xml|
- entry(xml, true) do
- author(xml) do
- include_author xml, @stream_entry.account
- end
-
- include_entry xml, @stream_entry
- end
-end.to_xml
diff --git a/app/views/stream_entries/show.html.haml b/app/views/stream_entries/show.html.haml
index c109ff4b..86294675 100644
--- a/app/views/stream_entries/show.html.haml
+++ b/app/views/stream_entries/show.html.haml
@@ -2,7 +2,7 @@
%link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/
%link{ rel: 'alternate', type: 'application/json+oembed', href: api_oembed_url(url: account_stream_entry_url(@account, @stream_entry), format: 'json') }/
- %meta{ property: 'og:site_name', content: 'Mastodon' }/
+ %meta{ property: 'og:site_name', content: site_title }/
%meta{ property: 'og:type', content: 'article' }/
%meta{ property: 'og:title', content: "#{@account.username} on #{Rails.configuration.x.local_domain}" }/
@@ -11,7 +11,7 @@
- else
%meta{ property: 'og:description', content: @stream_entry.activity.content }/
- - if @stream_entry.activity.is_a?(Status) && @stream_entry.activity.media_attachments.size > 0
+ - if @stream_entry.activity.is_a?(Status) && !@stream_entry.activity.sensitive? && @stream_entry.activity.media_attachments.size > 0
%meta{ property: 'og:image', content: full_asset_url(@stream_entry.activity.media_attachments.first.file.url(:small)) }/
- else
%meta{ property: 'og:image', content: full_asset_url(@account.avatar.url(:original)) }/
@@ -23,5 +23,5 @@
- if !user_signed_in? && !Rails.configuration.x.single_user_mode
= render partial: 'shared/landing_strip', locals: { account: @stream_entry.account }
-.activity-stream.activity-stream-headless
+.activity-stream.activity-stream-headless.h-entry
= render partial: "stream_entries/#{@type}", locals: { @type.to_sym => @stream_entry.activity, include_threads: true }
diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml
index 32a50e15..c894cdb2 100644
--- a/app/views/tags/show.html.haml
+++ b/app/views/tags/show.html.haml
@@ -15,4 +15,4 @@
- if @statuses.size == 20
.pagination
- = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), tag_url(@tag, max_id: @statuses.last.id), class: 'next_page', rel: 'next'
+ = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), tag_url(@tag, max_id: @statuses.last.id), class: 'next', rel: 'next'
diff --git a/app/views/user_mailer/confirmation_instructions.en.html.erb b/app/views/user_mailer/confirmation_instructions.en.html.erb
index 69e9ff80..f28a38be 100644
--- a/app/views/user_mailer/confirmation_instructions.en.html.erb
+++ b/app/views/user_mailer/confirmation_instructions.en.html.erb
@@ -1,5 +1,12 @@
-Welcome <%= @resource.email %>!
+Welcome <%= @resource.email %> !
-You can confirm your Mastodon account email through the link below:
+You just created an account on <%= @instance %>.
-<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+To confirm your inscription, please click on the following link :
+<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
+
+
Please also check out our <%= link_to 'terms and conditions', terms_url %>.
+
+Sincerely,
+
+
The <%= @instance %> team
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.en.text.erb b/app/views/user_mailer/confirmation_instructions.en.text.erb
index bb21cf8e..0419adef 100644
--- a/app/views/user_mailer/confirmation_instructions.en.text.erb
+++ b/app/views/user_mailer/confirmation_instructions.en.text.erb
@@ -1,5 +1,12 @@
-Welcome <%= @resource.email %>!
+Welcome <%= @resource.email %> !
-You can confirm your Mastodon account email through the link below:
+You just created an account on <%= @instance %>.
+To confirm your inscription, please click on the following link :
<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+Please also check out our terms and conditions <%= terms_url %>
+
+Sincerely,
+
+The <%= @instance %> team
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.fi.html.erb b/app/views/user_mailer/confirmation_instructions.fi.html.erb
new file mode 100644
index 00000000..8b72722d
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.fi.html.erb
@@ -0,0 +1,5 @@
+Tervetuloa <%= @resource.email %>!
+
+Voit vahvistaa Mastodon tilisi klikkaamalla alla olevaa linkkiä:
+
+<%= link_to 'Varmista tilini', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/user_mailer/confirmation_instructions.fi.text.erb b/app/views/user_mailer/confirmation_instructions.fi.text.erb
new file mode 100644
index 00000000..796913ab
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.fi.text.erb
@@ -0,0 +1,5 @@
+Tervetuloa <%= @resource.email %>!
+
+Voit vahvistaa Mastodon tilisi klikkaamalla alla olevaa linkkiä:
+
+<%= confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/user_mailer/confirmation_instructions.fr.html.erb b/app/views/user_mailer/confirmation_instructions.fr.html.erb
new file mode 100644
index 00000000..b0b3d0f5
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.fr.html.erb
@@ -0,0 +1,14 @@
+Bonjour <%= @resource.email %> !
+
+
Vous venez de vous créer un compte sur <%= @instance %> et nous vous en remercions :)
+
+Pour confirmer votre inscription, merci de cliquer sur le lien suivant :
+<%= link_to 'Confirmer mon compte', confirmation_url(@resource, confirmation_token: @token) %>
+
+Après votre première connexion, vous pourrez accéder à la documentation de l'outil.
+
+Pensez également à jeter un œil à nos <%= link_to 'conditions d\'utilisation', terms_url %>.
+
+Amicalement,
+
+L'équipe <%= @instance %>
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.fr.text.erb b/app/views/user_mailer/confirmation_instructions.fr.text.erb
new file mode 100644
index 00000000..cf8e3968
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.fr.text.erb
@@ -0,0 +1,14 @@
+Bonjour <%= @resource.email %> !
+
+Vous venez de vous créer un compte sur <%= @instance %> et nous vous en remercions.
+
+Pour confirmer votre inscription, merci de cliquer sur le lien suivant :
+<%= confirmation_url(@resource, confirmation_token: @token) %>
+
+Après votre première connexion, vous pourrez accéder à la documentation de l'outil.
+
+Pour rappel, nos conditions d'utilisation sont indiquées ici <%= terms_url %>
+
+Amicalement,
+
+L'équipe <%= @instance %>
\ No newline at end of file
diff --git a/app/views/user_mailer/confirmation_instructions.ja.html.erb b/app/views/user_mailer/confirmation_instructions.ja.html.erb
new file mode 100644
index 00000000..bbb44b2c
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.ja.html.erb
@@ -0,0 +1,5 @@
+ようこそ<%= @resource.email %>さん
+
+以下のリンクをクリックしてMastodonアカウントのメールアドレスを確認してください
+
+<%= link_to 'メールアドレスの確認', confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/user_mailer/confirmation_instructions.ja.text.erb b/app/views/user_mailer/confirmation_instructions.ja.text.erb
new file mode 100644
index 00000000..ad8abee2
--- /dev/null
+++ b/app/views/user_mailer/confirmation_instructions.ja.text.erb
@@ -0,0 +1,5 @@
+ようこそ<%= @resource.email %>さん
+
+以下のリンクをクリックしてMastodonアカウントのメールアドレスを確認してください
+
+<%= confirmation_url(@resource, confirmation_token: @token) %>
diff --git a/app/views/user_mailer/password_change.fi.html.erb b/app/views/user_mailer/password_change.fi.html.erb
new file mode 100644
index 00000000..c56b9659
--- /dev/null
+++ b/app/views/user_mailer/password_change.fi.html.erb
@@ -0,0 +1,3 @@
+Hei <%= @resource.email %>!
+
+Lähetämme tämän viestin ilmoittaaksemme että salasanasi on vaihdettu.
diff --git a/app/views/user_mailer/password_change.fi.text.erb b/app/views/user_mailer/password_change.fi.text.erb
new file mode 100644
index 00000000..d90c3fde
--- /dev/null
+++ b/app/views/user_mailer/password_change.fi.text.erb
@@ -0,0 +1,3 @@
+Hei <%= @resource.email %>!
+
+Lähetämme tämän viestin ilmoittaaksemme että salasanasi on vaihdettu.
diff --git a/app/views/user_mailer/password_change.fr.html.erb b/app/views/user_mailer/password_change.fr.html.erb
new file mode 100644
index 00000000..cb8a261f
--- /dev/null
+++ b/app/views/user_mailer/password_change.fr.html.erb
@@ -0,0 +1,3 @@
+Bonjour <%= @resource.email %> !
+
+Nous vous contactons pour vous informer que votre mot de passe sur Mastodon a bien été modifié.
diff --git a/app/views/user_mailer/password_change.fr.text.erb b/app/views/user_mailer/password_change.fr.text.erb
new file mode 100644
index 00000000..f068f1cd
--- /dev/null
+++ b/app/views/user_mailer/password_change.fr.text.erb
@@ -0,0 +1,3 @@
+Bonjour <%= @resource.email %> !
+
+Nous vous contactons pour vous informer que votre mot de passe sur Mastodon a bien été modifié.
diff --git a/app/views/user_mailer/password_change.ja.html.erb b/app/views/user_mailer/password_change.ja.html.erb
new file mode 100644
index 00000000..3aa83f18
--- /dev/null
+++ b/app/views/user_mailer/password_change.ja.html.erb
@@ -0,0 +1,3 @@
+こんにちは<%= @resource.email %>さん
+
+Mastodonアカウントのパスワードが変更されました。
diff --git a/app/views/user_mailer/password_change.ja.text.erb b/app/views/user_mailer/password_change.ja.text.erb
new file mode 100644
index 00000000..aa29b9b2
--- /dev/null
+++ b/app/views/user_mailer/password_change.ja.text.erb
@@ -0,0 +1,3 @@
+こんにちは<%= @resource.email %>さん
+
+Mastodonアカウントのパスワードが変更されました。
diff --git a/app/views/user_mailer/reset_password_instructions.fi.html.erb b/app/views/user_mailer/reset_password_instructions.fi.html.erb
new file mode 100644
index 00000000..53be0b62
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.fi.html.erb
@@ -0,0 +1,8 @@
+Hei <%= @resource.email %>!
+
+Joku on pyytänyt salasanvaihto Mastodonissa. Voit tehdä sen allaolevassa linkissä.
+
+<%= link_to 'Vaihda salasanani', edit_password_url(@resource, reset_password_token: @token) %>
+
+Jos et pyytänyt vaihtoa, poista tämä viesti.
+Salasanaasi ei vaihdeta ennen kuin menet ylläolevaan linkkiin ja luot uuden.
diff --git a/app/views/user_mailer/reset_password_instructions.fi.text.erb b/app/views/user_mailer/reset_password_instructions.fi.text.erb
new file mode 100644
index 00000000..c826d5fc
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.fi.text.erb
@@ -0,0 +1,8 @@
+Hei <%= @resource.email %>!
+
+Joku on pyytänyt salasanvaihto Mastodonissa. Voit tehdä sen allaolevassa linkissä.
+
+<%= edit_password_url(@resource, reset_password_token: @token) %>
+
+Jos et pyytänyt vaihtoa, poista tämä viesti.
+Salasanaasi ei vaihdeta ennen kuin menet ylläolevaan linkkiin ja luot uuden.
diff --git a/app/views/user_mailer/reset_password_instructions.fr.html.erb b/app/views/user_mailer/reset_password_instructions.fr.html.erb
new file mode 100644
index 00000000..95789e38
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.fr.html.erb
@@ -0,0 +1,8 @@
+Bonjour <%= @resource.email %> !
+
+Quelqu'un a demandé à réinitialiser votre mot de passe sur Mastodon. Vous pouvez effectuer la réinitialisation en cliquant sur le lien ci-dessous.
+
+<%= link_to 'Modifier mon mot de passe', edit_password_url(@resource, reset_password_token: @token) %>
+
+Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer ce message.
+Votre mot de passe ne sera pas modifié tant que vous n'accéderez pas au lien ci-dessus et n'en choisirez pas un nouveau.
diff --git a/app/views/user_mailer/reset_password_instructions.fr.text.erb b/app/views/user_mailer/reset_password_instructions.fr.text.erb
new file mode 100644
index 00000000..73160cb4
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.fr.text.erb
@@ -0,0 +1,8 @@
+Bonjour <%= @resource.email %> !
+
+Quelqu'un a demandé à réinitialiser votre mot de passe sur Mastodon. Vous pouvez effectuer la réinitialisation en cliquant sur le lien ci-dessous.
+
+<%= edit_password_url(@resource, reset_password_token: @token) %>
+
+Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer ce message.
+Votre mot de passe ne sera pas modifié tant que vous n'accéderez pas au lien ci-dessus et n'en choisirez pas un nouveau.
diff --git a/app/views/user_mailer/reset_password_instructions.ja.html.erb b/app/views/user_mailer/reset_password_instructions.ja.html.erb
new file mode 100644
index 00000000..156758ef
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.ja.html.erb
@@ -0,0 +1,8 @@
+こんにちは<%= @resource.email %>さん
+
+Mastodonアカウントのパスワードの変更がリクエストされました。以下のリンクをクリックして操作を完了できます。
+
+<%= link_to 'パスワードを変更', edit_password_url(@resource, reset_password_token: @token) %>
+
+このメールに見に覚えのない場合は無視してください。
+上記のリンクにアクセスし、変更をしない限りパスワードは変更されません。
diff --git a/app/views/user_mailer/reset_password_instructions.ja.text.erb b/app/views/user_mailer/reset_password_instructions.ja.text.erb
new file mode 100644
index 00000000..5fb0eba0
--- /dev/null
+++ b/app/views/user_mailer/reset_password_instructions.ja.text.erb
@@ -0,0 +1,8 @@
+こんにちは<%= @resource.email %>さん
+
+Mastodonアカウントのパスワードの変更がリクエストされました。以下のリンクをクリックして操作を完了できます。
+
+<%= edit_password_url(@resource, reset_password_token: @token) %>
+
+このメールに見に覚えのない場合は無視してください。
+上記のリンクにアクセスし、変更をしない限りパスワードは変更されません。
diff --git a/app/views/xrd/host_meta.xml.ruby b/app/views/well_known/host_meta/show.xml.ruby
similarity index 100%
rename from app/views/xrd/host_meta.xml.ruby
rename to app/views/well_known/host_meta/show.xml.ruby
diff --git a/app/views/xrd/webfinger.json.rabl b/app/views/well_known/webfinger/show.json.rabl
similarity index 100%
rename from app/views/xrd/webfinger.json.rabl
rename to app/views/well_known/webfinger/show.json.rabl
diff --git a/app/views/xrd/webfinger.xml.ruby b/app/views/well_known/webfinger/show.xml.ruby
similarity index 100%
rename from app/views/xrd/webfinger.xml.ruby
rename to app/views/well_known/webfinger/show.xml.ruby
diff --git a/app/workers/admin/suspension_worker.rb b/app/workers/admin/suspension_worker.rb
index 38761f3b..7ef2b35e 100644
--- a/app/workers/admin/suspension_worker.rb
+++ b/app/workers/admin/suspension_worker.rb
@@ -3,6 +3,8 @@
class Admin::SuspensionWorker
include Sidekiq::Worker
+ sidekiq_options queue: 'pull'
+
def perform(account_id)
SuspendAccountService.new.call(Account.find(account_id))
end
diff --git a/app/workers/after_remote_follow_request_worker.rb b/app/workers/after_remote_follow_request_worker.rb
index f1d6869c..92806921 100644
--- a/app/workers/after_remote_follow_request_worker.rb
+++ b/app/workers/after_remote_follow_request_worker.rb
@@ -3,7 +3,7 @@
class AfterRemoteFollowRequestWorker
include Sidekiq::Worker
- sidekiq_options retry: 5
+ sidekiq_options queue: 'pull', retry: 5
def perform(follow_request_id)
follow_request = FollowRequest.find(follow_request_id)
@@ -13,5 +13,7 @@ class AfterRemoteFollowRequestWorker
follow_request.destroy
FollowService.new.call(follow_request.account, updated_account.acct)
+ rescue ActiveRecord::RecordNotFound
+ true
end
end
diff --git a/app/workers/after_remote_follow_worker.rb b/app/workers/after_remote_follow_worker.rb
index 0d04456a..d12fa345 100644
--- a/app/workers/after_remote_follow_worker.rb
+++ b/app/workers/after_remote_follow_worker.rb
@@ -3,7 +3,7 @@
class AfterRemoteFollowWorker
include Sidekiq::Worker
- sidekiq_options retry: 5
+ sidekiq_options queue: 'pull', retry: 5
def perform(follow_id)
follow = Follow.find(follow_id)
@@ -13,5 +13,7 @@ class AfterRemoteFollowWorker
follow.destroy
FollowService.new.call(follow.account, updated_account.acct)
+ rescue ActiveRecord::RecordNotFound
+ true
end
end
diff --git a/app/workers/application_worker.rb b/app/workers/application_worker.rb
new file mode 100644
index 00000000..436f2476
--- /dev/null
+++ b/app/workers/application_worker.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class ApplicationWorker
+ def info(message)
+ Rails.logger.info("#{self.class.name} - #{message}")
+ end
+end
diff --git a/app/workers/distribution_worker.rb b/app/workers/distribution_worker.rb
index f4e738d8..f7953689 100644
--- a/app/workers/distribution_worker.rb
+++ b/app/workers/distribution_worker.rb
@@ -1,14 +1,11 @@
# frozen_string_literal: true
-class DistributionWorker
+class DistributionWorker < ApplicationWorker
include Sidekiq::Worker
def perform(status_id)
- status = Status.find(status_id)
-
- FanOutOnWriteService.new.call(status)
- WarmCacheService.new.call(status)
+ FanOutOnWriteService.new.call(Status.find(status_id))
rescue ActiveRecord::RecordNotFound
- true
+ info("Couldn't find the status")
end
end
diff --git a/app/workers/domain_block_worker.rb b/app/workers/domain_block_worker.rb
new file mode 100644
index 00000000..88447782
--- /dev/null
+++ b/app/workers/domain_block_worker.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class DomainBlockWorker
+ include Sidekiq::Worker
+
+ def perform(domain_block_id)
+ BlockDomainService.new.call(DomainBlock.find(domain_block_id))
+ rescue ActiveRecord::RecordNotFound
+ true
+ end
+end
diff --git a/app/workers/feed_insert_worker.rb b/app/workers/feed_insert_worker.rb
new file mode 100644
index 00000000..a58dfaa7
--- /dev/null
+++ b/app/workers/feed_insert_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class FeedInsertWorker
+ include Sidekiq::Worker
+
+ def perform(status_id, follower_id)
+ status = Status.find(status_id)
+ follower = Account.find(follower_id)
+
+ return if FeedManager.instance.filter?(:home, status, follower.id)
+ FeedManager.instance.push(:home, follower, status)
+ rescue ActiveRecord::RecordNotFound
+ true
+ end
+end
diff --git a/app/workers/import_worker.rb b/app/workers/import_worker.rb
index a3ae2a85..bb21468e 100644
--- a/app/workers/import_worker.rb
+++ b/app/workers/import_worker.rb
@@ -4,32 +4,55 @@ require 'csv'
class ImportWorker
include Sidekiq::Worker
+ sidekiq_options queue: 'pull', retry: false
- sidekiq_options retry: false
+ attr_reader :import
def perform(import_id)
- import = Import.find(import_id)
+ @import = Import.find(import_id)
- case import.type
+ case @import.type
when 'blocking'
- process_blocks(import)
+ process_blocks
when 'following'
- process_follows(import)
+ process_follows
+ when 'muting'
+ process_mutes
end
- import.destroy
+ @import.destroy
end
private
- def process_blocks(import)
- from_account = import.account
+ def from_account
+ @import.account
+ end
- CSV.foreach(import.data.path) do |row|
- next if row.size != 1
+ def import_contents
+ Paperclip.io_adapters.for(@import.data).read
+ end
+ def import_rows
+ CSV.new(import_contents).reject(&:blank?)
+ end
+
+ def process_mutes
+ import_rows.each do |row|
begin
- target_account = FollowRemoteAccountService.new.call(row[0])
+ target_account = FollowRemoteAccountService.new.call(row.first)
+ next if target_account.nil?
+ MuteService.new.call(from_account, target_account)
+ rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
+ next
+ end
+ end
+ end
+
+ def process_blocks
+ import_rows.each do |row|
+ begin
+ target_account = FollowRemoteAccountService.new.call(row.first)
next if target_account.nil?
BlockService.new.call(from_account, target_account)
rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
@@ -38,15 +61,11 @@ class ImportWorker
end
end
- def process_follows(import)
- from_account = import.account
-
- CSV.foreach(import.data.path) do |row|
- next if row.size != 1
-
+ def process_follows
+ import_rows.each do |row|
begin
- FollowService.new.call(from_account, row[0])
- rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
+ FollowService.new.call(from_account, row.first)
+ rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError
next
end
end
diff --git a/app/workers/link_crawl_worker.rb b/app/workers/link_crawl_worker.rb
index af3394b8..834b0088 100644
--- a/app/workers/link_crawl_worker.rb
+++ b/app/workers/link_crawl_worker.rb
@@ -3,7 +3,7 @@
class LinkCrawlWorker
include Sidekiq::Worker
- sidekiq_options retry: false
+ sidekiq_options queue: 'pull', retry: false
def perform(status_id)
FetchLinkCardService.new.call(Status.find(status_id))
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index 0f288f43..d745cb99 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -3,6 +3,8 @@
class MergeWorker
include Sidekiq::Worker
+ sidekiq_options queue: 'pull'
+
def perform(from_account_id, into_account_id)
FeedManager.instance.merge_into_timeline(Account.find(from_account_id), Account.find(into_account_id))
end
diff --git a/app/workers/notification_worker.rb b/app/workers/notification_worker.rb
index 1a2faefd..da1d6ab4 100644
--- a/app/workers/notification_worker.rb
+++ b/app/workers/notification_worker.rb
@@ -3,7 +3,7 @@
class NotificationWorker
include Sidekiq::Worker
- sidekiq_options retry: 5
+ sidekiq_options queue: 'push', retry: 5
def perform(xml, source_account_id, target_account_id)
SendInteractionService.new.call(xml, Account.find(source_account_id), Account.find(target_account_id))
diff --git a/app/workers/pubsubhubbub/delivery_worker.rb b/app/workers/pubsubhubbub/delivery_worker.rb
index 15005bc8..b440f798 100644
--- a/app/workers/pubsubhubbub/delivery_worker.rb
+++ b/app/workers/pubsubhubbub/delivery_worker.rb
@@ -13,15 +13,19 @@ class Pubsubhubbub::DeliveryWorker
def perform(subscription_id, payload)
subscription = Subscription.find(subscription_id)
headers = {}
+ host = Addressable::URI.parse(subscription.callback_url).host
+
+ return if DomainBlock.blocked?(host)
headers['User-Agent'] = 'Mastodon/PubSubHubbub'
headers['Link'] = LinkHeader.new([[api_push_url, [%w(rel hub)]], [account_url(subscription.account, format: :atom), [%w(rel self)]]]).to_s
- headers['X-Hub-Signature'] = signature(subscription.secret, payload) unless subscription.secret.blank?
+ headers['X-Hub-Signature'] = signature(subscription.secret, payload) if subscription.secret?
response = HTTP.timeout(:per_operation, write: 50, connect: 20, read: 50)
.headers(headers)
.post(subscription.callback_url, body: payload)
+ return subscription.destroy! if response.code > 299 && response.code < 500 && response.code != 429 # HTTP 4xx means error is not temporary, except for 429 (throttling)
raise "Delivery failed for #{subscription.callback_url}: HTTP #{response.code}" unless response.code > 199 && response.code < 300
subscription.touch(:last_successful_delivery_at)
diff --git a/app/workers/pubsubhubbub/distribution_worker.rb b/app/workers/pubsubhubbub/distribution_worker.rb
index 82ff257a..68ca0f87 100644
--- a/app/workers/pubsubhubbub/distribution_worker.rb
+++ b/app/workers/pubsubhubbub/distribution_worker.rb
@@ -10,14 +10,10 @@ class Pubsubhubbub::DistributionWorker
return if stream_entry.hidden?
- account = stream_entry.account
- renderer = AccountsController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https)
- payload = renderer.render(:show, assigns: { account: account, entries: [stream_entry] }, formats: [:atom])
- # domains = account.followers_domains
+ account = stream_entry.account
+ payload = AtomSerializer.render(AtomSerializer.new.feed(account, [stream_entry]))
Subscription.where(account: account).active.select('id, callback_url').find_each do |subscription|
- host = Addressable::URI.parse(subscription.callback_url).host
- next if DomainBlock.blocked?(host) # || !domains.include?(host)
Pubsubhubbub::DeliveryWorker.perform_async(subscription.id, payload)
end
rescue ActiveRecord::RecordNotFound
diff --git a/app/workers/push_update_worker.rb b/app/workers/push_update_worker.rb
new file mode 100644
index 00000000..fbcdcf63
--- /dev/null
+++ b/app/workers/push_update_worker.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class PushUpdateWorker
+ include Sidekiq::Worker
+
+ def perform(account_id, status_id)
+ account = Account.find(account_id)
+ status = Status.find(status_id)
+ message = InlineRenderer.render(status, account, 'api/v1/statuses/show')
+
+ Redis.current.publish("timeline:#{account.id}", Oj.dump(event: :update, payload: message, queued_at: (Time.now.to_f * 1000.0).to_i))
+ rescue ActiveRecord::RecordNotFound
+ true
+ end
+end
diff --git a/app/workers/regeneration_worker.rb b/app/workers/regeneration_worker.rb
index 3aece0ba..da8b845f 100644
--- a/app/workers/regeneration_worker.rb
+++ b/app/workers/regeneration_worker.rb
@@ -3,7 +3,9 @@
class RegenerationWorker
include Sidekiq::Worker
- def perform(account_id, timeline_type)
- PrecomputeFeedService.new.call(timeline_type, Account.find(account_id))
+ sidekiq_options queue: 'pull', backtrace: true, unique: :until_executed
+
+ def perform(account_id, _ = :home)
+ PrecomputeFeedService.new.call(:home, Account.find(account_id))
end
end
diff --git a/app/workers/remote_profile_update_worker.rb b/app/workers/remote_profile_update_worker.rb
new file mode 100644
index 00000000..03585ad2
--- /dev/null
+++ b/app/workers/remote_profile_update_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class RemoteProfileUpdateWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: 'pull'
+
+ def perform(account_id, body, resubscribe)
+ UpdateRemoteProfileService.new.call(body, Account.find(account_id), resubscribe)
+ rescue ActiveRecord::RecordNotFound
+ true
+ end
+end
diff --git a/app/workers/salmon_worker.rb b/app/workers/salmon_worker.rb
index fc95ce47..d37d4043 100644
--- a/app/workers/salmon_worker.rb
+++ b/app/workers/salmon_worker.rb
@@ -7,7 +7,7 @@ class SalmonWorker
def perform(account_id, body)
ProcessInteractionService.new.call(body, Account.find(account_id))
- rescue ActiveRecord::RecordNotFound
+ rescue Nokogiri::XML::XPath::SyntaxError, ActiveRecord::RecordNotFound
true
end
end
diff --git a/app/workers/thread_resolve_worker.rb b/app/workers/thread_resolve_worker.rb
index 593edd03..38287e8e 100644
--- a/app/workers/thread_resolve_worker.rb
+++ b/app/workers/thread_resolve_worker.rb
@@ -3,7 +3,7 @@
class ThreadResolveWorker
include Sidekiq::Worker
- sidekiq_options retry: false
+ sidekiq_options queue: 'pull', retry: false
def perform(child_status_id, parent_url)
child_status = Status.find(child_status_id)
diff --git a/app/workers/unmerge_worker.rb b/app/workers/unmerge_worker.rb
index dbf7243d..ea6aaceb 100644
--- a/app/workers/unmerge_worker.rb
+++ b/app/workers/unmerge_worker.rb
@@ -3,6 +3,8 @@
class UnmergeWorker
include Sidekiq::Worker
+ sidekiq_options queue: 'pull'
+
def perform(from_account_id, into_account_id)
FeedManager.instance.unmerge_from_timeline(Account.find(from_account_id), Account.find(into_account_id))
end
diff --git a/config/application.rb b/config/application.rb
index 9d32f30c..f3f1f7bf 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -24,7 +24,25 @@ module Mastodon
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
- config.i18n.available_locales = [:en, :de, :es, :pt, :fr, :hu, :uk, 'zh-CN']
+ config.i18n.available_locales = [
+ :en,
+ :bg,
+ :de,
+ :eo,
+ :es,
+ :fi,
+ :fr,
+ :hu,
+ :ja,
+ :nl,
+ :no,
+ :pt,
+ :ru,
+ :uk,
+ 'zh-CN',
+ :'zh-HK',
+ ]
+
config.i18n.default_locale = :en
# config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
diff --git a/config/database.yml b/config/database.yml
index 5ec342f9..810b8327 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -22,3 +22,4 @@ production:
password: <%= ENV['DB_PASS'] || '' %>
host: <%= ENV['DB_HOST'] || 'localhost' %>
port: <%= ENV['DB_PORT'] || 5432 %>
+ prepared_statements: <%= ENV['PREPARED_STATEMENTS'] || 'true' %>
diff --git a/config/environments/production.rb b/config/environments/production.rb
index dc5dd4af..0b0ae29c 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -38,9 +38,9 @@ Rails.application.configure do
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = false
- # Use the lowest log level to ensure availability of diagnostic information
+ # By default, use the lowest log level to ensure availability of diagnostic information
# when problems arise.
- config.log_level = :debug
+ config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info').to_sym
# Prepend all log lines with the following tags.
config.log_tags = [:request_id]
@@ -64,7 +64,7 @@ Rails.application.configure do
password: ENV.fetch('REDIS_PASSWORD') { false },
db: 0,
namespace: 'cache',
- expires_in: 20.minutes,
+ expires_in: 10.minutes,
}
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
@@ -94,15 +94,18 @@ Rails.application.configure do
# E-mails
config.action_mailer.smtp_settings = {
- :port => ENV['SMTP_PORT'],
- :address => ENV['SMTP_SERVER'],
- :user_name => ENV['SMTP_LOGIN'],
- :password => ENV['SMTP_PASSWORD'],
- :domain => ENV['SMTP_DOMAIN'] || config.x.local_domain,
- :authentication => :plain,
+ :port => ENV['SMTP_PORT'],
+ :address => ENV['SMTP_SERVER'],
+ :user_name => ENV['SMTP_LOGIN'],
+ :password => ENV['SMTP_PASSWORD'],
+ :domain => ENV['SMTP_DOMAIN'] || config.x.local_domain,
+ :authentication => ENV['SMTP_AUTH_METHOD'] || :plain,
+ :openssl_verify_mode => ENV['SMTP_OPENSSL_VERIFY_MODE'],
+ :enable_starttls_auto => ENV['SMTP_ENABLE_STARTTLS_AUTO'] || true,
}
- config.action_mailer.delivery_method = :smtp
+ config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
+
config.react.variant = :production
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 4304bbd1..7ae143f9 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -33,7 +33,7 @@ search:
ignore_unused:
- 'activerecord.attributes.*'
- - '{devise,will_paginate,doorkeeper}.*'
+ - '{devise,pagination,doorkeeper}.*'
- '{datetime,time}.*'
- 'simple_form.{yes,no}'
- 'simple_form.{placeholders,hints,labels}.*'
diff --git a/config/initializers/blacklists.rb b/config/initializers/blacklists.rb
index 52646e64..020d84f5 100644
--- a/config/initializers/blacklists.rb
+++ b/config/initializers/blacklists.rb
@@ -2,4 +2,5 @@
Rails.application.configure do
config.x.email_domains_blacklist = ENV.fetch('EMAIL_DOMAIN_BLACKLIST') { 'mvrht.com' }
+ config.x.email_domains_whitelist = ENV.fetch('EMAIL_DOMAIN_WHITELIST') { '' }
end
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index ede6640b..3c23e7b2 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -74,7 +74,8 @@ Devise.setup do |config|
# It will change confirmation, password recovery and other workflows
# to behave the same regardless if the e-mail provided was right or wrong.
# Does not affect registerable.
- # config.paranoid = true
+ # See : https://github.com/plataformatec/devise/wiki/How-To:-Using-paranoid-mode,-avoid-user-enumeration-on-registerable
+ config.paranoid = true
# By default Devise will store the user in session. You can skip storage for
# particular strategies by setting this option.
diff --git a/config/initializers/httplog.rb b/config/initializers/httplog.rb
index 37f113d5..5cfc16a8 100644
--- a/config/initializers/httplog.rb
+++ b/config/initializers/httplog.rb
@@ -1,3 +1,5 @@
-HttpLog.options[:logger] = Rails.logger
-HttpLog.options[:color] = { color: :yellow }
-HttpLog.options[:compact_log] = true
+HttpLog.configure do |config|
+ config.logger = Rails.logger
+ config.color = { color: :yellow }
+ config.compact_log = true
+end
diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb
new file mode 100644
index 00000000..bd455f38
--- /dev/null
+++ b/config/initializers/kaminari_config.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+Kaminari.configure do |config|
+ config.default_per_page = 40
+ config.window = 1
+ config.left = 3
+ config.right = 1
+end
diff --git a/config/initializers/pagination.rb b/config/initializers/pagination.rb
new file mode 100644
index 00000000..e69de29b
diff --git a/config/initializers/rabl_init.rb b/config/initializers/rabl_init.rb
index f7be0c60..132a4214 100644
--- a/config/initializers/rabl_init.rb
+++ b/config/initializers/rabl_init.rb
@@ -1,4 +1,5 @@
Rabl.configure do |config|
+ config.json_engine = Oj
config.cache_all_output = false
config.cache_sources = Rails.env.production?
config.include_json_root = false
diff --git a/config/locales/bg.yml b/config/locales/bg.yml
new file mode 100644
index 00000000..bceb66ca
--- /dev/null
+++ b/config/locales/bg.yml
@@ -0,0 +1,169 @@
+---
+bg:
+ about:
+ about_mastodon: Mastodon е безплатен сървър с отворен код за социални мрежи. Като децентрализирана алтернатива на комерсиалните платформи, той позволява избягването на риска от монополизация на твоята комуникация от единични компании. Изберете си сървър, на който се доверявате, и ще можете да контактувате с всички останали. Всеки може да пусне Mastodon и лесно да вземе участие в социалната мрежа.
+ about_this: За тази инстанция
+ apps: Приложения
+ business_email: 'Служебен e-mail:'
+ 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: Не следвай
+ application_mailer:
+ settings: 'Промяна на предпочитанията за e-mail: %{link}'
+ signature: Mastodon известия от %{instance}
+ view: 'Преглед:'
+ applications:
+ invalid_url: Предоставеният 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} г."
+ x_days: "%{count} дни"
+ x_minutes: "%{count} мин."
+ x_months: "%{count} м."
+ x_seconds: "%{count} сек."
+ exports:
+ blocks: Вашите блокирания
+ csv: CSV
+ follows: Вашите следвания
+ storage: Съхранение на мултимедия
+ generic:
+ changes_saved_msg: Успешно запазване на промените!
+ powered_by: поддържано от %{link}
+ save_changes: Запази промените
+ validation_errors:
+ one: Нещо все още не е наред! Моля, прегледай грешката по-долу
+ other: Нещо все още не е наред! Моля, прегледай грешките по-долу
+ 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: 'Ето кратко резюме на нещата, които се случиха от последното ти посещение в %{instance} на %{since}:'
+ mention: "%{name} те спомена в:"
+ new_followers_summary:
+ one: Имаш един нов последовател! Ура!
+ 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: Назад
+ remote_follow:
+ acct: Въведи потребителско_име@домейн, от които искаш да следваш
+ missing_resource: Неуспешно търсене на нужния URL за пренасочване за твоя акаунт
+ proceed: Започни следване
+ prompt: 'Ще последваш:'
+ 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: "%d %b, %Y, %H:%M"
+ two_factor_auth:
+ description_html: При активация на двустепенно удостоверяване, за да влезеш в приложението, ще трябва да използваш телефона си. През него ще се генерира код, който да въвеждаш при влизане.
+ disable: Деактивирай
+ enable: Активирай
+ instructions_html: "Сканирай този QR код с Google Authenticator или подобно приложение от своя телефон. Oтсега нататък, това приложение ще генерира код, който ще трябва да въвеждаш при всяко влизане."
+ plaintext_secret_html: 'Тайна в обикновен текст: %{secret}'
+ warning: Ако не можеш да настроиш приложението за удостверяване сега, избери "Деактивирай". В противен случай, няма да можеш да влезеш в акаунта си.
+ users:
+ invalid_email: E-mail адресът е невалиден
+ invalid_otp_token: Невалиден код
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 320bd314..75ac4e1b 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -1,7 +1,7 @@
---
de:
about:
- about_mastodon: Mastodon ist ein freier, quelloffener soziales Netzwerkserver. Eine dezentralisierte Alternative zu kommerziellen Plattformen, verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am sozialen Netzwerk teilnehmen.
+ about_mastodon: Mastodon ist ein freier, quelloffener soziales Netzwerkserver. Als dezentralisierte Alternative zu kommerziellen Plattformen verhindert es die Risiken, die entstehen, wenn eine einzelne Firma deine Kommunikation monopolisiert. Jeder kann Mastodon verwenden und ganz einfach am sozialen Netzwerk teilnehmen.
get_started: Erste Schritte
source_code: Quellcode
terms: AGB
@@ -27,7 +27,7 @@ de:
reset_password: Passwort zurücksetzen
set_new_password: Neues Passwort setzen
authorize_follow:
- error: Das entfernte Profil konnte nicht geladen werden
+ error: Das Profil konnte nicht geladen werden
follow: Folgen
prompt_html: 'Du (%{self}) möchtest dieser Person folgen:'
title: "%{acct} folgen"
@@ -55,25 +55,25 @@ de:
notification_mailer:
favourite:
body: 'Dein Beitrag wurde von %{name} favorisiert:'
- subject: "%{name} hat deinen Beitrag favorisiert"
+ subject: "%{name} hat deinen Beitrag favorisiert."
follow:
body: "%{name} folgt dir jetzt!"
- subject: "%{name} folgt dir nun"
+ subject: "%{name} folgt dir jetzt."
follow_request:
body: "%{name} möchte dir folgen:"
- subject: "%{name} möchte dir folgen"
+ subject: "%{name} möchte dir folgen."
mention:
body: "%{name} hat dich erwähnt:"
- subject: "%{name} hat dich erwähnt"
+ subject: "%{name} hat dich erwähnt."
reblog:
body: 'Dein Beitrag wurde von %{name} geteilt:'
- subject: "%{name} teilte deinen Beitrag"
+ subject: "%{name} teilte deinen Beitrag."
pagination:
next: Vorwärts
prev: Zurück
remote_follow:
- acct: Dein Nutzername@Domain, von dem du dieser Person folgen möchtest
- missing_resource: Die erforderliche Weiterleitungs-URL konnte leider in deinem Profil nicht gefunden werden
+ acct: Dein Nutzername@Domain, von dem aus du dieser Person folgen möchtest.
+ missing_resource: Die erforderliche Weiterleitungs-URL konnte leider in deinem Profil nicht gefunden werden.
proceed: Weiter
prompt: 'Du wirst dieser Person folgen:'
settings:
@@ -88,5 +88,3 @@ de:
default: "%d.%m.%Y %H:%M"
users:
invalid_email: Inkorrekte E-mail-Addresse
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/devise.bg.yml b/config/locales/devise.bg.yml
new file mode 100644
index 00000000..7485b823
--- /dev/null
+++ b/config/locales/devise.bg.yml
@@ -0,0 +1,61 @@
+---
+bg:
+ devise:
+ confirmations:
+ confirmed: Твоят профил беше успешно потвърден. Влизането в профила е успешно.
+ send_instructions: Ще получиш писмо с инструкции как да потвърдиш своя профил до няколко минути.
+ send_paranoid_instructions: Ако твоят имейл адрес съществува в базата ни, ще получиш там инструкции как да потвърдиш своя профил.
+ failure:
+ already_authenticated: Вече си вътре в профила си.
+ inactive: Профилът ти все още не е активиран.
+ invalid: Невалиден имейл адрес или парола.
+ 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: 'Инструкции за смяна на паролата'
+ unlock_instructions:
+ subject: 'Инструкции за отключване'
+ omniauth_callbacks:
+ failure: "Не успяхме да те упълномощим чрез %{kind}, защото \"%{reason}\"."
+ success: "Успешно упълномощаване чрез %{kind} профил."
+ passwords:
+ no_token: Може да достъпваш тази страница само от имейл за промяна на паролата. Ако тази страница е отворена от такъв имейл, увери се, че използваш целия URL-адрес, който сме ти изпратили.
+ 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: "Една грешка попречи този %{resource} да бъде записан:"
+ other: "%{count} грешки попречиха този %{resource} да бъде записан:"
diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml
index 181502f9..58bfaa3d 100644
--- a/config/locales/devise.de.yml
+++ b/config/locales/devise.de.yml
@@ -2,59 +2,59 @@
de:
devise:
confirmations:
- confirmed: "Vielen Dank für Deine Registrierung. Bitte melde dich jetzt an."
- send_instructions: "Du erhältst in wenigen Minuten eine E-Mail, mit der Du Deine Registrierung bestätigen kannst."
- send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert erhältst Du in wenigen Minuten eine E-Mail mit der Du Deine Registrierung bestätigen kannst."
+ confirmed: "Vielen Dank für deine Registrierung. Bitte melde dich jetzt an."
+ send_instructions: "Du erhältst in wenigen Minuten eine E-Mail, mit der du deine Registrierung bestätigen kannst."
+ send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert, erhältst Du in wenigen Minuten eine E-Mail mit der du deine Registrierung bestätigen kannst."
failure:
already_authenticated: "Du bist bereits angemeldet."
inactive: "Dein Account ist nicht aktiv."
invalid: "Ungültige Anmeldedaten."
- last_attempt: "Du hast noch einen Versuch bevor dein Account gesperrt wird"
+ last_attempt: "Du hast noch einen Versuch bevor dein Account gesperrt wird."
locked: "Dein Account ist gesperrt."
not_found_in_database: "E-Mail-Adresse oder Passwort ungültig."
- timeout: "Deine Sitzung ist abgelaufen, bitte melde Dich erneut an."
- unauthenticated: "Du musst Dich anmelden oder registrieren, bevor Du fortfahren kannst."
- unconfirmed: "Du musst Deinen Account bestätigen, bevor Du fortfahren kannst."
+ timeout: "Deine Sitzung ist abgelaufen, bitte melde dich erneut an."
+ unauthenticated: "Du musst Dich anmelden oder registrieren, bevor du fortfahren kannst."
+ unconfirmed: "Du musst deinen Account bestätigen, bevor du fortfahren kannst."
mailer:
confirmation_instructions:
- subject: "Mastodon: Anleitung zur Bestätigung Deines Accounts"
+ subject: "Mastodon: Anleitung zur Bestätigung deines Accounts"
password_change:
subject: 'Mastodon: Passwort wurde geändert'
reset_password_instructions:
- subject: "Mastodon: Anleitung um Dein Passwort zurückzusetzen"
+ subject: "Mastodon: Anleitung um dein Passwort zurückzusetzen"
unlock_instructions:
- subject: "Mastodon: Anleitung um Deinen Account freizuschalten"
+ subject: "Mastodon: Anleitung um deinen Account freizuschalten"
omniauth_callbacks:
- failure: "Du konntest nicht Deinem %{kind}-Account angemeldet werden, weil '%{reason}'."
- success: "Du hast Dich erfolgreich mit Deinem %{kind}-Account angemeldet."
+ failure: "Du konntest nicht mit deinem %{kind}-Account angemeldet werden, weil '%{reason}'."
+ success: "Du hast dich erfolgreich mit Deinem %{kind}-Account angemeldet."
passwords:
- no_token: "Du kannst diese Seite nur von dem Link aus einer E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast stelle bitte sicher, dass du die vollständige Adresse aufrufst."
- send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Dein Passwort zurücksetzen kannst."
- send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert erhältst Du in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Dein Passwort zurücksetzen können."
+ no_token: "Du kannst diese Seite nur über den Link aus der E-Mail zum Passwort-Zurücksetzen aufrufen. Wenn du einen solchen Link aufgerufen hast, stelle bitte sicher, dass du die vollständige Adresse aufrufst."
+ send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst."
+ send_paranoid_instructions: "Falls deine E-Mail-Adresse in unserer Datenbank existiert erhältst du in wenigen Minuten eine E-Mail mit der Anleitung, wie du dein Passwort zurücksetzen kannst."
updated: "Dein Passwort wurde geändert. Du bist jetzt angemeldet."
updated_not_active: "Dein Passwort wurde geändert."
registrations:
destroyed: "Dein Account wurde gelöscht."
signed_up: "Du hast dich erfolgreich registriert."
- signed_up_but_inactive: "Du hast dich erfolgreich registriert. Wir konnten Dich noch nicht anmelden, da Dein Account inaktiv ist."
- signed_up_but_locked: "Du hast dich erfolgreich registriert. Wir konnten Dich noch nicht anmelden, da Dein Account gesperrt ist."
- signed_up_but_unconfirmed: "Du hast Dich erfolgreich registriert. Wir konnten Dich noch nicht anmelden, da Dein Account noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail mit der Anleitung, wie Du Deinen Account freischalten kannst."
- update_needs_confirmation: "Deine Daten wurden aktualisiert, aber Du musst Deine neue E-Mail-Adresse bestätigen. Du erhälst in wenigen Minuten eine E-Mail, mit der Du die Änderung Deiner E-Mail-Adresse abschließen kannst."
+ signed_up_but_inactive: "Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account inaktiv ist."
+ signed_up_but_locked: "Du hast dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account gesperrt ist."
+ signed_up_but_unconfirmed: "Du hast Dich erfolgreich registriert. Wir konnten dich noch nicht anmelden, da dein Account noch nicht bestätigt ist. Du erhältst in Kürze eine E-Mail mit der Anleitung, wie Du Deinen Account freischalten kannst."
+ update_needs_confirmation: "Deine Daten wurden aktualisiert, aber du musst deine neue E-Mail-Adresse bestätigen. Du erhälst in wenigen Minuten eine E-Mail, mit der du die Änderung deiner E-Mail-Adresse abschließen kannst."
updated: "Deine Daten wurden aktualisiert."
sessions:
already_signed_out: "Erfolgreich abgemeldet."
signed_in: "Erfolgreich angemeldet."
signed_out: "Erfolgreich abgemeldet."
unlocks:
- send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Deinen Account entsperren können."
- send_paranoid_instructions: "Falls Deine E-Mail-Adresse in unserer Datenbank existiert erhältst Du in wenigen Minuten eine E-Mail mit der Anleitung, wie Du Deinen Account entsperren kannst."
+ send_instructions: "Du erhältst in wenigen Minuten eine E-Mail mit der Anleitung, wie du deinen Account entsperren können."
+ send_paranoid_instructions: "Falls deine E-Mail-Adresse in unserer Datenbank existiert erhältst du in wenigen Minuten eine E-Mail mit der Anleitung, wie du deinen Account entsperren kannst."
unlocked: "Dein Account wurde entsperrt. Du bist jetzt angemeldet."
errors:
messages:
- already_confirmed: "wurde bereits bestätigt"
- confirmation_period_expired: "muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an"
- expired: "ist abgelaufen, bitte neu anfordern"
- not_found: "nicht gefunden"
+ already_confirmed: "wurde bereits bestätigt."
+ confirmation_period_expired: "muss innerhalb %{period} bestätigt werden, bitte fordere einen neuen Link an."
+ expired: "ist abgelaufen, bitte neu anfordern."
+ not_found: "wurde nicht gefunden."
not_locked: "ist nicht gesperrt"
not_saved:
one: "Konnte %{resource} nicht speichern: ein Fehler."
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index 32ac92cf..b9204b59 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -17,7 +17,7 @@ en:
unconfirmed: You have to confirm your email address before continuing.
mailer:
confirmation_instructions:
- subject: 'Mastodon: Confirmation instructions'
+ subject: 'Mastodon: Confirmation instructions for %{instance}'
password_change:
subject: 'Mastodon: Password changed'
reset_password_instructions:
@@ -29,7 +29,7 @@ en:
success: Successfully authenticated from %{kind} account.
passwords:
no_token: You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided.
- send_instructions: You will receive an email with instructions on how to reset your password in a few minutes.
+ send_instructions: If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.
send_paranoid_instructions: If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes.
updated: Your password has been changed successfully. You are now signed in.
updated_not_active: Your password has been changed successfully.
diff --git a/config/locales/devise.eo.yml b/config/locales/devise.eo.yml
new file mode 100644
index 00000000..b786647d
--- /dev/null
+++ b/config/locales/devise.eo.yml
@@ -0,0 +1,61 @@
+---
+eo:
+ devise:
+ confirmations:
+ confirmed: Via konto estas konfirmita.
+ send_instructions: Vi ricevos instrukciojn por konfirmi vian konton post kelkaj minutoj.
+ send_paranoid_instructions: Se via retpoŝt-adreso ekzistas en nia datumbazo, vi baldaŭ ricevos retpoŝt-mesaĝon, kiu enhavas la instrukciojn por konfirmi vian konton.
+ failure:
+ already_authenticated: Vi jam estas ensalutita.
+ inactive: Via konto ankoraŭ ne estas konfirmita.
+ invalid: Malĝusta retpoŝt-adreso aŭ pasvorto.
+ last_attempt: Vi ankoraŭ povas provi unufoje antaŭ ol via konto estos ŝlosita.
+ locked: Via konto estas ŝlosita.
+ not_found_in_database: Malĝusta retpoŝt-adreso aŭ pasvorto.
+ timeout: Via sesio eksiĝis. Bonvolu reensaluti por daŭrigi.
+ unauthenticated: Vi devas ensaluti aŭ membriĝi por daŭrigi.
+ unconfirmed: Vi devas konfirmi vian konton por daŭrigi.
+ mailer:
+ confirmation_instructions:
+ subject: Instrukcioj por konfirmi
+ password_change:
+ subject: Via pasvorto estis ŝanĝita senprobleme.
+ reset_password_instructions:
+ subject: Instrukcioj por ŝanĝi la pasvorton
+ unlock_instructions:
+ subject: Instrukcioj por malŝlosi la konton
+ omniauth_callbacks:
+ failure: 'Ni ne povis aŭtentigi vin per %{kind}: ''%{reason}''.'
+ success: Aŭtentigita senprobleme per %{kind}.
+ passwords:
+ no_token: Vi ne povas iri al tiu paĝo per alia vojo ol retpoŝt-mesaĝo por ŝanĝi pasvorton. Se vi venas de tia retpoŝt-mesaĝo, kontrolu ke vi uzis la tutan URL.
+ send_instructions: Vi ricevos retpoŝt-mesaĝon kun instrukcioj por ŝanĝi vian pasvorton post kelkaj minutoj.
+ send_paranoid_instructions: Se via retpoŝt-adreso ekzistas en nia datumbazo, vi ricevos ligilon por ŝanĝi vian pasvorton per retpoŝt-mesaĝo.
+ updated: Via pasvorto estis redaktita senprobleme, vi nun estas ensalutita.
+ updated_not_active: Via pasvorto estis redaktita senprobleme.
+ registrations:
+ destroyed: Ĝis! Via konto estis forigita senprobleme. Ni esperas revidi vin baldaŭ.
+ signed_up: Bonvenon! Vi membriĝis senprobleme.
+ signed_up_but_inactive: Vi bone membriĝis, sed vi ankoraŭ ne povas ensaluti ĉar via konto ne estis konfirmita.
+ signed_up_but_locked: Vi bone membriĝis, sed vi ne povas ensaluti ĉar via konto estas ŝlosita.
+ signed_up_but_unconfirmed: Retpoŝt-mesaĝo kun via ligilo por konfirmi vian konton estis sendita al via retpoŝt-adreso. Bonvolu uzi tiun ligilon por konfirmi vian konton.
+ update_needs_confirmation: Vi bone aktualigis vian konton, sed ni bezonas kontroli vian novan retpoŝt-adreson. Bonvolu kontroli viajn retpoŝt-mesaĝojn kaj uzi la ligilon por konfirmi vian novan retpoŝt-adreson.
+ updated: Via konto estis aktualigita senprobleme.
+ sessions:
+ already_signed_out: Elsalutita.
+ signed_in: Ensalutita.
+ signed_out: Elsalutita.
+ unlocks:
+ send_instructions: Vi ricevos retpoŝt-mesaĝon kun instrukcioj por malŝlosi vian konton post kelkaj minutoj.
+ send_paranoid_instructions: Se via retpoŝt-adreso ekzistas en nia datumbazo, vi ricevos ligilon por malŝlosi vian konton per retpoŝt-mesaĝo.
+ unlocked: Via konto estis malŝlosita senprobleme, vi nun estas ensalutita.
+ errors:
+ messages:
+ already_confirmed: jam estis konfirmita, bonvolu provi ensaluti
+ confirmation_period_expired: devas esti konfirmita en %{period}, bonvolu repeti
+ expired: eksiĝis, bonvolu repeti
+ not_found: ne estis trovita
+ not_locked: ne estis ŝlosita
+ not_saved:
+ one: '1 eraro malpermesis al tiu %{resource} esti konservita:'
+ other: '%{count} eraroj malpermesis al tiu %{resource} esti konservita:'
diff --git a/config/locales/devise.fi.yml b/config/locales/devise.fi.yml
new file mode 100644
index 00000000..bf4f530d
--- /dev/null
+++ b/config/locales/devise.fi.yml
@@ -0,0 +1,61 @@
+---
+fi:
+ devise:
+ confirmations:
+ confirmed: Sähköpostisi on onnistuneesti vahvistettu.
+ send_instructions: Saat kohta sähköpostiisi ohjeet kuinka voit aktivoida tilisi.
+ send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet sen varmentamiseen.
+ failure:
+ already_authenticated: Olet jo kirjautunut sisään.
+ inactive: Tiliäsi ei ole viellä aktivoitu.
+ invalid: Virheellinen %{authentication_keys} tai salasana.
+ last_attempt: Sinulla on yksi yritys jäljellä tai tili lukitaan.
+ locked: Tili on lukittu.
+ not_found_in_database: Virheellinen %{authentication_keys} tai salasana.
+ timeout: Sessiosi on umpeutunut. Kirjaudu sisään jatkaaksesi.
+ unauthenticated: Sinun tarvitsee kirjautua sisään tai rekisteröityä jatkaaksesi.
+ unconfirmed: Sinun tarvitsee varmentaa sähköpostisi jatkaaksesi.
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: Varmistus ohjeet'
+ password_change:
+ subject: 'Mastodon: Salasana vaihdettu'
+ reset_password_instructions:
+ subject: 'Mastodon: Salasanan vaihto ohjeet'
+ unlock_instructions:
+ subject: 'Mastodon: Avauksen ohjeet'
+ omniauth_callbacks:
+ failure: Varmennus %{kind} epäonnistui koska "%{reason}".
+ success: Onnistuneesti varmennettu %{kind} tilillä.
+ passwords:
+ no_token: Et pääse tälle sivulle ilman salasanan vaihto sähköpostia. Jos tulet tämmöisestä postista, varmista että sinulla on täydellinen URL.
+ send_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen.
+ send_paranoid_instructions: Jos sähköpostisi on meidän tietokannassa, saat pian ohjeet salasanan palautukseen.
+ updated: Salasanasi vaihdettu onnistuneesti. Olet nyt kirjautunut sisään.
+ updated_not_active: Salasanasi vaihdettu onnistuneesti.
+ registrations:
+ destroyed: Näkemiin! Tilisi on onnistuneesti peruttu. Toivottavasti näemme joskus uudestaan.
+ signed_up: Tervetuloa! Rekisteröitymisesi onnistu.
+ signed_up_but_inactive: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tiliäsi ei ole viellä aktivoitu.
+ signed_up_but_locked: Olet onnistuneesti rekisteröitynyt, mutta emme voi kirjata sinua sisään koska tilisi on lukittu.
+ signed_up_but_unconfirmed: Varmistuslinkki on lähetty sähköpostiisi. Seuraa sitä jotta tilisi voidaan aktivoida.
+ update_needs_confirmation: Tilisi on onnistuneesti päivitetty, mutta meidän tarvitsee vahvistaa sinun uusi sähköpostisi. Tarkista sähköpostisi ja seuraa viestissä tullutta linkkiä varmistaaksesi uuden osoitteen..
+ updated: Tilisi on onnistuneesti päivitetty.
+ sessions:
+ already_signed_out: Ulos kirjautuminen onnistui.
+ signed_in: Sisäänkirjautuminen onnistui.
+ signed_out: Ulos kirjautuminen onnistui.
+ unlocks:
+ send_instructions: Saat sähköpostiisi pian ohjeet, jolla voit avata tilisi uudestaan.
+ send_paranoid_instructions: Jos tilisi on olemassa, saat sähköpostiisi pian ohjeet tilisi avaamiseen.
+ unlocked: Tilisi on avattu onnistuneesti. Kirjaudu normaalisti sisään.
+ errors:
+ messages:
+ already_confirmed: on jo varmistettu. Yritä kirjautua sisään
+ confirmation_period_expired: pitää varmistaa %{period} sisällä, ole hyvä ja pyydä uusi
+ expired: on erääntynyt, ole hyvä ja pyydä uusi
+ not_found: ei löydy
+ not_locked: ei ollut lukittu
+ not_saved:
+ one: '1 virhe esti %{resource} tallennuksen:'
+ other: "%{count} virhettä esti %{resource} tallennuksen:"
diff --git a/config/locales/devise.fr.yml b/config/locales/devise.fr.yml
index ce44d041..d1dae7c3 100644
--- a/config/locales/devise.fr.yml
+++ b/config/locales/devise.fr.yml
@@ -12,32 +12,32 @@ fr:
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.
- timeout: Votre session est expirée. Veuillez vous reconnecter pour continuer.
+ 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.
mailer:
confirmation_instructions:
- subject: Instructions de confirmation
+ subject: "Merci de confirmer votre inscription sur %{instance}"
password_change:
subject: Votre mot de passe a été modifié avec succés.
reset_password_instructions:
- subject: Instructions pour changer le mot de passe
+ subject: Instructions pour changer votre mot de passe
unlock_instructions:
- subject: Instructions pour déverrouiller le compte
+ subject: Instructions pour déverrouiller votre compte
omniauth_callbacks:
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é par un e-mail de ce type, assurez-vous d'utiliser l'URL complète.
+ 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.
send_instructions: Vous allez recevoir les instructions de réinitialisation du mot de passe dans quelques instants
send_paranoid_instructions: Si votre e-mail existe dans notre base de données, vous allez recevoir un lien de réinitialisation par e-mail
- updated: Votre mot de passe a été édité avec succès, vous êtes maintenant connecté
- updated_not_active: Votre mot de passe a été changé avec succès.
+ updated: Votre mot de passe a été modifié avec succès, vous êtes maintenant connecté⋅e
+ updated_not_active: Votre mot de passe a été modifié avec succès.
registrations:
destroyed: Votre compte a été supprimé avec succès. Nous espérons vous revoir bientôt.
- signed_up: Bienvenue, vous êtes connecté.
- signed_up_but_inactive: Vous êtes bien enregistré. Vous ne pouvez cependant pas vous connecter car votre compte n'est pas encore activé.
- signed_up_but_locked: Vous êtes bien enregistré. Vous ne pouvez cependant pas vous connecter car votre compte est verrouillé.
+ 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_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.
updated: Votre compte a été modifié avec succès.
@@ -48,14 +48,14 @@ fr:
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.
- unlocked: Votre compte a été déverrouillé avec succès, vous êtes maintenant connecté.
+ 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
+ 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)
+ 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é ce(tte) %{resource} d''être sauvegardé(e): '
+ 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.hr.yml b/config/locales/devise.hr.yml
new file mode 100644
index 00000000..40e0effc
--- /dev/null
+++ b/config/locales/devise.hr.yml
@@ -0,0 +1,60 @@
+---
+hr:
+ devise:
+ confirmations:
+ already_authenticated: Već si prijavljen.
+ confirmed: Tvoja email adresa je uspješno potvrđena.
+ inactive: Tvoj račun još nije aktiviran.
+ invalid: Nevaljan %{authentication_keys} ili lozinka.
+ last_attempt: Imaš još jedan pokušaj prije no što ti se račun zaključa.
+ locked: Tvoj račun je zaključan.
+ not_found_in_database: Nevaljani %{authentication_keys} ili lozinka.
+ send_instructions: Primit ćeš email sa uputama kako potvrditi svoju email adresu za nekoliko minuta.
+ send_paranoid_instructions: Ako tvoja email adresa postoji u našoj bazi podataka, primit ćeš email sa uputama kako ju potvrditi za nekoliko minuta.
+ timeout: Tvoja sesija je istekla. Molimo te, prijavi se ponovo kako bi nastavio.
+ unauthenticated: Moraš se registrirati ili prijaviti prije no što nastaviš.
+ unconfirmed: Moraš potvrditi svoju email adresu prije no što nastaviš.
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: Upute za potvrđivanje'
+ password_change:
+ subject: 'Mastodon: Lozinka je promijenjena'
+ reset_password_instructions:
+ subject: 'Mastodon: Upute za resetiranje lozinke'
+ unlock_instructions:
+ subject: 'Mastodon: Upute za otključavanje'
+ omniauth_callbacks:
+ failure: Ne možemo te autentificirati sa %{kind} zbog "%{reason}".
+ success: Uspješno autentificiran sa %{kind} računa.
+ passwords:
+ no_token: Ne možeš pristupiti ovoj stranici bez dolaženja sa emaila za resetiranje lozinke. Ako dolaziš sa tog emaila, pazi da koristiš potpuni link koji ti je dan.
+ send_instructions: Primit ćeš email sa uputama kako resetirati svoju lozinku za nekoliko minuta.
+ send_paranoid_instructions: Ako tvoja email adresa postoji u našoj bazi podataka, primit ćeš link za povrat lozinke na svoju email adresu za nekoliko minuta.
+ updated: Tvoja lozinka je uspješno izmijenjena. Sada si prijavljen.
+ updated_not_active: Toja lozinka je uspješno izmijenjena.
+ registrations:
+ destroyed: Zbogom! Tvoj račun je uspješno otkazan. Nadamo se da ćemo te vidjeti ponovo.
+ signed_up: Dobro došao! Uspješno si se prijavio.
+ signed_up_but_inactive: Uspješno si se registrirao. No, ne možeš se prijaviti, jer ti račun još nije aktiviran.
+ signed_up_but_locked: Uspješno si se registrirao. No, ne možeš se prijaviti jer je tvoj račun zaključan.
+ signed_up_but_unconfirmed: Poruka sa linkom za potvrđivanje je poslana na tvoju email adresu. Molimo, slijedi link kako bi tvoj račun bio aktiviran.
+ update_needs_confirmation: Tvoj račun je uspješno ažuriran, ali trebamo provjeriti tvoju novu email adresu. Molimo, provjeri svoj email i slijedi link za potvrđivanje kako bi tvoja nova email adresa bila potvrđena.
+ updated: Tvoj račun je uspješno ažuriran.
+ sessions:
+ already_signed_out: Uspješno si odjavljen.
+ signed_in: Uspješno si prijavljen.
+ signed_out: Uspješno si odjavljen.
+ unlocks:
+ send_instructions: Primit ćeš email sa uputama kako otključati svoj račun za nekoliko minuta.
+ send_paranoid_instructions: Ako tvoj račun postoji, primit ćeš email sa uputama kako ga otključati za nekoliko minuta.
+ unlocked: Tvoj račun je uspješno otključan. Prijavi se kako bi nastavio.
+ errors:
+ messages:
+ already_confirmed: je već potvrđen, pokušaj se prijaviti
+ confirmation_period_expired: mora biti potvrđen u roku od %{period}, molimo zatraži novi
+ expired: je istekao, zatraži novu
+ not_found: nije nađen
+ not_locked: nije zaključan
+ not_saved:
+ one: '1 greška je zabranila da ovaj %{resource} bude sačuvan:'
+ other: "%{count} greške su zabranile da ovaj %{resource} bude sačuvan:"
diff --git a/config/locales/devise.ja.yml b/config/locales/devise.ja.yml
new file mode 100644
index 00000000..4aeb09cd
--- /dev/null
+++ b/config/locales/devise.ja.yml
@@ -0,0 +1,61 @@
+---
+ja:
+ devise:
+ confirmations:
+ confirmed: メールアドレスの確認が正常に完了しました。
+ send_instructions: まもなくメールアドレスの確認の方法が記載されたメールが送信されます。
+ send_paranoid_instructions: もしあなたのメールアドレスが登録されていれば、まもなくメールアドレスの確認の方法が記載されたメールが送信されます。
+ failure:
+ already_authenticated: 既にログイン済みです。
+ inactive: あなたのアカウントはまだアクティベートされていません。
+ invalid: '%{authentication_keys}かパスワードが誤っています'
+ last_attempt: あと1回失敗するとアカウントがロックされます。
+ 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: '%{reason}によって%{kind}からのアクセスを認証できませんでした。'
+ success: '%{kind}からのアクセスは正常に認証されました。'
+ passwords:
+ no_token: パスワード再発行のメール以外からこのページにアクセスすることはできません。 パスワード再発行のメールからアクセスしたのにもかかわらずこのメッセージが表示される場合は、アクセスしたURLが間違っていないか確認してください。
+ send_instructions: パスワード再発行の方法が記載されたメールが間もなく送信されます。
+ send_paranoid_instructions: メールアドレスが登録済みであれば、パスワードをリセットするリンクが記載されたメールがあなたのメールアドレスに送信されます。
+ updated: パスワードは正常に更新されました。なお、既にログイン済みです。
+ updated_not_active: パスワードは正常に更新されました。
+ registrations:
+ destroyed: アカウントの作成はキャンセルされました。またのご利用をお待ちしています。
+ signed_up: アカウントの作成が完了しました。Mastodonへようこそ!
+ 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: 'エラーが発生したため、%{resource}の保存に失敗しました。'
+ other: "%{count}個のエラーが発生したため、保存に失敗しました。 %{resource}"
diff --git a/config/locales/devise.nl.yml b/config/locales/devise.nl.yml
new file mode 100644
index 00000000..9057a677
--- /dev/null
+++ b/config/locales/devise.nl.yml
@@ -0,0 +1,59 @@
+---
+nl:
+ devise:
+ confirmations:
+ confirmed: Je account is bevestigd.
+ send_instructions: Je ontvangt via e-mail instructies hoe je je account kan bevestigen.
+ send_paranoid_instructions: Als je e-mailadres bestaat in de database, ontvang je via e-mail instructies hoe je je account kan bevestigen.
+ failure:
+ already_authenticated: Je bent al ingelogd.
+ inactive: Je account is nog niet geactiveerd.
+ invalid: Ongeldig e-mail of wachtwoord.
+ invalid_token: Invalide authenticiteit token.
+ last_attempt: Je hebt nog een poging over voordat je account wordt geblokkeerd.
+ locked: Je account is gelocked.
+ not_found_in_database: Ongeldig e-mail of wachtwoord.
+ timeout: Je sessie is verlopen, log a.u.b. opnieuw in.
+ unauthenticated: Je dient in te loggen of je in te schrijven.
+ unconfirmed: Je dient eerst je account te bevestigen.
+ mailer:
+ confirmation_instructions:
+ subject: Bevestiging mailadres
+ reset_password_instructions:
+ subject: Wachtwoord resetten
+ unlock_instructions:
+ subject: Unlock instructies
+ omniauth_callbacks:
+ failure: Kon je niet aanmelden met je %{kind} account, omdat "%{reason}".
+ success: Successvol aangemeld met je %{kind} account.
+ passwords:
+ no_token: Je kan deze pagina niet benaderen zonder een "wachtwoord reset e-mail"
+ send_instructions: Je ontvangt via e-mail instructies hoe je je wachtwoord moet resetten.
+ send_paranoid_instructions: Als je e-mailadres bestaat in de database, ontvang je via e-mail instructies hoe je je wachtwoord moet resetten.
+ updated: Je wachtwoord is gewijzigd. Je bent nu ingelogd.
+ updated_not_active: Je wachtwoord is gewijzigd.
+ registrations:
+ destroyed: Je account is verwijderd, wellicht tot ziens!
+ signed_up: Je bent ingeschreven.
+ signed_up_but_inactive: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat je account nog niet geactiveerd is.
+ signed_up_but_locked: Je bent ingeschreven. Je kon alleen niet automatisch ingelogd worden omdat je account geblokkeerd is.
+ signed_up_but_unconfirmed: Je ontvangt via e-mail instructies hoe je je account kunt activeren.
+ update_needs_confirmation: Je hebt je e-mailadres succesvol gewijzigd, maar we moeten je nieuwe mailadres nog verifiëren. Controleer je e-mail en klik op de link in de mail om je mailadres te verifiëren.
+ updated: Je account gegevens zijn opgeslagen.
+ sessions:
+ signed_in: Je bent succesvol ingelogd.
+ signed_out: Je bent succesvol uitgelogd.
+ unlocks:
+ send_instructions: Je ontvangt via e-mail instructies hoe je je account kan unlocken.
+ send_paranoid_instructions: Als je e-mailadres bestaat in de database, ontvang je via e-mail instructies hoe je je account kan unlocken.
+ unlocked: Je account is ge-unlocked. Je kan nu weer inloggen.
+ errors:
+ messages:
+ already_confirmed: is reeds bevestigd
+ confirmation_period_expired: moet worden bevestigd binnen %{period}, probeer het a.u.b. nog een keer
+ expired: is verlopen, vraag een nieuwe aan
+ not_found: niet gevonden
+ not_locked: is niet gesloten
+ not_saved:
+ one: '1 fout blokkeerde het opslaan van deze %{resource}:'
+ other: "%{count} fouten blokkeerden het opslaan van deze %{resource}:"
diff --git a/config/locales/devise.no.yml b/config/locales/devise.no.yml
new file mode 100644
index 00000000..8b650e54
--- /dev/null
+++ b/config/locales/devise.no.yml
@@ -0,0 +1,61 @@
+---
+'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.
+ 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.
+ 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.
+ unauthenticated: Du må logge inn eller registrere deg før du kan fortsette.
+ unconfirmed: Du må bekrefte epostadressen din før du kan fortsette.
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: Instruksjoner for å bekrefte epostadresse'
+ password_change:
+ subject: 'Mastodon: Passord endret'
+ reset_password_instructions:
+ 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.
+ 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.
+ 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.
+ 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.
+ 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:"
diff --git a/config/locales/devise.ru.yml b/config/locales/devise.ru.yml
new file mode 100644
index 00000000..f829f9d8
--- /dev/null
+++ b/config/locales/devise.ru.yml
@@ -0,0 +1,61 @@
+---
+ru:
+ devise:
+ confirmations:
+ confirmed: Ваш адрес e-mail был успешно подтвержден.
+ send_instructions: Вы получите e-mail с инструкцией по подтверждению Вашего адреса e-mail в течение нескольких минут.
+ send_paranoid_instructions: Если Ваш адрес e-mail есть в нашей базе данных, вы получите e-mail с инструкцией по подтверждению Вашего адреса в течение нескольких минут.
+ failure:
+ already_authenticated: Вы уже авторизованы.
+ inactive: Ваш аккаунт еще не активирован.
+ invalid: Неверно введены %{authentication_keys} или пароль.
+ last_attempt: У Вас есть последняя попытка, после чего вход будет заблокирован.
+ locked: Ваш аккаунт заблокирован.
+ not_found_in_database: Неверно введены %{authentication_keys} или пароль.
+ timeout: Ваша сессия истекла. Пожалуйста, войдите снова, чтобы продолжить.
+ unauthenticated: Вам необходимо войти или зарегистрироваться.
+ unconfirmed: Вам необходимо подтвердить ваш адрес e-mail для продолжения.
+ 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: Вы можете получить доступ к этой странице, только перейдя по ссылке в e-mail для сброса пароля. Если Вы действительно перешли по такой ссылке, пожалуйста, удостоверьтесь, что ссылка была введена полностью и без изменений.
+ send_instructions: Вы получите e-mail с инструкцией по сбросу пароля в течение нескольких минут.
+ send_paranoid_instructions: Если Ваш адрес e-mail есть в нашей базе данных, Вы получите e-mail со ссылкой для сброса пароля в течение нескольких минут.
+ updated: Ваш пароль был успешно изменен. Вход выполнен.
+ updated_not_active: Ваш пароль был успешно изменен.
+ registrations:
+ destroyed: До свидания! Ваш аккаунт был успешно удален. Мы надеемся скоро увидеть Вас снова.
+ signed_up: Добро пожаловать! Вы успешно зарегистрировались.
+ signed_up_but_inactive: Вы успешно зарегистрировались. Тем не менее, мы не можем авторизовать Вас, поскольку Ваш аккаунт еще не активирован.
+ signed_up_but_locked: Вы успешно зарегистрировались. Тем не менее, мы не можем авторизовать Вас, поскольку Ваш аккаунт заблокирован.
+ signed_up_but_unconfirmed: Сообщение со ссылкой для подтверждения было выслано на Ваш адрес e-mail. Пожалуйста, пройдите по ссылке для активации Вашего аккаунта.
+ update_needs_confirmation: Вы успешно обновили Ваш аккаунт, но нам нужно подтвердить ваш новый адрес e-mail. Пожалуйста, проверьте почту и пройдите по ссылке для подтверждения Вашего нового адреса.
+ updated: Ваш аккаунт был успешно обновлен.
+ sessions:
+ already_signed_out: Выход прошел успешно.
+ signed_in: Вход прошел успешно.
+ signed_out: Выход прошел успешно.
+ unlocks:
+ send_instructions: Вы получите e-mail с инструкцией по разблокировке Вашего аккаунта в течение нескольких минут.
+ send_paranoid_instructions: Если Ваш аккаунт существует, Вы получите e-mail с инструкцией по его разблокировке в течение нескольких минут.
+ 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/devise.zh-HK.yml b/config/locales/devise.zh-HK.yml
new file mode 100644
index 00000000..cecd4007
--- /dev/null
+++ b/config/locales/devise.zh-HK.yml
@@ -0,0 +1,61 @@
+---
+zh-HK:
+ 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.bg.yml b/config/locales/doorkeeper.bg.yml
new file mode 100644
index 00000000..6fafdfc5
--- /dev/null
+++ b/config/locales/doorkeeper.bg.yml
@@ -0,0 +1,113 @@
+---
+bg:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Име
+ redirect_uri: URI за пренасочване
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: не може да съдържа фрагмент.
+ 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: Разделяй диапазоните с интервал. Остави празно, за да използваш диапазона по подразбиране.
+ index:
+ callback_url: URL за обратно повикване
+ name: Име
+ new: Ново приложение
+ title: Твоите приложения
+ new:
+ title: Ново приложение
+ show:
+ actions: Действия
+ application_id: Идентификатор на приложението
+ callback_urls: URL-и за обратно повикване
+ 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: Resource Owner Password Credentials предизвика грешка, заради това, че настройките за Doorkeeper.configure.resource_owner_from_credentials липсват.
+ invalid_client: Удостоверяването на клиента предизвика грешка, поради непознат клиент, липсващо клиентско удостоверяване, или заради това, че методът на удостоверяване не се поддържа.
+ invalid_grant: Предоставеното удостоверение за достъп е невалидно, изтекло, отхвърлено, не съвпада с пренасочващото URI, използвано в заявката за удостоверение, или е бил издадено от друг клиент.
+ invalid_redirect_uri: Наличното пренасочващо URI е невалидно.
+ invalid_request: Заявката е с липсващ задължителен параметър, включва стойност на параметъра, която не се поддържа, или е изкривена по друг начин.
+ invalid_resource_owner: Предоставените идентификационни данни на притежателя на ресурса са невалидни, или притежателят не може да бъде намерен.
+ invalid_scope: Заявеният диапазон е невалиден, неизвестен или изкривен.
+ invalid_token:
+ expired: Маркерът за достъп изтече
+ revoked: Маркерът за достъп беше отхвърлен
+ unknown: Маркерът за достъп е невалиден
+ resource_owner_authenticator_not_configured: Намирането на Resource Owner се провали поради липса на конфигурация на 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/doorkeeper.eo.yml b/config/locales/doorkeeper.eo.yml
new file mode 100644
index 00000000..33cc7cc1
--- /dev/null
+++ b/config/locales/doorkeeper.eo.yml
@@ -0,0 +1,113 @@
+---
+eo:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Nomo
+ redirect_uri: URI de plusendo
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: ne povas enhavi eron.
+ invalid_uri: devas esti valida URI.
+ relative_uri: devas esti absoluta URI.
+ secured_uri: devas esti HTTPS/SSL-a URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Rajtigi
+ cancel: Rezigni
+ destroy: Detrui
+ edit: Redakti
+ submit: Sendi
+ confirmations:
+ destroy: Ĉu vi certas?
+ edit:
+ title: Redakti aplikaĵon
+ form:
+ error: Ups! Kontrolu vian formularon ĉu estas eraroj
+ help:
+ native_redirect_uri: Uzu %{native_redirect_uri} por lokaj provoj
+ redirect_uri: Uzu unu linion por ĉiu URI
+ scopes: Apartigu ampleksojn per spacetoj. Lasu malplena por uzi la senŝanĝajn ampleksojn.
+ index:
+ callback_url: URL vokita per referenco
+ name: Nomo
+ new: Nova Aplikaĵo
+ title: Viaj aplikaĵoj
+ new:
+ title: Nova aplikaĵo
+ show:
+ actions: Agoj
+ application_id: Identigo de la aplikaĵo
+ callback_urls: URL-j vokitaj per referenco
+ scopes: Ampleksoj
+ secret: Sekreto
+ title: 'Aplikaĵo: %{name}'
+ authorizations:
+ buttons:
+ authorize: Rajtigi
+ deny: Rifuzi
+ error:
+ title: Eraro okazis
+ new:
+ able_to: Povos
+ prompt: La aplikaĵo %{client_name} petas aliron al via konto
+ title: Rajtigo bezonata
+ show:
+ title: Rajtiga kodo
+ authorized_applications:
+ buttons:
+ revoke: Malrajtigi
+ confirmations:
+ revoke: Ĉu vi certas?
+ index:
+ application: Aplikaĵo
+ created_at: Rajtigita
+ date_format: "%Y-%m-%d %H:%M:%S"
+ scopes: Ampleksoj
+ title: Viaj rajtigitaj aplikaĵoj
+ errors:
+ messages:
+ access_denied: La posedanto de la rimedo aŭ la rajtiga servilo rifuzis vian peton.
+ credential_flow_not_configured: La sendado de la identigiloj de la posedanto de la rimedo malsukcesis ĉar Doorkeeper.configure.resource_owner_from_credentials ne estis agordita.
+ invalid_client: La aŭtentigo de la kliento malsukcesis ĉar la kliento estas nekonata, aŭ mankis peto aŭtentigi, aŭ la aŭtentig-metodo ne estas subtenata.
+ invalid_grant: La rajtiga konsento ne estas valida, ne plu estas valida, estis forigita, ne kongruas kun la plusenda URI uzita en la aŭtentiga peto, aŭ estis sendita al alia kliento.
+ invalid_redirect_uri: La plusenda URI uzita en estas valida.
+ invalid_request: Mankis al la peto nepra parametro, enhavas nesubtenatan parametran valoron, aŭ la peto simple estas misformita.
+ invalid_resource_owner: La donitaj identigiloj pri la posedanto de la rimedo ne estas validaj, aŭ tiu ne povas esti trovita.
+ invalid_scope: La petita amplekso ne estas valida, estas nekonata, aŭ estas misformita.
+ invalid_token:
+ expired: La atingoĵetono eskiĝis.
+ revoked: La atingoĵetono estis rifuzita.
+ unknown: La atingoĵetono ne estas valida.
+ resource_owner_authenticator_not_configured: La posedanto de la rimedo ne povis esti trovita ĉar Doorkeeper.configure.resource_owner_authenticator ne estas agordita.
+ server_error: La rajtiga servilo rimarkis neatenditan kondiĉon, kiu malpermesis al ĝi plenumi la peton.
+ temporarily_unavailable: La rajtiga servilo ne povas nun plenumi la peton pro dumtempa superŝarĝo aŭ prizorgado de la servilo.
+ unauthorized_client: La kliento ne rajtas fari tian peton uzante tiun metodon.
+ unsupported_grant_type: La tipo de la rajtiga konsento ne estas subtenata de la rajtiga servilo.
+ unsupported_response_type: La rajtiga servilo ne subtenas tian respondon.
+ flash:
+ applications:
+ create:
+ notice: Aplikaĵo kreita.
+ destroy:
+ notice: Aplikaĵo forigita.
+ update:
+ notice: Aplikaĵo aktualigita.
+ authorized_applications:
+ destroy:
+ notice: Aplikaĵo malrajtigita.
+ layouts:
+ admin:
+ nav:
+ applications: Aplikaĵoj
+ oauth2_provider: OAuth2-provizanto
+ application:
+ title: OAuth-a rajtigo bezonata
+ scopes:
+ follow: sekvi, bloki, malbloki kaj malsekvi kontojn
+ read: legi la datumojn de via konto
+ write: mesaĝi kiel vi
diff --git a/config/locales/doorkeeper.fi.yml b/config/locales/doorkeeper.fi.yml
new file mode 100644
index 00000000..cd1a9d05
--- /dev/null
+++ b/config/locales/doorkeeper.fi.yml
@@ -0,0 +1,113 @@
+---
+fi:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Nimi
+ redirect_uri: Uudelleenohjaus URI
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: ei voi sisältää osia.
+ invalid_uri: pitää olla validi URI.
+ relative_uri: pitää olla täydellinen URI.
+ secured_uri: pitää olla HTTPS/SSL URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Valtuuta
+ cancel: Peruuta
+ destroy: Tuhoa
+ edit: Muokkaa
+ submit: Lähetä
+ confirmations:
+ destroy: Oletko varma?
+ edit:
+ title: Muokkaa applikaatiota
+ form:
+ error: Whoops! Tarkista lomakkeesi mahdollisten virheiden varalta
+ help:
+ native_redirect_uri: Käytä %{native_redirect_uri} paikallisiin testeihin
+ redirect_uri: Käytä yhtä riviä per URI
+ scopes: Erota scopet välilyönnein. Jätä tyhjäksi käyteksi oletus scopeja.
+ index:
+ callback_url: Callback URL
+ name: Nimi
+ new: Uusi applikaatio
+ title: Sinun applikaatiosi
+ new:
+ title: Uusi applikaatio
+ show:
+ actions: Toiminnot
+ application_id: Applikaation Id
+ callback_urls: Callback urls
+ scopes: Scopet
+ secret: Salainen avain
+ title: 'Applikaatio: %{name}'
+ authorizations:
+ buttons:
+ authorize: Valtuuta
+ deny: Evää
+ error:
+ title: Virhe on tapahtunut
+ new:
+ able_to: Se voi
+ prompt: Applikaatio %{client_name} pyytää lupaa tilillesi
+ title: Valtuutus vaaditaan
+ show:
+ title: Valtuutus koodi
+ authorized_applications:
+ buttons:
+ revoke: Evää
+ confirmations:
+ revoke: Oletko varma?
+ index:
+ application: Applikaatio
+ created_at: Valtuutettu
+ date_format: "%Y-%m-%d %H:%M:%S"
+ scopes: Scopet
+ title: Valtuuttamasi applikaatiot
+ errors:
+ messages:
+ access_denied: Resurssin omistaja tai valtuutus palvelin hylkäsi pyynnönr.
+ credential_flow_not_configured: Resurssin omistajan salasana epäonnistui koska Doorkeeper.configure.resource_owner_from_credentials ei ole konfiguroitu.
+ invalid_client: Asiakkaan valtuutus epäonnistui koska tuntematon asiakas, asiakas ei sisältänyt valtuutusta, tai tukematon valtuutus tapa
+ invalid_grant: Antamasi valtuutus lupa on joko väärä, erääntynyt, peruttu, ei vastaa uudelleenohjaus URI jota käytetään valtuutus pyynnössä, tai se myönnettin toiselle asiakkaalle.
+ invalid_redirect_uri: Uudelleenohjaus uri ei ole oikein.
+ invalid_request: Pyynnöstä puutti parametri, sisältää tukemattoman parametri arvonn, tai on korruptoitunut.
+ invalid_resource_owner: Annetut resurssin omistajan tunnnukset ovat väärät, tai resurssin omistajaa ei löydy
+ invalid_scope: Pyydetty scope on väärä, tuntemat, tai korruptoitunut.
+ invalid_token:
+ expired: Access token vanhentunut
+ revoked: Access token evätty
+ unknown: Access token väärä
+ resource_owner_authenticator_not_configured: Resurssin omistajan etsiminen epäonnistui koska Doorkeeper.configure.resource_owner_authenticator ei ole konfiguroitu.
+ server_error: Valtuutus palvelin kohtasi odottamattoman virheen joka esti sitä täyttämästä pyyntöä.
+ temporarily_unavailable: Valtuutus palvelin ei voi tällä hetkellä käsitellä pyyntöäsi joko väliaikaisen ruuhkan tai huollon takia.
+ unauthorized_client: Asiakas ei ole valtuutettu tekemään tätä pyyntöä käyttäen tätä metodia.
+ unsupported_grant_type: Valtuutus grant type ei ole tuettu valtuutus palvelimella.
+ unsupported_response_type: Valtuutus palvelin ei tue tätä vastaus tyyppiä.
+ flash:
+ applications:
+ create:
+ notice: Applikaatio luotu.
+ destroy:
+ notice: Applikaatio poistettu.
+ update:
+ notice: Applikaatio päivitetty.
+ authorized_applications:
+ destroy:
+ notice: Applikaatio tuhottu.
+ layouts:
+ admin:
+ nav:
+ applications: Applikaatiot
+ oauth2_provider: OAuth2 Provider
+ application:
+ title: OAuth valtuutus tarvitaan
+ scopes:
+ follow: seuraa, estä, peru esto ja lopeta tilien seuraaminen
+ read: lukea tilin dataa
+ write: julkaista puolestasi
diff --git a/config/locales/doorkeeper.fr.yml b/config/locales/doorkeeper.fr.yml
index c94e5c09..20ea7a9d 100644
--- a/config/locales/doorkeeper.fr.yml
+++ b/config/locales/doorkeeper.fr.yml
@@ -1,12 +1,25 @@
---
fr:
+ activemodel:
+ errors:
+ models:
+ remote_follow:
+ attributes:
+ acct:
+ blank: Le nom d'utilisateur ne doit pas être vide
activerecord:
attributes:
doorkeeper/application:
name: Nom
redirect_uri: L'URL de redirection
errors:
+ messages:
+ record_invalid: Données invalides
models:
+ account:
+ attributes:
+ username:
+ blank: Identifiant vide
doorkeeper/application:
attributes:
redirect_uri:
@@ -14,6 +27,17 @@ fr:
invalid_uri: doit être une URL valide.
relative_uri: doit être une URL absolue.
secured_uri: doit être une URL HTTP/SSL.
+ user:
+ attributes:
+ email:
+ blank: Email vide
+ invalid: Email invalide
+ taken: Email pris
+ password:
+ blank: Mot de passe vide
+ too_short: Mot de passe trop court
+ password_confirmation:
+ confirmation: Le mot de passe ne correspond pas
doorkeeper:
applications:
buttons:
@@ -23,11 +47,11 @@ fr:
edit: Modifier
submit: Envoyer
confirmations:
- destroy: Êtes-vous certain?
+ destroy: Êtes-vous certain ?
edit:
title: Modifier l'application
form:
- error: Oups! Vérifier votre formulaire pour des erreurs possibles
+ error: Oups ! Vérifier votre formulaire pour des erreurs possibles
help:
native_redirect_uri: Utiliser %{native_redirect_uri} pour les tests locaux
redirect_uri: Utiliser une ligne par URL
@@ -54,7 +78,7 @@ fr:
title: Une erreur est survenue
new:
able_to: Cette application pourra
- prompt: Autoriser %{client_name} à utiliser votre compte?
+ prompt: Autoriser %{client_name} à utiliser votre compte ?
title: Autorisation requise
show:
title: Code d'autorisation
@@ -62,7 +86,7 @@ fr:
buttons:
revoke: Annuler
confirmations:
- revoke: Êtes-vous certain?
+ revoke: Êtes-vous certain ?
index:
application: Application
created_at: Créé le
@@ -72,19 +96,19 @@ fr:
errors:
messages:
access_denied: Le propriétaire de la ressource ou le serveur d'autorisation a refusé la demande.
- credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué en raison de Doorkeeper.configure.resource_owner_from_credentials n'est pas configuré.
+ credential_flow_not_configured: Le flux des identifiants du mot de passe du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_from_credentials n'est pas configuré.
invalid_client: L'authentification du client a échoué à cause d'un client inconnu, d'aucune authentification de client incluse, ou d'une méthode d'authentification non prise en charge.
invalid_grant: Le consentement d'autorisation accordé n'est pas valide, a expiré, est annulé, ne concorde pas avec l'URL de redirection utilisée dans la demande d'autorisation, ou a été émis à un autre client.
invalid_redirect_uri: L'URL de redirection n'est pas valide.
invalid_request: La demande manque un paramètre requis, inclut une valeur de paramètre non prise en charge, ou est autrement mal formée.
- invalid_resource_owner: Les identifiants fournis du propriétaire de la ressource ne sont pas valides, ou le propriétaire de la ressource ne peut être trouvé
+ invalid_resource_owner: Les identifiants fournis par le propriétaire de la ressource ne sont pas valides, ou le propriétaire de la ressource ne peut être trouvé
invalid_scope: La portée demandée n'est pas valide, est inconnue, ou est mal formée.
invalid_token:
expired: Le jeton d'accès a expiré
revoked: Le jeton d'accès a été révoqué
unknown: Le jeton d'accès n'est pas valide
- resource_owner_authenticator_not_configured: La recherche du propriétaire de la ressource a échoué en raison de Doorkeeper.configure.resource_owner_authenticator n'est pas configuré.
- server_error: Le serveur d'autorisation a rencontré une condition inattendue qui l'a empêché de remplir la demande.
+ resource_owner_authenticator_not_configured: La recherche du propriétaire de la ressource a échoué car Doorkeeper.configure.resource_owner_authenticator n'est pas configuré.
+ server_error: Le serveur d'autorisation a rencontré une condition inattendue l'empêchant de remplir la demande.
temporarily_unavailable: Le serveur d'autorisation est actuellement incapable de traiter la demande à cause d'une surcharge ou d'un entretien temporaire du serveur.
unauthorized_client: Le client n'est pas autorisé à effectuer cette demande à l'aide de cette méthode.
unsupported_grant_type: Le type de consentement d'autorisation n'est pas pris en charge par le serveur d'autorisation.
@@ -109,5 +133,5 @@ fr:
title: Autorisation OAuth requise
scopes:
follow: s’abonner, se désabonner, bloquer, et débloquer des comptes
- read: lire les données de votre compte
+ read: lire les données de votre compte
write: poster en tant que vous
diff --git a/config/locales/doorkeeper.hr.yml b/config/locales/doorkeeper.hr.yml
new file mode 100644
index 00000000..26d21c10
--- /dev/null
+++ b/config/locales/doorkeeper.hr.yml
@@ -0,0 +1,113 @@
+---
+hr:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Ime
+ redirect_uri: Redirect URI
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: ne može sadržavati fragment.
+ invalid_uri: mora biti valjan URI.
+ relative_uri: mora biti apsolutan URI.
+ secured_uri: mora biti HTTPS/SSL URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Odobri
+ cancel: Otkaži
+ destroy: Uništi
+ edit: Uredi
+ submit: Pošalji
+ confirmations:
+ destroy: Jesi li siguran?
+ edit:
+ title: Uredi aplikaciju
+ form:
+ error: Ups! Provjeri svoju formu za moguće greške
+ help:
+ native_redirect_uri: Koristi %{native_redirect_uri} za lokalne testove
+ redirect_uri: Koristi jednu liniju po URI
+ scopes: Odvoji scopes sa razmacima. Ostavi prazninu kako bi koristio zadane scopes.
+ index:
+ callback_url: Callback URL
+ name: Ime
+ new: Nova Aplikacija
+ title: Tvoje aplikacije
+ new:
+ title: Nova Aplikacija
+ show:
+ actions: Akcije
+ application_id: Id Aplikacije
+ callback_urls: Callback urls
+ scopes: Scopes
+ secret: Tajna
+ title: 'Aplikacija: %{name}'
+ authorizations:
+ buttons:
+ authorize: Odobri
+ deny: Odbij
+ error:
+ title: Došlo je do greške
+ new:
+ able_to: Moći će
+ prompt: Aplikacija %{client_name} je zatražila pristup tvom računu
+ title: Traži se autorizacija
+ show:
+ title: Autorizacijski kod
+ authorized_applications:
+ buttons:
+ revoke: Odbij
+ confirmations:
+ revoke: Jesi li siguran?
+ index:
+ application: Aplikacija
+ created_at: Ovlašeno
+ date_format: "%Y-%m-%d %H:%M:%S"
+ scopes: Scopes
+ title: Tvoje autorizirane aplikacije
+ errors:
+ messages:
+ access_denied: Vlasnik resursa / autorizacijski server je odbio zahtjev.
+ credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
+ invalid_client: Autentifikacija klijenta nije uspjela zbog nepoznatog klijenta, neuključene autentifikacije od strane klijenta, ili nepodržane metode autentifikacije.
+ invalid_grant: The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
+ invalid_redirect_uri: The redirect uri included nije valjan.
+ invalid_request: Zahtjevu nedostaje traženi parametar, uključuje nepodržanu vrijednost parametra, ili je na neki drugi način neispravno formiran.
+ invalid_resource_owner: The provided resource owner credentials nisu valjani, ili vlasnik resursa ne može biti nađen
+ invalid_scope: Traženi scope nije valjan, znan, ili je neispravno oblikovan.
+ invalid_token:
+ expired: Pristupni token je istekao
+ revoked: Pristupni token je odbijen
+ unknown: Pristupni token nije valjan
+ resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.
+ server_error: Autorizacijski server naišao je na neočekivani uvjet, što ga je onemogućilo da ispuni zahtjev.
+ temporarily_unavailable: Autorizacijski server trenutno nije u mogućnosti izvesti zahtjev zbog privremenog preopterećenja ili održavanja servera.
+ unauthorized_client: Klijent nije ovlašten izvesti zahtjev koristeći ovu metodu.
+ unsupported_grant_type: The authorization grant tip nije podržan od autorizacijskog servera.
+ unsupported_response_type: Autorizacijski server ne podržava ovaj tip odgovora.
+ flash:
+ applications:
+ create:
+ notice: Aplikacija je stvorena.
+ destroy:
+ notice: Aplikacija je obrisana.
+ update:
+ notice: Aplikacija je ažurirana.
+ authorized_applications:
+ destroy:
+ notice: Aplikacija je odbijena.
+ layouts:
+ admin:
+ nav:
+ applications: Aplikacije
+ oauth2_provider: OAuth2 Provider
+ application:
+ title: Traži se OAuth autorizacija
+ scopes:
+ follow: slijediti, blokirati, deblokirati i prestati slijediti račune
+ read: čitati podatke tvog računa
+ write: slati poruke u tvoje ime
diff --git a/config/locales/doorkeeper.ja.yml b/config/locales/doorkeeper.ja.yml
new file mode 100644
index 00000000..d3ea9378
--- /dev/null
+++ b/config/locales/doorkeeper.ja.yml
@@ -0,0 +1,113 @@
+---
+ja:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: 名前
+ redirect_uri: リダイレクトURI
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: フラグメントを含めることはできません。
+ invalid_uri: 有効なURIである必要があります。
+ relative_uri: 絶対URIである必要があります。
+ secured_uri: URIはHTTPS/SSLである必要があります。
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: 承認
+ cancel: キャンセル
+ destroy: 削除
+ edit: 編集
+ submit: 送信
+ confirmations:
+ destroy: 本当に削除しますか?
+ edit:
+ title: アプリの編集
+ form:
+ error: フォームにエラーが無いか確認してください。
+ help:
+ native_redirect_uri: ローカルテストに %{native_redirect_uri} を使用
+ redirect_uri: 一行に一つのURLを入力してください
+ scopes: アクセス権は半角スペースで区切ることができます。 空白のままにするとデフォルトを使用します。
+ index:
+ callback_url: コールバックURL
+ name: 名前
+ new: 新規アプリ
+ title: アプリ
+ new:
+ title: 新規アプリ
+ show:
+ actions: アクション
+ application_id: アクションId
+ callback_urls: コールバックurl
+ 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: 無効なリダイレクトURIが含まれています。
+ invalid_request: リクエストに必要なパラメータが欠けているか、サポートされていないパラメータが含まれている、または不正なフォーマットです。
+ invalid_resource_owner: 指定されたリソース所有者のクレデンシャルが無効であるか、リソース所有者が見つかりません。
+ invalid_scope: 要求されたアクセス権は無効であるか、不明、または不正なフォーマットです。
+ invalid_token:
+ expired: アクセストークンの有効期限が切れています
+ revoked: アクセストークンは取り消されています。
+ unknown: アクセストークンが無効です。
+ 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/doorkeeper.nl.yml b/config/locales/doorkeeper.nl.yml
new file mode 100644
index 00000000..91e62dc0
--- /dev/null
+++ b/config/locales/doorkeeper.nl.yml
@@ -0,0 +1,114 @@
+---
+nl:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Naam
+ redirect_uri: Redirect URI
+ scopes: Scopes
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: kan geen fragment bevatten.
+ invalid_uri: moet een geldige URI zijn.
+ relative_uri: moet een absolute URI zijn.
+ secured_uri: moet een HTTPS/SSL URI zijn.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Autoriseren
+ cancel: Annuleren
+ destroy: Verwijderen
+ edit: Bewerken
+ submit: Opslaan
+ confirmations:
+ destroy: Weet je het zeker?
+ edit:
+ title: Bewerk applicatie
+ form:
+ error: Oops! Controleer het formulier op fouten
+ help:
+ native_redirect_uri: Gebruik %{native_redirect_uri} voor lokale tests
+ redirect_uri: 'Gebruik één regel per URI. '
+ scopes: Scheid scopes met spaties. Laat leeg om de standaard scopes te gebruiken.
+ index:
+ callback_url: Callback URL
+ name: Naam
+ new: Nieuwe applicatie
+ title: Jouw applicaties
+ new:
+ title: Nieuwe applicatie
+ show:
+ actions: Acties
+ application_id: Applicatie Id
+ callback_urls: Callback urls
+ scopes: Scopes
+ secret: Secret
+ title: 'Applicatie: %{name}'
+ authorizations:
+ buttons:
+ authorize: Autoriseren
+ deny: Weigeren
+ error:
+ title: Er is een fout opgetreden
+ new:
+ able_to: Deze applicatie zal in staat zijn om
+ prompt: "%{client_name} autoriseren om uw account te gebruiken?"
+ title: Autorisatie vereist
+ show:
+ title: Autorisatie code
+ authorized_applications:
+ buttons:
+ revoke: Intrekken
+ confirmations:
+ revoke: Weet je het zeker?
+ index:
+ application: Applicatie
+ created_at: Aangemaakt op
+ date_format: "%d-%m-%Y %H:%M:%S"
+ title: Jouw geautoriseerde applicaties
+ errors:
+ messages:
+ access_denied: De resource eigenaar of autorisatie-server weigerde het verzoek.
+ credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
+ invalid_client: Client verificatie is mislukt door onbekende klant, geen client authenticatie opgegeven, of een niet-ondersteunde authenticatie methode.
+ invalid_grant: De verstrekte autorisatie is ongeldig, verlopen, ingetrokken, komt niet overeen met de redirect uri die is opgegeven, of werd uitgegeven aan een andere klant.
+ invalid_redirect_uri: De opgegeven redirect uri is niet geldig.
+ invalid_request: Het verzoek mist een vereiste parameter, bevat een niet-ondersteunde parameter waarde of is anderszins onjuist.
+ invalid_resource_owner: De verstrekte resource eigenaar gegevens zijn niet geldig of de resource eigenaar kan niet worden gevonden
+ invalid_scope: De opgevraagde scope is niet geldig, onbekend of onjuist.
+ invalid_token:
+ expired: Het toegangstoken is verlopen
+ revoked: Het toegangstoken is geweigerd
+ unknown: Het toegangstoken is ongeldig
+ resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.
+ server_error: De autorisatieserver is een onverwachte voorwaarde tegengekomen die het verzoek verhinderd.
+ temporarily_unavailable: De autorisatieserver is momenteel niet in staat het verzoek te behandelen als gevolg van een tijdelijke overbelasting of onderhoud aan de server.
+ unauthorized_client: De client is niet bevoegd om dit verzoek met deze methode uit te voeren.
+ unsupported_grant_type: Het type autorisatie is niet ondersteund door de autorisatieserver
+ unsupported_response_type: De autorisatieserver ondersteund dit response type niet
+ flash:
+ applications:
+ create:
+ notice: Applicatie aangemaakt.
+ destroy:
+ notice: Applicatie verwijderd.
+ update:
+ notice: Applicatie bewerkt.
+ authorized_applications:
+ destroy:
+ notice: Applicatie ingetrokken.
+ layouts:
+ admin:
+ nav:
+ applications: Applicaties
+ home: Home
+ oauth2_provider: OAuth2 Provider
+ application:
+ title: OAuth autorisatie vereist
+ scopes:
+ follow: volg, blokkeer, deblokkeer en stop volgen accounts
+ read: lees je accountgegevens
+ write: plaatsen namens jou
diff --git a/config/locales/doorkeeper.no.yml b/config/locales/doorkeeper.no.yml
new file mode 100644
index 00000000..f149f53e
--- /dev/null
+++ b/config/locales/doorkeeper.no.yml
@@ -0,0 +1,113 @@
+---
+'no':
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Navn
+ redirect_uri: Omdirigerings-URI
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: kan ikke inneholde ett fragment.
+ invalid_uri: må være en gyldig URI.
+ relative_uri: må være en absolutt URI.
+ secured_uri: må være en HTTPS/SSL URI.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Autoriser
+ cancel: Avbryt
+ destroy: Ødelegg
+ edit: Endre
+ submit: Send inn
+ confirmations:
+ destroy: Er du sikker?
+ edit:
+ title: Endre applikasjon
+ form:
+ error: Whoops! Sjekk skjemaet ditt for mulige feil
+ help:
+ native_redirect_uri: Bruk %{native_redirect_uri} for lokale tester
+ redirect_uri: Bruk en linje per URI
+ scopes: Adskill omfang med mellomrom. La det være blankt for å bruke standard omfang.
+ index:
+ callback_url: Callback URL
+ name: Navn
+ new: Ny Applikasjon
+ title: Dine applikasjoner
+ new:
+ title: Ny Applikasjoner
+ show:
+ actions: Operasjoner
+ application_id: Applikasjon Id
+ callback_urls: Callback urls
+ scopes: Omfang
+ secret: Hemmelighet
+ title: 'Applikasjon: %{name}'
+ authorizations:
+ buttons:
+ authorize: Autoriser
+ deny: Avvis
+ error:
+ title: En feil oppsto
+ new:
+ able_to: Den vil ha mulighet til
+ prompt: Applikasjon %{client_name} spør om tilgang til din konto
+ title: Autorisasjon påkrevd
+ show:
+ title: Autoriserings kode
+ authorized_applications:
+ buttons:
+ revoke: Opphev
+ confirmations:
+ revoke: Opphev?
+ index:
+ application: Applikasjon
+ created_at: Autorisert
+ date_format: "%Y-%m-%d %H:%M:%S"
+ scopes: Omfang
+ 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.
+ 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_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.
+ 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.
+ flash:
+ applications:
+ create:
+ notice: Applikasjon opprettet.
+ destroy:
+ notice: Applikasjon slettet.
+ update:
+ notice: Applikasjon oppdatert.
+ authorized_applications:
+ destroy:
+ notice: Applikasjon opphevet.
+ layouts:
+ admin:
+ nav:
+ applications: Applikasjoner
+ oauth2_provider: OAuth2 tilbyder
+ application:
+ title: OAuth autorisering påkrevet
+ scopes:
+ follow: følg, blokker, avblokker, avfølg kontoer
+ read: lese dine data
+ write: poste på dine vegne
diff --git a/config/locales/doorkeeper.ru.yml b/config/locales/doorkeeper.ru.yml
new file mode 100644
index 00000000..8862936d
--- /dev/null
+++ b/config/locales/doorkeeper.ru.yml
@@ -0,0 +1,113 @@
+---
+ru:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Название
+ redirect_uri: URI перенаправления
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: не может содержать фрагмент.
+ 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: Разделяйте список разрешений пробелами. Оставьте незаполненным для использования разрешений по умолчанию.
+ index:
+ callback_url: Callback URL
+ name: Название
+ new: Новое Приложение
+ title: Ваши приложения
+ new:
+ title: Новое Приложение
+ show:
+ actions: Действия
+ application_id: Id приложения
+ callback_urls: 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: Включенный URI перенаправления некорректен.
+ invalid_request: В запросе не хватает обязательного параметра, присутствует неподдерживаемое значение параметра, либо он был сформирован неверно.
+ invalid_resource_owner: Предоставленные данные владельца ресурса некорректны, или владелец ресурса не может быть найден
+ invalid_scope: Запрошенное разрешение некорректно, неизвестно или неверно сформировано.
+ invalid_token:
+ expired: Токен доступа истек
+ revoked: Токен доступа был отменен
+ unknown: Токен доступа некорректен
+ 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/doorkeeper.zh-HK.yml b/config/locales/doorkeeper.zh-HK.yml
new file mode 100644
index 00000000..90224c73
--- /dev/null
+++ b/config/locales/doorkeeper.zh-HK.yml
@@ -0,0 +1,113 @@
+---
+zh-HK:
+ 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: 資源擁有者密碼認證程序 (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: 資源擁有者的登入資訊錯誤、或者無法找到該資源擁有者。
+ invalid_scope: 請求的權限範圍 (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: 用戶程式無權用此方法 (method) 請行這個請求。
+ unsupported_grant_type: 授權伺服器不支援這個授權類型 (grant type)。
+ unsupported_response_type: 授權伺服器不支援這個回應類型 (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 157f107a..fdb9f45e 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -5,6 +5,7 @@ en:
about_this: About this instance
apps: Apps
business_email: 'Business e-mail:'
+ closed_registrations: Registrations are currently closed on this instance.
contact: Contact
description_headline: What is %{domain}?
domain_count_after: other instances
@@ -38,6 +39,30 @@ en:
posts: Posts
remote_follow: Remote follow
unfollow: Unfollow
+ admin:
+ settings:
+ click_to_edit: Click to edit
+ contact_information:
+ email: Enter a public e-mail address
+ label: Contact information
+ username: Enter a username
+ registrations:
+ closed_message:
+ desc_html: Displayed on frontpage when registrations are closed
You can use HTML tags
+ title: Closed registration message
+ open:
+ disabled: Disabled
+ enabled: Enabled
+ title: Open registration
+ setting: Setting
+ site_description:
+ desc_html: Displayed as a paragraph on the frontpage and used as a meta tag.
You can use HTML tags, in particular <a>
and <em>
.
+ title: Site description
+ site_description_extended:
+ desc_html: Displayed on extended information page
You can use HTML tags
+ title: Extended site description
+ site_title: Site title
+ title: Site Settings
application_mailer:
settings: 'Change e-mail preferences: %{link}'
signature: Mastodon notifications from %{instance}
@@ -73,10 +98,17 @@ en:
x_minutes: "%{count}m"
x_months: "%{count}mo"
x_seconds: "%{count}s"
+ errors:
+ '404': The page you were looking for doesn't exist.
+ '410': The page you were looking for doesn't exist anymore.
+ '422':
+ content: Security verification failed. Are you blocking cookies?
+ title: Security verification failed
exports:
blocks: You block
csv: CSV
follows: You follow
+ mutes: You mute
storage: Media storage
generic:
changes_saved_msg: Changes successfully saved!
@@ -91,8 +123,13 @@ en:
types:
blocking: Blocking list
following: Following list
+ muting: Muting list
upload: Upload
landing_strip_html: %{name} is a user on %{domain}. You can follow them or interact with them if you have an account anywhere in the fediverse. If you don't, you can sign up here.
+ media_attachments:
+ validations:
+ images_and_video: Cannot attach a video to a status that already contains images
+ too_many: Cannot attach more than 4 files
notification_mailer:
digest:
body: 'Here is a brief summary of what you missed on %{instance} since your last visit on %{since}:'
@@ -121,11 +158,30 @@ en:
pagination:
next: Next
prev: Prev
+ truncate: "…"
remote_follow:
acct: Enter your username@domain you want to follow from
missing_resource: Could not find the required redirect URL for your account
proceed: Proceed to follow
prompt: 'You are going to follow:'
+ reports:
+ comment:
+ label: Comment
+ none: None
+ delete: Delete
+ id: ID
+ mark_as_resolved: Mark as resolved
+ report: 'Report #%{id}'
+ reported_account: Reported account
+ reported_by: Signalé par
+ reports: Reports
+ resolved: Resolved
+ silence_account: Silence account
+ status: Status
+ suspend_account: Suspend account
+ target: Target
+ unresolved: Unresolved
+ view: View
settings:
authorized_apps: Authorized apps
back: Back to Mastodon
@@ -151,12 +207,16 @@ en:
formats:
default: "%b %d, %Y, %H:%M"
two_factor_auth:
+ code_hint: Enter the code generated by your authenticator app to confirm
description_html: If you enable two-factor authentication, logging in will require you to be in possession of your phone, which will generate tokens for you to enter.
disable: Disable
enable: Enable
- instructions_html: "Scan this QR code into Google Authenticator or a similiar app on your phone. From now on, that app will generate tokens that you will have to enter when logging in."
- plaintext_secret_html: 'Plain-text secret: %{secret}'
+ enabled_success: Two-factor authentication successfully enabled
+ 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."
+ manual_instructions: 'If you can''t scan the QR code and need to enter it manually, here is the plain-text secret:'
+ 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
diff --git a/config/locales/eo.yml b/config/locales/eo.yml
new file mode 100644
index 00000000..e82e4249
--- /dev/null
+++ b/config/locales/eo.yml
@@ -0,0 +1,162 @@
+---
+eo:
+ about:
+ about_mastodon: Mastodon estas senpaga, malfermitkoda socia reto. Ĝi estas sencentra alia eblo al komercaj servoj. Ĝi evitigas, ke unusola firmao regu vian tutan komunikadon. Elektu servilon, kiun vi fidas. Kiu ajn estas via elekto, vi povas interagi kun ĉiuj aliaj uzantoj. Iu ajn povas krei sian propran aperaĵon de Mastodon en sia servilo, kaj partopreni en la socia reto tute glate.
+ about_this: Pri tiu aperaĵo
+ apps: Aplikaĵoj
+ business_email: 'Profesia retpoŝt-adreso:'
+ contact: Kontakti
+ description_headline: Kio estas %{domain}?
+ domain_count_after: aliaj aperaĵoj
+ domain_count_before: Konektita al
+ features:
+ api: Malfermita API por aplikaĵoj kaj servoj
+ blocks: Kompletaj iloj por bloki kaj kaŝi
+ characters: Po 500 signoj por ĉiu mesaĝo
+ chronology: Tempolinioj laŭtempaj
+ ethics: 'Etike kreita: neniu reklamo, neniu ŝpurado'
+ gifv: Eblo diskonigi etajn videojn kaj GIFV
+ privacy: Videbleco agordita laŭ la mesaĝo
+ public: Publikaj tempolinioj
+ features_headline: Kiel Mastodon estas malsimila
+ get_started: Komenci
+ links: Ligiloj
+ other_instances: Aliaj aperaĵoj
+ source_code: Fontkodo
+ status_count_after: mesaĝoj
+ status_count_before: Kiu publikigis
+ terms: Terms
+ user_count_after: uzantoj
+ user_count_before: Hejmo de
+ accounts:
+ follow: Sekvi
+ followers: Sekvantoj
+ following: Sekvatoj
+ nothing_here: Estas nenio ĉi tie!
+ people_followed_by: Sekvatoj de %{name}
+ people_who_follow: Sekvantoj de %{name}
+ posts: Mesaĝoj
+ remote_follow: Fore sekvi
+ unfollow: Malsekvi
+ application_mailer:
+ settings: 'Ŝanĝi la retpoŝt-mesaĝajn preferojn: %{link}'
+ signature: Sciigoj de Mastodon el %{instance}
+ view: 'Vidi:'
+ applications:
+ invalid_url: La URL donita ne estas valida
+ auth:
+ change_password: Ŝanĝi pasvorton
+ didnt_get_confirmation: Ĉu vi ne ricevis la instrukciojn por konfirmi?
+ forgot_password: Pasvorto forgesita?
+ login: Ensaluti
+ logout: Elsaluti
+ register: Membriĝi
+ resend_confirmation: Resendi la instrukciojn por konfirmi
+ reset_password: Ŝanĝi la pasvorton
+ set_new_password: Elekti novan pasvorton
+ authorize_follow:
+ error: Bedaŭrinde, okazis eraro provante konsulti la foran konton
+ follow: Sekvi
+ prompt_html: 'Vi (%{self}) petis sekvi:'
+ title: Sekvi %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}h"
+ about_x_months: "%{count}mo"
+ about_x_years: "%{count}j"
+ almost_x_years: "%{count}j"
+ half_a_minute: Ĵus
+ less_than_x_minutes: "%{count}m"
+ less_than_x_seconds: Ĵus
+ over_x_years: "%{count}j"
+ x_days: "%{count}t"
+ x_minutes: "%{count}m"
+ x_months: "%{count}mo"
+ x_seconds: "%{count}s"
+ exports:
+ blocks: Vi blokas
+ csv: CSV
+ follows: Vi sekvas
+ storage: Mediaĵa konservado
+ generic:
+ changes_saved_msg: Ŝanĝoj senprobleme konservitaj!
+ powered_by: povigita de %{link}
+ save_changes: Konservi la ŝanĝojn
+ validation_errors:
+ one: Io ne okazis senprobleme! Bonvolu konsulti la suban erar-raporton.
+ other: Io ne okazis senprobleme! Bonvolu konsulti la subajn %{count} erar-raportojn.
+ imports:
+ preface: Vi povas alporti kelkajn datumojn, kiel listojn de ĉiuj homoj kiujn vi sekvas aŭ blokas, al via konto de ĉi tiu aperaĵo, per dosiero elportita de alia aperaĵo.
+ success: Viaj datumoj estis senprobleme alportitaj kaj estos traktitaj kiel planite.
+ types:
+ blocking: Listo de blokitoj
+ following: Listo de sekvatoj
+ upload: Alporti
+ landing_strip_html: %{name} estas uzanto en %{domain}. Vi povas sekvi tiun aŭ interagi kun tiu, se vi havas konton ie ajn en la Fediverse. Se vi ne havas, vi povas membriĝi ĉi tie..
+ notification_mailer:
+ digest:
+ body: 'Jen eta resumo de tio, kio okazis en %{instance}, ekde kiam vi laste vizitis en %{since}:'
+ mention: "%{name} menciis vin en:"
+ new_followers_summary:
+ one: Vi ekhavis novan sekvanton! Jej!
+ other: Vi ekhavis %{count} novajn sekvantojn! Mirinde!
+ subject:
+ one: "1 nova sciigo ekde via lasta vizito \U0001F418"
+ other: "%{count} novaj sciigoj ekde via lasta vizito \U0001F418"
+ favourite:
+ body: '%{name} favoris vian mesaĝon:'
+ subject: "%{name} favoris vian mesaĝon"
+ follow:
+ body: "%{name} eksekvis vin:"
+ subject: "%{name} eksekvis vin"
+ follow_request:
+ body: "%{name} petis sekvi vin:"
+ subject: '%{name} petis sekvi vin'
+ mention:
+ body: '%{name} menciis vin en:'
+ subject: '%{name} menciis vin'
+ reblog:
+ body: '%{name} diskonigis vian mesaĝon:'
+ subject: "%{name} diskonigis vian mesaĝon"
+ pagination:
+ next: Sekva
+ prev: Malsekva
+ remote_follow:
+ acct: Enmetu vian uzantnomo@aperaĵo de kie vi volas sekvi tiun uzanton
+ missing_resource: La URL de plusendado ne povis esti trovita
+ proceed: Daŭrigi por plusendi
+ prompt: 'Vi eksekvos:'
+ settings:
+ authorized_apps: Rajtigitaj aplikaĵoj
+ back: Reveni al Mastodon
+ edit_profile: Redakti la profilon
+ export: Elporti datumojn
+ import: Alporti
+ preferences: Preferoj
+ settings: Agordoj
+ two_factor_auth: Dufaktora aŭtentigo
+ statuses:
+ open_in_web: Malfermi retumile
+ over_character_limit: limo de %{max} signoj trapasita
+ show_more: Montri pli
+ visibilities:
+ private: Montri nur al sekvantoj
+ public: Publika
+ unlisted: Publika, sed ne aperos en publikaj tempolinioj
+ stream_entries:
+ click_to_show: Alklaki por montri
+ reblogged: diskonigita
+ sensitive_content: Tikla enhavo
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ description_html: Se vi ebligas dufaktoran aŭtentigon, vi bezonos vian poŝtelefonon por ensaluti, ĉar ĝi kreos nombrojn, kiujn vi devos entajpi.
+ disable: Malebligi
+ enable: Ebligi
+ instructions_html: "Skanu tiun QR-kodon per Google Authenticator aŭ per simila aplikaĵo de via poŝtelefono. De tiam, la aplikaĵo kreos nombrojn, kiujn vi devos entajpi."
+ plaintext_secret_html: 'Rekte legebla sekreta kodo: %{secret}'
+ warning: Se vi ne povas agordi aŭtentigan aplikaĵon nun, elektu "malebligi" aŭ vi ne plu povos ensaluti.
+ users:
+ invalid_email: La retpoŝt-adreso ne estas valida
+ invalid_otp_token: La dufaktora aŭtentigila kodo ne estas valida
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 19f2c71b..3b0181f6 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -2,9 +2,33 @@
es:
about:
about_mastodon: Mastodon es un servidor de red social libre y de código abierto. Una alternativa descentralizada a plataformas comerciales, que evita el riesgo de que una única compañía monopolice tu comunicación. Cualquiera puede ejecutar Mastodon y participar sin problemas en la red social.
+ about_this: Acerca de esta instancia
+ apps: Apps
+ business_email: 'Correo de negocios:'
+ closed_registrations: Los registros están actualmente cerrados en esta instancia.
+ contact: Contacto
+ description_headline: "¿Qué es %{domain}?"
+ domain_count_after: otras instancias
+ domain_count_before: Conectado a
+ features:
+ api: API pública para aplicaciones y servicios
+ blocks: Moderación de contenido
+ characters: 500 caracteres por publicación
+ chronology: Las historias son cronológicas
+ ethics: 'Diseño etico: sin anuncios, sin rastreos'
+ gifv: Videos cortos y GIFV
+ privacy: Configuraciones de privacidad ajustables
+ public: Historia federada
+ features_headline: Lo que distingue a Mastodon
get_started: Comenzar
+ links: Enlaces
+ other_instances: Otras instancias
source_code: Código fuente
- terms: Términos de uso
+ status_count_after: estados
+ status_count_before: Que han escrito
+ terms: Términos
+ user_count_after: usuarios registrados
+ user_count_before: Tenemos
accounts:
follow: Seguir
followers: Seguidores
@@ -12,44 +36,132 @@ es:
nothing_here: "¡No hay nada aquí!"
people_followed_by: Usuarios a quien %{name} sigue
people_who_follow: Usuarios que siguen a %{name}
- posts: Publicaciones
+ posts: Toots
+ remote_follow: Seguir
unfollow: Dejar de seguir
application_mailer:
+ settings: 'Cambiar preferencias de correo: %{link}'
signature: Notificaciones de Mastodon desde %{instance}
+ view: 'Vista:'
+ applications:
+ invalid_url: La URL proporcionada es incorrecta
auth:
change_password: Cambiar contraseña
- didnt_get_confirmation: "¿No recibió instrucciones de confirmación?"
- forgot_password: "¿Olvidó su contraseña?"
+ didnt_get_confirmation: "¿No recibió el correo de confirmación?"
+ forgot_password: "¿Olvidaste tu contraseña?"
login: Iniciar sesión
+ logout: Cerrar sesión
register: Registrarse
- resend_confirmation: Volver a enviar las instrucciones de confirmación
+ resend_confirmation: Volver a enviar el correo de confirmación
reset_password: Restablecer contraseña
set_new_password: Establecer nueva contraseña
+ authorize_follow:
+ error: Desafortunadamente, ha ocurrido un error buscando la cuenta remota
+ follow: Seguir
+ prompt_html: 'Tú (%{self}) has solicitado seguir:'
+ title: Seguir %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}h"
+ about_x_months: "%{count}m"
+ about_x_years: "%{count}y"
+ almost_x_years: "%{count}y"
+ half_a_minute: Justo ahora
+ less_than_x_minutes: "%{count}m"
+ less_than_x_seconds: Justo ahora
+ over_x_years: "%{count}y"
+ x_days: "%{count}d"
+ x_minutes: "%{count}m"
+ x_months: "%{count}m"
+ x_seconds: "%{count}s"
+ exports:
+ blocks: Personas que has bloqueado
+ csv: CSV
+ follows: Personas que sigues
+ storage: Almacenamiento
generic:
changes_saved_msg: "¡Cambios guardados con éxito!"
powered_by: powered by %{link}
save_changes: Guardar cambios
validation_errors:
- one: "¡Algo no está todavía bien! Por favor, revise el error más abajo"
- other: "¡Algo no está todavía bien! Por favor, revise %{count} errores más abajo"
+ one: "¡Algo no está bien! Por favor, revisa el error"
+ other: "¡Algo no está bien! Por favor, revise %{count} errores más abajo"
+ imports:
+ preface: Puedes importar ciertos datos, como todas las personas que estás siguiendo o bloqueando en tu cuenta en esta instancia, desde archivos exportados de otra instancia.
+ success: Sus datos se han cargado correctamente y serán procesados en brevedad
+ types:
+ blocking: Lista de bloqueados
+ following: Lista de seguidos
+ upload: Cargar
+ landing_strip_html: %{name} es un usuario en %{domain}. Puedes seguirlo(a) o interactuar con el o ella si tienes una cuenta en cualquier parte del fediverse. Si no tienes una, puedes registrar aquí.
+ media_attachments:
+ validations:
+ images_and_video: No se puede adjuntar un video a un estado que ya contenga imágenes
+ too_many: No se pueden adjuntar más de 4 archivos
notification_mailer:
+ digest:
+ body: 'Un resumen de lo que te perdiste en %{instance} desde tu última visita el %{since}:'
+ mention: "%{name} te ha mencionado en:"
+ new_followers_summary:
+ one: "¡Hurra!. Alguien más te ha comenzado a seguir"
+ other: "¡Genial!. Te han seguido %{count} nuevas personas"
+ subject:
+ one: "1 nueva notificación desde tu última visita \U0001F418"
+ other: "%{count} nuevas notificaciones desde tu última visita \U0001F418"
favourite:
- body: 'Su estado fue marcado como favorito por %{name}:'
- subject: "%{name} marcó como favorito su estado"
+ body: 'Tu estado fue marcado como favorito por %{name}:'
+ subject: "%{name} marcó como favorito tu estado"
follow:
- body: "¡%{name} le está ahora siguiendo!"
- subject: "%{name} le esta ahora siguiendo"
+ body: "¡%{name} te está siguiendo!"
+ subject: "%{name} te está siguiendo"
+ follow_request:
+ body: "%{name} ha solicitado seguirte"
+ subject: 'Seguidor pendiente: %{name}'
mention:
- body: 'Fue mencionado por %{name} en:'
- subject: Fue mencionado por %{name}
+ body: 'Fuiste mencionado por %{name} en:'
+ subject: Fuiste mencionado por %{name}
reblog:
- body: 'Su estado fue vuelto a publicar por %{name}:'
- subject: "%{name} volvió a publicar su estado"
+ body: "%{name} ha retooteado tu estado"
+ subject: "%{name} ha retooteado tu estado"
pagination:
next: Próximo
prev: Anterior
+ remote_follow:
+ acct: Ingesa el usuario@dominio de la persona que quieres seguir
+ missing_resource: No se pudo encontrar la URL de redirección necesaria para su cuenta.
+ proceed: Proceder a seguir
+ prompt: 'Vas a seguir a:'
settings:
+ authorized_apps: Aplicaciones autorizadas
+ back: Volver al inicio
edit_profile: Editar perfil
+ export: Exportar información
+ import: Importar
preferences: Preferencias
- will_paginate:
- page_gap: "…"
+ settings: Ajustes
+ two_factor_auth: Aute/ción. de dos factores
+ statuses:
+ open_in_web: Abrir en web
+ over_character_limit: Límite de caracteres de %{max} superado
+ show_more: Mostrar más
+ visibilities:
+ private: Sólo mostrar a seguidores
+ public: Público
+ unlisted: Público, pero no mostrar en la historia federada
+ stream_entries:
+ click_to_show: Click para mostrar
+ reblogged: retooteado
+ sensitive_content: Contenido sensible
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ description_html: Sí habilitas la autenticación de dos factores, se requerirá estar en posesión de su teléfono, lo que generará tokens para que usted pueda iniciar sesión.
+ disable: Deshabilitar
+ enable: Habilitar
+ instructions_html: "Escanea este código QR desde Google Authenticator o una aplicación similar en su teléfono. Desde ahora, esta aplicación va a generar tokens que tienes que ingresar cuando quieras iniciar sesión."
+ plaintext_secret_html: 'Código en texto plano: %{secret}'
+ warning: Sí no puedes configurar una aplicación de autenticación ahora, deberás deshabilitar la autenticación de dos factores o no podrás iniciar sesión.
+ users:
+ invalid_email: La dirección de correo es incorrecta
+ invalid_otp_token: Código de dos factores incorrecto
diff --git a/config/locales/fi.yml b/config/locales/fi.yml
new file mode 100644
index 00000000..db8194ff
--- /dev/null
+++ b/config/locales/fi.yml
@@ -0,0 +1,162 @@
+---
+fi:
+ about:
+ about_mastodon: Mastodon on ilmainen, avoimeen lähdekoodiin perustuva sosiaalinen verkosto. Hajautettu vaihtoehto kaupallisille alustoille, se välttää eiskit yhden yrityksen monopolisoinnin sinun viestinnässäsi. Valitse palvelin mihin luotat — minkä tahansa valitset, voit vuorovaikuttaa muiden kanssa. Kuka tahansa voi luoda Mastodon palvelimen ja ottaa osaa sosiaaliseen verkkoon saumattomasti.
+ about_this: Tietoja tästä palvelimesta
+ apps: Ohjelmat
+ business_email: 'Business e-mail:'
+ contact: Ota yhteyttä
+ description_headline: Mikä on %{domain}?
+ domain_count_after: muuhun palvelimeen
+ domain_count_before: Yhdistyneenä
+ features:
+ api: Avoin API ohjelmille ja palveluille
+ blocks: Rikkaat esto- ja hiljennystyökalut
+ characters: 500 kirjainta per viesti
+ chronology: Aikajana on kronologisessa järjestyksessä
+ ethics: 'Eettinen suunnittelu: ei mainoksia, ei seurantaa'
+ gifv: GIFV settejä ja lyhyitä videoita
+ privacy: Julkaisukohtainen yksityisyysasetus
+ public: Julkiset aikajanat
+ features_headline: Mikä erottaa Mastodonin muista
+ get_started: Aloita käyttö
+ links: Linkit
+ other_instances: Muut palvelimet
+ source_code: Lähdekoodi
+ status_count_after: statusta
+ status_count_before: Ovat luoneet
+ terms: Ehdot
+ user_count_after: käyttäjälle
+ user_count_before: Koti
+ accounts:
+ follow: Seuraa
+ followers: Seuraajat
+ following: Seuratut
+ nothing_here: Täällä ei ole mitään!
+ people_followed_by: Henkilöitä joita %{name} seuraa
+ people_who_follow: Henkilöt jotka seuraa %{name}
+ posts: Postaukset
+ remote_follow: Etäseuranta
+ unfollow: Lopeta seuraaminen
+ application_mailer:
+ settings: 'Muokkaa sähköpostiasetuksia: %{link}'
+ signature: Mastodon-ilmoituksia palvelimelta %{instance}
+ view: 'Katso:'
+ applications:
+ invalid_url: Annettu URL on väärä
+ auth:
+ change_password: Tunnukset
+ didnt_get_confirmation: Etkö saanut varmennusohjeita?
+ forgot_password: Unohditko salasanasi?
+ login: Kirjaudu sisään
+ logout: Kirjaudu ulos
+ register: Rekisteröidy
+ resend_confirmation: Lähetä varmennusohjeet uudestaan
+ reset_password: Palauta salasana
+ set_new_password: Aseta uusi salasana
+ authorize_follow:
+ error: Valitettavasti tapahtui virhe etätilin haussa.
+ follow: Seuraa
+ prompt_html: 'Sinä (%{self}) olet pyytänyt lupaa seurata:'
+ title: Seuraa %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}t"
+ about_x_months: "%{count}kk"
+ about_x_years: "%{count}v"
+ almost_x_years: "%{count}v"
+ half_a_minute: Juuri nyt
+ less_than_x_minutes: "%{count}m"
+ less_than_x_seconds: Juuri nyt
+ over_x_years: "%{count}v"
+ x_days: "%{count}pv"
+ x_minutes: "%{count}m"
+ x_months: "%{count}kk"
+ x_seconds: "%{count}s"
+ exports:
+ blocks: Estosi
+ csv: CSV
+ follows: Seurattavat
+ storage: Mediasi
+ generic:
+ changes_saved_msg: Muutokset onnistuneesti tallennettu!
+ powered_by: powered by %{link}
+ save_changes: Tallenna muutokset
+ validation_errors:
+ one: Jokin ei ole viellä oikein! Katso virhe alapuolelta.
+ other: Jokin ei ole viellä oikein! Katso %{count} virhettä alapuolelta.
+ imports:
+ preface: Voit tuoda tiettyä dataa kaikista ihmisistä joita seuraat tai estät tilillesi tälle palvelimelle tiedostoista, jotka on luotu toisella palvelimella
+ success: Datasi on onnistuneesti ladattu ja käsitellään pian
+ types:
+ blocking: Estetyt lista
+ following: Seuratut lista
+ upload: Lähetä
+ landing_strip_html: %{name} on käyttäjä domainilla %{domain}. Voit seurata tai vuorovaikuttaa heidän kanssaan jos sinulla on tili yleisessä verkossa. Jos sinulla ei ole tiliä, voit rekisteröityä täällä.
+ notification_mailer:
+ digest:
+ body: 'Tässä on pieni yhteenveto palvelimelta %{instance} viimeksi kun olit paikalla %{since}:'
+ mention: "%{name} mainitsi sinut:"
+ new_followers_summary:
+ one: Olet saanut yhden uuden seuraajan! Jee!
+ other: Olet saanut %{count} uutta seuraajaa! Loistavaa!
+ subject:
+ one: "1 uusi ilmoitus viimeisen käyntisi jälkeen \U0001F418"
+ other: "%{count} uutta ilmoitusta viimeisen käyntisi jälkeen \U0001F418"
+ favourite:
+ body: 'Statuksestasi tykkäsi %{name}:'
+ subject: "%{name} tykkäsi sinun statuksestasi"
+ follow:
+ body: "%{name} seuraa nyt sinua!"
+ subject: "%{name} seuraa nyt sinua"
+ follow_request:
+ body: "%{name} on pyytänyt seurata sinua"
+ subject: 'Odottava seuraus pyyntö: %{name}'
+ mention:
+ body: 'Sinut mainitsi %{name} postauksessa:'
+ subject: Sinut mainitsi %{name}
+ reblog:
+ body: 'Sinun statustasi boostasi %{name}:'
+ subject: "%{name} boostasi statustasi"
+ pagination:
+ next: Seuraava
+ prev: Edellinen
+ remote_follow:
+ acct: Syötä sinun käyttäjänimesi@domain jos haluat seurata palvelimelta
+ missing_resource: Ei löydetty tarvittavaa uudelleenohjaavaa URL-linkkiä tilillesi
+ proceed: Siirry seuraamiseen
+ prompt: 'Sinä aiot seurata:'
+ settings:
+ authorized_apps: Valtuutetut ohjelmat
+ back: Takaisin Mastodoniin
+ edit_profile: Muokkaa profiilia
+ export: Vie dataa
+ import: Tuo dataa
+ preferences: Ominaisuudet
+ settings: Asetukset
+ two_factor_auth: Kaksivaiheinen tunnistus
+ statuses:
+ open_in_web: Avaa webissä
+ over_character_limit: sallittu kirjanmäärä %{max} ylitetty
+ show_more: Näytä lisää
+ visibilities:
+ private: Näytä vain seuraajille
+ public: Julkinen
+ unlisted: Julkinen, mutta älä näytä julkisella aikajanalla
+ stream_entries:
+ click_to_show: Klikkaa näyttääksesi
+ reblogged: boosted
+ sensitive_content: Herkkä materiaali
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ description_html: Jos otat käyttöön kaksivaiheisen tunnistuksen, kirjautumiseen vaaditaan puhelin, joka voi luoda tokeneita kirjautumista varten.
+ disable: Poista käytöstä
+ enable: Ota käyttöön
+ instructions_html: "Skannaa tämä QR-koodi Google Authenticator- tai vastaavaan sovellukseen puhelimellasi. Tästä hetkestä lähtien ohjelma luo koodin, mikä sinun tarvitsee syöttää sisäänkirjautuessa."
+ plaintext_secret_html: 'Plain-text secret: %{secret}'
+ warning: Jos et juuri nyt voi konfiguroida authenticator-applikaatiota juuri nyt, sinun pitäisi klikata "Poista käytöstä" tai et voi kirjautua sisään.
+ users:
+ invalid_email: Virheellinen sähköposti
+ invalid_otp_token: Virheellinen kaksivaihetunnistuskoodi
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index d41106eb..c5e89634 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -4,22 +4,25 @@ fr:
about_mastodon: Mastodon est un serveur libre de réseautage social. Alternative décentralisée aux plateformes commerciales, la monopolisation de vos communications par une entreprise unique est évitée. Tout un chacun peut faire tourner Mastodon et participer au réseau social de manière transparente.
about_this: À propos de cette instance
apps: Applications
- business_email: E-mail professionnel
+ business_email: Courriel professionnel
+ closed_registrations: Les inscriptions sont actuellement fermées sur cette instance.
+ contact: Contact
description_headline: Qu'est-ce que %{domain} ?
domain_count_after: autres instances
domain_count_before: Connectés à
features:
api: API ouverte aux apps et services
- blocking: Outils complets de bloquage et masquage
+ blocks: Outils complets de bloquage et masquage
characters: 500 caractères par post
chronology: Fil chronologique
- ethics: 'Pas de pubs, pas de pistage'
+ ethics: Pas de pubs, pas de pistage
gifv: Partage de vidéos et de GIFs
privacy: Réglages de confidentialité au niveau des posts
public: Fils publics
features_headline: Ce qui rend Mastodon différent
get_started: Rejoindre le réseau
links: Liens
+ other_instances: Autres instances
source_code: Code source
status_count_after: posts
status_count_before: Ayant publié
@@ -36,10 +39,34 @@ fr:
posts: Statuts
remote_follow: Suivre à distance
unfollow: Ne plus suivre
+ admin:
+ settings:
+ click_to_edit: Cliquez pour éditer
+ contact_information:
+ email: Entrez une adresse courriel publique
+ label: Informations de contact
+ username: Entrez un nom d'utilisateur
+ registrations:
+ closed_message:
+ desc_html: Affiché sur la page d'accueil lorsque les inscriptions sont fermées
Vous pouvez utiliser des balises HTML
+ title: Message de fermeture des inscriptions
+ open:
+ disabled: Désactivées
+ enabled: Activées
+ title: Inscriptions
+ setting: Paramètre
+ site_description:
+ desc_html: Affichée sous la forme d'un paragraphe sur la page d'accueil et utilisée comme balise meta.
Vous pouvez utiliser des balises HTML, en particulier <a>
et <em>
.
+ title: Description du site
+ site_description_extended:
+ desc_html: Affichée sur la page d'informations complémentaires du site
Vous pouvez utiliser des balises HTML
+ title: Description étendue du site
+ site_title: Titre du site
+ title: Paramètres du site
application_mailer:
- settings: 'Changer les préférences e-mail: ${link}'
+ settings: 'Changer les préférences courriel : %{link}'
signature: Notifications de Mastodon depuis %{instance}
- view: 'Voir:'
+ view: 'Voir :'
applications:
invalid_url: L'URL fournie est invalide
auth:
@@ -53,9 +80,36 @@ fr:
reset_password: Réinitialiser le mot de passe
set_new_password: Définir le nouveau mot de passe
authorize_follow:
+ error: Malheureusement, il y a eu une erreur en cherchant les détails du compte distant
follow: Suivre
- prompt_html: 'Vous (%{self}) avez demandé à suivre:'
+ prompt_html: 'Vous (%{self}) avez demandé à suivre :'
title: Suivre %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}h"
+ about_x_months: "%{count}mois"
+ about_x_years:
+ one: un an
+ other: "%{count} ans"
+ almost_x_years:
+ one: un an
+ other: "%{count} ans"
+ half_a_minute: A l'instant
+ less_than_x_minutes: "%{count}min"
+ less_than_x_seconds: A l'instant
+ over_x_years:
+ one: un an
+ other: "%{count} ans"
+ x_days: "%{count}j"
+ x_minutes: "%{count}min"
+ x_months: "%{count}mois"
+ x_seconds: "%{count}s"
+ errors:
+ '404': La page que vous recherchez n'existe pas.
+ '410': La page que vous recherchez n'existe plus.
+ '422':
+ content: Vérification de sécurité échouée. Bloquez-vous les cookies ?
+ title: Vérification de sécurité échouée
exports:
blocks: Vous bloquez
csv: CSV
@@ -78,8 +132,8 @@ fr:
landing_strip_html: %{name} utilise %{domain}. Vous pouvez le/la suivre et interagir si vous possédez un compte quelque part dans le "fediverse". Si ce n'est pas le cas, vous pouvez en créer un ici.
notification_mailer:
digest:
- body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite (%{}):'
- mention: '%{name} vous a mentionné⋅e'
+ body: 'Voici ce que vous avez raté sur ${instance} depuis votre dernière visite (%{}) :'
+ mention: "%{name} vous a mentionné⋅e"
new_followers_summary:
one: Vous avez un⋅e nouvel⋅le abonné⋅e ! Youpi !
other: Vous avez %{count} nouveaux abonné⋅es ! Incroyable !
@@ -92,6 +146,9 @@ fr:
follow:
body: "%{name} vous suit !"
subject: "%{name} vous suit"
+ follow_request:
+ body: "%{name} a demandé à vous suivre"
+ subject: 'Abonné⋅es en attente : %{name}'
mention:
body: "%{name} vous a mentionné⋅e dans :"
subject: "%{name} vous a mentionné⋅e"
@@ -106,6 +163,24 @@ fr:
missing_resource: L'URL de redirection n'a pas pu être trouvée
proceed: Continuez pour suivre
prompt: 'Vous allez suivre :'
+ reports:
+ comment:
+ label: Commentaire
+ none: Aucun
+ delete: Supprimer
+ id: ID
+ mark_as_resolved: Marqué comme résolu
+ report: 'Signalement #%{id}'
+ reported_account: Compte signalé
+ reported_by: Signalé par
+ reports: Signalements
+ resolved: Résolus
+ silence_account: Rendre le compte muet
+ status: Statut
+ suspend_account: Suspendre le compte
+ target: Cible
+ unresolved: Non résolus
+ view: Voir
settings:
authorized_apps: Applications autorisées
back: Retour vers Mastodon
@@ -129,16 +204,16 @@ fr:
sensitive_content: Contenu sensible
time:
formats:
- default: '%d %b %Y, %H:%M'
+ default: "%d %b %Y, %H:%M"
two_factor_auth:
- description_html: Si vous activez l'identification à deux facteurs vous devrez être en posession de votre téléphone afin de générer un code de connexion.
+ description_html: Si vous activez l'identification à deux facteurs, vous devrez être en possession de votre téléphone afin de générer un code de connexion.
disable: Désactiver
enable: Activer
- instructions_html: "Scannez ce QR code grâce à Google Authenticator or une application similaire sur votre téléphone. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."
- plaintext_secret_html: 'Code secret en clair: %{secret}'
+ instructions_html: "Scannez ce QR code grâce à Google Authenticator, Authy ou une application similaire sur votre téléphone. Désormais, cette application générera des jetons que vous devrez saisir à chaque connexion."
+ plaintext_secret_html: 'Code secret en clair : %{secret}'
warning: Si vous ne pouvez pas configurer une application d'authentification maintenant, vous devriez cliquer sur "Désactiver" pour ne pas bloquer l'accès à votre compte.
users:
- invalid_email: L'adresse e-mail est invalide
- invalid_otp_token: Le code d'identification à deux facteurs est invalide
+ 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
new file mode 100644
index 00000000..64b1ae2c
--- /dev/null
+++ b/config/locales/hr.yml
@@ -0,0 +1,165 @@
+---
+hr:
+ about:
+ about_mastodon: Mastodon je besplatna, open-source socijalna mreža. Decentralizirana alternativa komercijalnim platformama, izbjegava rizik toga da jedna tvrtka monopolizira vašu komunikaciju. Izaberite server kojem ćete vjerovati — koji god odabrali, moći ćete komunicirati sa svima ostalima. Bilo tko može imati svoju vlastitu Mastodon instancu i sudjelovati u socijalnoj mreži bez problema.
+ about_this: O ovoj instanci
+ apps: Aplikacije
+ business_email: 'Poslovni e-mail:'
+ closed_registrations: Registracije na ovoj instanci su trenutno zatvorene.
+ contact: Kontakt
+ description_headline: Što je %{domain}?
+ domain_count_after: druge instance
+ domain_count_before: Spojen na
+ features:
+ api: Otvoren API za aplikacije i servise
+ blocks: Bogati alati za blokiranje i ušutkivanje
+ characters: 500 znakova po postu
+ chronology: Timelines su kronološke
+ ethics: 'Etički dizajn: bez oglasa, bez praćenja'
+ gifv: GIFV setovi i kratki videi
+ privacy: Granularne postavke privatnosti, po postu
+ public: Javne timelines
+ features_headline: Po čemu se Mastodon razlikuje
+ get_started: Započni
+ links: Linkovi
+ other_instances: Druge instance
+ source_code: Izvorni kod
+ status_count_after: statusi
+ status_count_before: Tko je autor
+ terms: Uvjeti
+ user_count_after: korisnici
+ user_count_before: Home to
+ accounts:
+ follow: Slijedi
+ followers: Sljedbenici
+ following: Slijedim
+ nothing_here: Ovdje nema ničeg!
+ people_followed_by: Ljudi koje %{name} slijedi
+ people_who_follow: Ljudi koji slijede %{name}
+ posts: Postovi
+ remote_follow: Remote follow
+ unfollow: Prestani slijediti
+ application_mailer:
+ settings: 'Promijeni e-mail postavke: %{link}'
+ signature: Mastodon notifikacije sa %{instance}
+ view: 'View:'
+ applications:
+ invalid_url: Uneseni link nije valjan
+ auth:
+ change_password: Vjerodajnice
+ didnt_get_confirmation: Niste primili instrukcije za potvrđivanje?
+ forgot_password: Zaboravljena lozinka?
+ login: Prijavi se
+ logout: Odjavi se
+ register: Registriraj se
+ resend_confirmation: Ponovo pošalji instrukcije za potvrđivanje
+ reset_password: Resetiraj lozinku
+ set_new_password: Postavi novu lozinku
+ authorize_follow:
+ error: Nažalost, došlo je do greške looking up the remote račun
+ follow: Slijedi
+ prompt_html: 'Ti si (%{self}) poslao zahtjev za sljeđenje:'
+ title: Slijedi %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}s"
+ about_x_months: "%{count}mj"
+ about_x_years: "%{count}g"
+ almost_x_years: "%{count}g"
+ half_a_minute: upravo
+ less_than_x_minutes: "%{count}m"
+ less_than_x_seconds: upravo
+ over_x_years: "%{count}g"
+ x_days: "%{count}d"
+ x_minutes: "%{count}m"
+ x_months: "%{count}mj"
+ x_seconds: "%{count}sek"
+ exports:
+ blocks: Blokirao si
+ csv: CSV
+ follows: Slijediš
+ storage: Pohrana media zapisa
+ generic:
+ changes_saved_msg: Izmjene su uspješno sačuvane!
+ powered_by: omogućuje %{link}
+ save_changes: Sačuvaj izmjene
+ validation_errors:
+ one: Nešto ne štima! Vidi grešku ispod
+ other: Nešto još uvijek ne štima! Vidi %{count} greške ispod
+ imports:
+ preface: Možeš uvesti određene podatke kao što su svi ljudi koje slijediš ili blokiraš u svoj račun na ovoj instanci, sa fajlova kreiranih izvozom sa druge instance.
+ success: Tvoji podaci su uspješno uploadani i bit će obrađeni u dogledno vrijeme
+ types:
+ blocking: Lista blokiranih
+ following: Lista onih koje slijedim
+ upload: Upload
+ landing_strip_html: %{name} je korisnik na %{domain}. Možeš ih slijediti ili komunicirati s njima ako imaš račun igdje u fediversu. Ako nemaš, možeš se registrirati ovdje.
+ notification_mailer:
+ digest:
+ body: 'Ovo je kratak sažetak propuštenog %{instance} od tvog prošlog posjeta %{since}:'
+ mention: "%{name} te je spomenuo:"
+ new_followers_summary:
+ one: Imaš novog sljedbenika! Yay!
+ other: Imaš %{count} novih sljedbenika! Prekrašno!
+ subject:
+ one: "1 nova notifikacija od tvog prošlog posjeta \U0001F418"
+ other: "%{count} novih notifikacija od tvog prošlog posjeta \U0001F418"
+ favourite:
+ body: 'Tvoj status je %{name} označio kao omiljen:'
+ subject: "%{name} je označio kao omiljen tvoj status"
+ follow:
+ body: "%{name} te sada slijedi!"
+ subject: "%{name} te sada slijedi"
+ follow_request:
+ body: "%{name} je zatražio da te slijedi"
+ subject: 'Sljedbenik na čekanju: %{name}'
+ mention:
+ body: 'Spomenuo te je %{name} in:'
+ subject: Spomenuo te je %{name}
+ reblog:
+ body: 'Tvoj status je potaknut od %{name}:'
+ subject: "%{name} je potakao tvoj status"
+ pagination:
+ next: Sljedeći
+ prev: Prošli
+ remote_follow:
+ acct: Unesi svoje username@domain sa koje želiš slijediti
+ missing_resource: Could not find the required redirect URL za tvoj račun
+ proceed: Nastavi slijediti
+ prompt: 'Slijediti ćeš:'
+ settings:
+ authorized_apps: Autorizirane aplikacije
+ back: Natrag na Mastodon
+ edit_profile: Uredi profil
+ export: Izvoz podataka
+ import: Uvezi
+ preferences: Postavke
+ settings: Podešenja
+ two_factor_auth: Dvo-faktorska Autentifikacija
+ statuses:
+ open_in_web: Otvori na webu
+ over_character_limit: prijeđen je limit od %{max} znakova
+ show_more: Prikaži više
+ visibilities:
+ private: Pokaži samo sljedbenicima
+ public: Javno
+ unlisted: Javno, no nemoj prikazati na javnom timelineu
+ stream_entries:
+ click_to_show: Klikni da bi prikazao
+ reblogged: potaknut
+ sensitive_content: Osjetljivi sadržaj
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ description_html: Ako omogućiš two-factor autentifikaciju, prijavljivanje će zahtjevati da kod sebe imaš svoj mobitel, koji će generirati tokene koje ćeš unijeti.
+ disable: Onemogući
+ enable: Omogući
+ instructions_html: "Skeniraj ovaj QR kod into Google Authenticator or a similiar app on your phone. Od sada, ta aplikacija će generirati tokene koje ćeš unijeti pri prijavljivanju."
+ plaintext_secret_html: 'Plain-text secret: %{secret}'
+ warning: Ako trenuno ne možeš konfigurirati authenticator app, trebaš kliknuti "onemogući" ili se nećeš moći prijaviti.
+ users:
+ invalid_email: E-mail adresa nije valjana
+ invalid_otp_token: Nevaljani dvo-faktorski kod
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/hu.yml b/config/locales/hu.yml
index 915d02c1..96b73d43 100644
--- a/config/locales/hu.yml
+++ b/config/locales/hu.yml
@@ -51,5 +51,3 @@ hu:
settings:
edit_profile: Profil szerkesztése
preferences: Beállítások
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
new file mode 100644
index 00000000..c1df73b7
--- /dev/null
+++ b/config/locales/ja.yml
@@ -0,0 +1,221 @@
+---
+ja:
+ about:
+ about_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: 1つの投稿は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:
+ settings:
+ click_to_edit: クリックして編集
+ contact_information:
+ email: 公開するメールアドレスを入力
+ label: 連絡先情報
+ username: ユーザー名を入力
+ registrations:
+ open:
+ disabled: 無効
+ enabled: 有効
+ title: 新規登録を受け付ける
+ setting: 設定
+ site_description:
+ desc_html: トップページへの表示と meta タグに使用されます。
HTMLタグ、特に<a>
and <em>
が利用可能です。
+ title: サイトの説明文
+ site_description_extended:
+ desc_html: インスタンスについてのページに表示されます。
HTMLタグが利用可能です。
+ title: サイトの詳細な説明
+ site_title: サイトのタイトル
+ title: サイト設定
+ application_mailer:
+ settings: 'メール設定の変更: %{link}'
+ signature: Mastodon %{instance} インスタンスからの通知
+ view: 'View:'
+ applications:
+ invalid_url: 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}年"
+ x_days: "%{count}日"
+ x_minutes: "%{count}分"
+ x_months: "%{count}月"
+ x_seconds: "%{count}秒"
+ errors:
+ '404': お探しのページは見つかりませんでした。
+ '410': お探しのページはもう存在しません。
+ '422':
+ content: セキュリティ認証に失敗しました。Cookieをブロックしていませんか?
+ title: セキュリティ認証に失敗
+ exports:
+ blocks: ブロック
+ csv: CSV
+ follows: フォロー
+ mutes: ミュート
+ storage: メディア
+ generic:
+ changes_saved_msg: 正常に変更されました
+ powered_by: powered by %{link}
+ save_changes: 変更を保存
+ validation_errors:
+ one: エラーが発生しました。以下のエラーを確認してください
+ other: エラーが発生しました。以下の%{count}個のエラーを確認してください
+ imports:
+ preface: 他のインスタンスでエクスポートされたファイルから、フォロー/ブロックした情報をこのインスタンス上のアカウントにインポートできます。
+ success: ファイルは正常にアップロードされ、現在処理中です。しばらくしてから確認してください
+ types:
+ blocking: ブロックしたアカウントリスト
+ following: フォロー中のアカウントリスト
+ muting: ミュートしたアカウントリスト
+ upload: アップロード
+ landing_strip_html: %{name} さんはインスタンス %{domain} のユーザーです。アカウントさえ持っていればフォローしたり会話したりできます。もしお持ちでないなら こちら からサインアップできます。
+ media_attachments:
+ validations:
+ images_and_video: 既に画像が追加されているため、動画を追加することはできません。
+ too_many: 追加できるファイルは4つまでです。
+ notification_mailer:
+ digest:
+ body: "%{instance} での最後のログインからの出来事:"
+ mention: "%{name} さんがあなたに返信しました:"
+ new_followers_summary:
+ one: 新たなフォロワーを獲得しました!
+ 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: 戻る
+ edit_profile: プロフィールを編集
+ export: データのエクスポート
+ import: データのインポート
+ preferences: ユーザー設定
+ settings: 設定
+ two_factor_auth: 二段階認証
+ statuses:
+ open_in_web: Webで開く
+ over_character_limit: 上限は %{max}文字までです
+ show_more: もっと見る
+ visibilities:
+ private: Private - フォロワーだけに見せる
+ public: Public - 全体に公開する
+ unlisted: 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か、もしくはほかのTOTPアプリでこのQRコードをスキャンしてください。これ以降、ログインするときはそのアプリで生成されるコードが必要になります。"
+ manual_instructions: 'QRコードがスキャンできず、手動での登録を希望の場合はこのシークレットコードを利用してください。:'
+ setup: 初期設定
+ warning: 現在認証アプリを設定できない場合、無効に設定して、有効にしないでください。
+ wrong_code: コードが間違っています。サーバー上の時間とデバイス上の時間が一致していることを確認してください。
+ users:
+ invalid_email: メールアドレスが無効です
+ invalid_otp_token: 二段階認証コードが間違っています
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
new file mode 100644
index 00000000..22fed228
--- /dev/null
+++ b/config/locales/nl.yml
@@ -0,0 +1,165 @@
+---
+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_this: Over deze server
+ apps: Apps
+ business_email: 'Zakelijke e-mailadres:'
+ closed_registrations: Registrateren op deze server is momenteel uitgeschakeld.
+ contact: Contact
+ description_headline: Wat is %{domain}?
+ domain_count_after: andere servers
+ domain_count_before: Verbonden met
+ features:
+ api: Open API voor apps en services
+ blocks: Rijke blokkeer- en dempingshulpmiddelen
+ 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
+ public: Openbare tijdlijnen
+ features_headline: Wat maak 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
+ user_count_after: gebruikers
+ user_count_before: Thuis naar
+ accounts:
+ follow: Volg
+ followers: Volgens
+ following: Volgend
+ nothing_here: Hier is niets!
+ people_followed_by: Mensen die %{name} volgt
+ people_who_follow: Mensen die %{name} volgen
+ posts: Berichten
+ remote_follow: Externe volg
+ unfollow: Ontvolgen
+ application_mailer:
+ settings: 'Wijzigen e-mailvoorkeuren: %{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?
+ forgot_password: Wachtwoord vergeten?
+ login: Inloggen
+ logout: Uitloggen
+ register: Registreren
+ resend_confirmation: Herstuur de bevestigingsinstructies
+ reset_password: Herstel wachtwoord
+ set_new_password: Instellen nieuw wachtwoord
+ authorize_follow:
+ error: Helaas, er was een fout bij het opzoeken van het externe account
+ follow: Volgen
+ prompt_html: 'Je (%{self}) hebt volgen aangevraagd:'
+ title: Volg %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count}u"
+ about_x_months: "%{count}ma"
+ about_x_years: "%{count}j"
+ almost_x_years: "%{count}j"
+ half_a_minute: Net
+ less_than_x_minutes: "%{count}m"
+ less_than_x_seconds: Net
+ over_x_years: "%{count}j"
+ x_days: "%{count}d"
+ x_minutes: "%{count}m"
+ x_months: "%{count}ma"
+ x_seconds: "%{count}s"
+ exports:
+ blocks: Je blokkeert
+ csv: CSV
+ follows: Je volgt
+ storage: Media-opslag
+ generic:
+ changes_saved_msg: Wijzigingen succesvol opgeslagen!
+ powered_by: powered by %{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
+ types:
+ blocking: Blokkadelijst
+ 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.
+ notification_mailer:
+ digest:
+ body: 'Hier is een korte samenvatting van wat je hebt gemist op %{instance} sinds je laatste bezoek op %{since}:'
+ mention: "%{name} vermeldde je in:"
+ new_followers_summary:
+ one: Je hebt een nieuwe volger! Hoera!
+ other: Je hebt %{count} nieuwe volgers! Prachtig!
+ subject:
+ 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"
+ follow:
+ body: "%{name} volgt je nu!"
+ subject: "%{name} volgt je nu"
+ follow_request:
+ body: "%{name} wil je graag volgend"
+ subject: 'Volgen in afwachting: %{name}'
+ mention:
+ body: 'Je werd door %{name} vermeld in:'
+ subject: Je werd vermeld door %{name}
+ reblog:
+ body: 'Je status werd geboost door %{name}:'
+ subject: "%{name} booste je status"
+ pagination:
+ next: Volgende
+ 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
+ proceed: Ga door om te volgen
+ prompt: 'Je gaat volgen:'
+ settings:
+ authorized_apps: Geautoriseerde
+ back: Terug naar Mastodon
+ edit_profile: Bewerk profiel
+ export: Gegevensexport
+ import: Import
+ preferences: Voorkeuren
+ settings: Instellingen
+ two_factor_auth: Twe-factor authenticatie
+ statuses:
+ open_in_web: Openen in web
+ over_character_limit: Tekenlimiet van %{max} overschreden
+ show_more: Toon meer
+ visibilities:
+ private: Toon alleen aan volgers
+ public: Openbaar
+ unlisted: Openbaar, maar niet tonen op openbare tijdlijn
+ stream_entries:
+ click_to_show: Klik om te tonen
+ reblogged: boostte
+ sensitive_content: Gevoelige inhoud
+ time:
+ 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.
+ 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."
+ 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.
+ users:
+ invalid_email: Het e-mailadres is ongeldig
+ invalid_otp_token: Ongeldige twe-factor code
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/no.yml b/config/locales/no.yml
new file mode 100644
index 00000000..d13d90c2
--- /dev/null
+++ b/config/locales/no.yml
@@ -0,0 +1,162 @@
+---
+'no':
+ about:
+ about_mastodon: Mastodon er et sosialt nettverk laget med fri programvare. Et desentralisert alternativ til kommersielle plattformer. Slik kan det unngå risikoene ved å ha et enkelt selskap som monopoliserer din kommunikasjon. Velg en tjener du stoler på — uansett hvilken du velger så kan du kommunisere med alle andre. Alle kan kjøre sin egen Mastodon og delta sømløst i det sosiale nettverket.
+ about_this: Om denne instansen
+ apps: Applikasjoner
+ business_email: 'Bedriftsepost:'
+ contact: Kontakt
+ description_headline: Hva er %{domain}?
+ domain_count_after: andre instanser
+ domain_count_before: Koblet til
+ features:
+ api: Åpent API for applikasjoner og tjenester
+ blocks: Rikholdige blokkeringsverktøy
+ characters: 500 tegn per status
+ chronology: Tidslinjer er kronologiske
+ ethics: 'Etisk design: Ingen reklame, ingen sporing'
+ gifv: Støtte for GIFV og korte videoer
+ privacy: Finmaskede personvernsinnstillinger
+ public: Forente tidslinjer
+ features_headline: Hva skiller Mastodon fra andre sosiale nettverk
+ get_started: Kom i gang
+ links: Lenker
+ other_instances: Andre instanser
+ source_code: Kildekode
+ status_count_after: statuser
+ status_count_before: Som skrev
+ terms: Betingelser
+ user_count_after: brukere
+ user_count_before: Her bor
+ accounts:
+ follow: Følg
+ followers: Følgere
+ following: Følger
+ nothing_here: Det er ingenting her!
+ people_followed_by: Folk som %{name} følger
+ people_who_follow: Folk som følger %{name}
+ posts: Poster
+ remote_follow: Følg fra andre instanser
+ unfollow: Avfølg
+ application_mailer:
+ settings: 'Endre foretrukne epost innstillinger: %{link}'
+ signature: Mastodon notiser fra %{instance}
+ view: 'Se:'
+ applications:
+ invalid_url: Den oppgitte URLen er ugyldig
+ auth:
+ change_password: Brukerdetaljer
+ didnt_get_confirmation: Fikk du ikke bekreftelsesmailen din?
+ forgot_password: Har du glemt passordet ditt?
+ login: Innlogging
+ logout: Logg ut
+ register: Bli med
+ resend_confirmation: Send bekreftelsesinstruksjoner på nytt
+ 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.
+ 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"
+ exports:
+ blocks: Du blokkerer
+ csv: CSV
+ follows: Du følger
+ storage: Media lagring
+ 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
+ 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.
+ 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.
+ 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}:'
+ mention: "%{name} nevnte deg i:"
+ new_followers_summary:
+ one: Du har fått en ny følger. Jippi!
+ other: Du har fått %{count} nye følgere! Imponerende!
+ subject:
+ one: "1 ny hendelse siden ditt siste besøk \U0001F418"
+ other: "%{count} nye hendelser siden ditt siste besøk \U0001F418"
+ favourite:
+ body: 'Din status ble likt av %{name}'
+ subject: "%{name} likte din status."
+ follow:
+ 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}'
+ 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"
+ pagination:
+ next: Neste
+ prev: Forrige
+ remote_follow:
+ 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:'
+ settings:
+ authorized_apps: Autoriserte applikasjoner
+ back: Tilbake til Mastodon
+ edit_profile: Endre profil
+ export: Data eksport
+ import: Importer
+ preferences: Foretrukne valg
+ settings: Innstillinger
+ two_factor_auth: To-faktor autentisering
+ statuses:
+ open_in_web: Åpne i nettleser
+ over_character_limit: tegngrense på %{max} overskredet
+ show_more: Vis mer
+ visibilities:
+ private: Vis kun til følgere
+ public: Offentlig
+ unlisted: Offentlig, men vis ikke på forent tidslinje
+ stream_entries:
+ click_to_show: Klikk for å vise
+ reblogged: reblogget
+ sensitive_content: Sensitivt 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.
+ 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"
+ 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.
+ users:
+ invalid_email: E-post addressen er ugyldig
+ invalid_otp_token: Ugyldig two-faktor kode
diff --git a/config/locales/pt.yml b/config/locales/pt.yml
index ad7d05e3..f2c7458f 100644
--- a/config/locales/pt.yml
+++ b/config/locales/pt.yml
@@ -51,5 +51,3 @@ pt:
settings:
edit_profile: Editar perfil
preferences: Preferências
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/ru.yml b/config/locales/ru.yml
new file mode 100644
index 00000000..ca73dd45
--- /dev/null
+++ b/config/locales/ru.yml
@@ -0,0 +1,168 @@
+---
+ru:
+ about:
+ about_mastodon: Mastodon - это свободная социальная сеть с открытым исходным кодом. Как децентрализованная альтернатива коммерческим платформам, Mastodon предотвращает риск монополизации Вашего общения одной компанией. Выберите сервер, которому Вы доверяете — что бы Вы ни выбрали, Вы сможете общаться со всеми остальными. Любой может запустить свой собственный узел Mastodon и участвовать в социальной сети совершенно бесшовно.
+ about_this: Об этом узле
+ apps: Приложения
+ business_email: 'Деловой e-mail:'
+ 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: Отписаться
+ application_mailer:
+ settings: 'Изменить настройки e-mail: %{link}'
+ signature: Уведомления Mastodon от %{instance}
+ view: 'Просмотр:'
+ applications:
+ invalid_url: Введенный 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}г"
+ x_days: "%{count}д"
+ x_minutes: "%{count}мин"
+ x_months: "%{count}мес"
+ x_seconds: "%{count}сек"
+ 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}. Вы можете подписаться на него/нее и общаться с ним/ней, если у Вас есть аккаунт на любом узле общей сети. Если у Вас его нет, вы можете зарегистрироваться здесь.
+ notification_mailer:
+ digest:
+ body: 'Кратко о пропущенном Вами на %{instance} с Вашего последнего захода %{since}:'
+ mention: "%{name} упомянул(а) Вас в:"
+ new_followers_summary:
+ one: У Вас появился новый подписчик! Ура!
+ 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: Пред
+ remote_follow:
+ acct: Введите username@domain, откуда Вы хотите подписаться
+ missing_resource: Поиск требуемого перенаправления URL для Вашего аккаунта завершился неудачей
+ proceed: Продолжить подписку
+ prompt: 'Вы хотите подписаться на:'
+ settings:
+ authorized_apps: Авторизованные приложения
+ back: Назад в Mastodon
+ edit_profile: Изменить профиль
+ export: Экспорт данных
+ import: Импорт
+ preferences: Настройки
+ settings: Опции
+ two_factor_auth: Двухфакторная аутентификация
+ statuses:
+ open_in_web: Открыть в WWW
+ over_character_limit: превышен лимит символов (%{max})
+ show_more: Подробнее
+ visibilities:
+ private: Показывать только подписчикам
+ public: Показывать всем
+ unlisted: Показывать всем, но не отображать в публичных лентах
+ stream_entries:
+ click_to_show: Показать
+ reblogged: продвинул(а)
+ sensitive_content: Чувствительный контент
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ code_hint: Для подтверждения введите код, сгенерированный приложением аутентификатора
+ description_html: При включении двухфакторной аутентификации, вход потребует от Вас использования Вашего телефона, который сгенерирует входные токены.
+ disable: Отключить
+ enable: Включить
+ instructions_html: "Отсканируйте этот QR-код с помощью Google Authenticator или другого подобного приложения на Вашем телефоне. С этого момента приложение будет генерировать токены, которые будет необходимо ввести для входа."
+ manual_instructions: 'Если Вы не можете отсканировать QR-код и хотите ввести его вручную, секрет представлен здесь открытым текстом:'
+ plaintext_secret_html: 'Секрет открытым текстом: %{secret}'
+ setup: Настроить
+ warning: Если сейчас у Вас не получается настроить аутентификатор, нажмите "отключить", иначе Вы не сможете войти!
+ users:
+ invalid_email: Введенный e-mail неверен
+ invalid_otp_token: Введен неверный код
+ will_paginate:
+ page_gap: "…"
diff --git a/config/locales/simple.form.hr.yml b/config/locales/simple.form.hr.yml
new file mode 100644
index 00000000..d34b4623
--- /dev/null
+++ b/config/locales/simple.form.hr.yml
@@ -0,0 +1,46 @@
+---
+hr:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF ili JPG. Najviše 2MB. Bit će smanjen na 120x120px
+ display_name: Najviše 30 znakova
+ header: PNG, GIF ili JPG. Najviše 2MB. Bit će smanjen na 700x335px
+ locked: traži te da ručno odobriš sljedbenike i postavlja privatnost postova na dostupnu samo sljedbenicima
+ note: Najviše 160 znakova
+ imports:
+ data: CSV fajl izvezen iz druge Mastodon instance
+ labels:
+ defaults:
+ avatar: Avatar
+ confirm_new_password: Potvrdi novu lozinku
+ confirm_password: Potvrdi lozinku
+ current_password: Trenutna lozinka
+ data: Podaci
+ display_name: Ime koje ću prikazati
+ email: E-mail adresa
+ header: Header
+ locale: Jezik
+ locked: Učini račun privatnim
+ new_password: Nova lozinka
+ note: Bio
+ otp_attempt: Dvo-faktorski kod
+ password: Lozinka
+ setting_default_privacy: Privatnost posta
+ type: Tip uvoženja
+ username: Korisničko ime
+ interactions:
+ must_be_follower: Blokiraj notifikacije onih koji me ne slijede
+ must_be_following: Blokiraj notifikacije ljudi koje ne slijedim
+ notification_emails:
+ digest: Šalji mi e-mailove s notifikacijama
+ favourite: Pošalji mi e-mail kad netko lajka moj status
+ follow: Pošalji mi e-mail kad me netko počne slijediti
+ follow_request: Pošalji mi e-mail kad mi netko pošalje zahtjev da me želi slijediti
+ mention: Pošalji mi e-mail kad me netko spomene
+ reblog: Pošalji mi e-mail kad netko rebloga moj status
+ 'no': 'Ne'
+ required:
+ mark: "*"
+ text: traženo
+ 'yes': 'Da'
diff --git a/config/locales/simple_form.bg.yml b/config/locales/simple_form.bg.yml
new file mode 100644
index 00000000..55b80277
--- /dev/null
+++ b/config/locales/simple_form.bg.yml
@@ -0,0 +1,46 @@
+---
+bg:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 120x120 пиксела
+ display_name: До 30 символа
+ header: PNG, GIF или JPG. До 2MB. Ще бъде смалена до 700x335 пиксела
+ locked: Изисква ръчно одобрение на последователите. По подразбиране, публикациите са достъпни само до последователи.
+ note: До 160 символа
+ imports:
+ data: CSV файл, експортиран от друга инстанция на Mastodon
+ labels:
+ defaults:
+ avatar: Аватар
+ confirm_new_password: Потвърди новата парола
+ confirm_password: Потвърди паролата
+ current_password: Текуща парола
+ data: Данни
+ display_name: Показвано име
+ email: E-mail адрес
+ 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: Изпращай e-mail, когато някой хареса твоя публикация
+ follow: Изпращай e-mail, когато някой те последва
+ follow_request: Изпращай e-mail, когато някой пожелае да те последва
+ mention: Изпращай e-mail, когато някой те спомене
+ reblog: Изпращай e-mail, когато някой сподели твоя публикация
+ 'no': 'Не'
+ required:
+ mark: "*"
+ text: задължително
+ 'yes': 'Да'
diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml
index 614cd491..d9de9ea3 100644
--- a/config/locales/simple_form.de.yml
+++ b/config/locales/simple_form.de.yml
@@ -3,7 +3,7 @@ de:
simple_form:
hints:
defaults:
- locked: Erlaubt dir, Folger zu überprüfen, bevor sie dir folgen können
+ locked: Erlaubt dir, Nutzer zu überprüfen, bevor sie dir folgen können
labels:
defaults:
avatar: Avatar
@@ -11,16 +11,16 @@ de:
confirm_password: Passwort bestätigen
current_password: Derzeitiges Passwort
display_name: Anzeigename
- email: E-mail-Addresse
+ email: E-Mail-Addresse
header: Kopfbild
locale: Sprache
- locked: Gesperrter Profil
+ locked: Gesperrtes Profil
new_password: Neues Passwort
note: Über mich
password: Passwort
username: Nutzername
interactions:
- must_be_follower: Benachrichtigungen von nicht-Folgern blockieren
+ must_be_follower: Benachrichtigungen von Nicht-Folgern blockieren
must_be_following: Benachrichtigungen von Nutzern blockieren, denen ich nicht folge
notification_emails:
favourite: E-mail senden, wenn jemand meinen Beitrag favorisiert
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index df4f6ca0..e66c6e38 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -26,6 +26,7 @@ en:
note: Bio
otp_attempt: Two-factor code
password: Password
+ setting_boost_modal: Show confirmation dialog before boosting
setting_default_privacy: Post privacy
type: Import type
username: Username
@@ -38,7 +39,7 @@ en:
follow: Send e-mail when someone follows you
follow_request: Send e-mail when someone requests to follow you
mention: Send e-mail when someone mentions you
- reblog: Send e-mail when someone reblogs your status
+ reblog: Send e-mail when someone boosts your status
'no': 'No'
required:
mark: "*"
diff --git a/config/locales/simple_form.eo.yml b/config/locales/simple_form.eo.yml
new file mode 100644
index 00000000..8c89a56e
--- /dev/null
+++ b/config/locales/simple_form.eo.yml
@@ -0,0 +1,46 @@
+---
+eo:
+ simple_form:
+ hints:
+ defaults:
+ avatar: En la formato PNG, GIF aŭ JPG. Ĝis 2Mo. Estos malgrandigita al 120x120px
+ display_name: 30 signoj pleje
+ header: En la formato PNG, GIF aŭ JPG. Ĝis 2Mo. Estos malgrandigita al 700x335px
+ locked: Vi devos aprobi ĉiun peton de sekvado, kaj viaj mesaĝoj estos senŝanĝe nur por viaj sekvantoj.
+ note: 160 signoj pleje
+ imports:
+ data: Dosiero CSV el alia aperaĵo de Mastodon
+ labels:
+ defaults:
+ avatar: Profilbildo
+ confirm_new_password: Konfirmi novan pasvorton
+ confirm_password: Konfirmi la pasvorton
+ current_password: Nuna pasvorto
+ data: Datumoj
+ display_name: Publika nomo
+ email: Retpoŝt-adreso
+ header: Kapbildo
+ locale: Lingvo
+ locked: Privatigi la konton
+ new_password: Nova pasvorto
+ note: Sinprezento
+ otp_attempt: Dufaktora identigilo
+ password: Pasvorto
+ setting_default_privacy: Videbleco de la mesaĝoj
+ type: Tipo de alportado
+ 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
+ notification_emails:
+ digest: Sendi resumajn retpoŝt-mesaĝojn
+ favourite: Sendi retpoŝt-mesaĝon, kiam iu favoras mesaĝon de vi
+ follow: Sendi retpoŝt-mesaĝon, kiam iu eksekvas vin
+ follow_request: Sendi retpoŝt-mesaĝon, kiam iu petas sekvi vin
+ mention: Sendi retpoŝt-mesaĝon, kiam iu mencias vin
+ reblog: Sendi retpoŝt-mesaĝon, kiam iu diskonigas mesaĝon de vi
+ 'no': 'Ne'
+ required:
+ mark: "*"
+ text: bezonata
+ 'yes': 'Jes'
diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml
index f1db48df..1c1afcdd 100644
--- a/config/locales/simple_form.es.yml
+++ b/config/locales/simple_form.es.yml
@@ -1,24 +1,40 @@
---
es:
simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF o JPG. Máximo 2MB. Será escalado a 120x120px
+ display_name: Máximo 30 caracteres
+ header: PNG, GIF o JPG. Máximo 2MB. Será escalado a 700x335px
+ locked: Requiere que manualmente apruebes seguidores y las publicaciones serán mostradas solamente a tus seguidores
+ note: Máximo 160 caracteres
+ imports:
+ data: Archivo CSV exportado desde otra instancia de Mastodon
labels:
defaults:
avatar: Avatar
confirm_new_password: Confirmar nueva contraseña
confirm_password: Confirmar contraseña
current_password: Contraseña actual
+ data: Información
display_name: Mostrar nombre
email: Dirección de correo electrónico
header: Img. cabecera
locale: Idioma
new_password: Nueva contraseña
note: Biografía
+ otp_attempt: Código de dos factores
password: Contraseña
+ setting_default_privacy: Privacidad de publicaciones
+ 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:
favourite: Enviar correo electrónico cuando alguien de a favorito en su publicación
- follow: Enviar correo electrónico cuando alguien le siga
- mention: Enviar correo electrónico cuando alguien le mencione
+ follow: Enviar correo electrónico cuando alguien te siga
+ mention: Enviar correo electrónico cuando alguien te mencione
reblog: Enviar correo electrónico cuando alguien comparta su publicación
'no': 'No'
required:
diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml
new file mode 100644
index 00000000..2bacd6d2
--- /dev/null
+++ b/config/locales/simple_form.fi.yml
@@ -0,0 +1,46 @@
+---
+fi:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 120x120px
+ display_name: Korkeintaan 30 merkkiä
+ header: PNG, GIF tai JPG. Korkeintaan 2MB. Skaalataan kokoon 700x335px
+ locked: Vaatii sinun manuaalisesti hyväksymään seuraajat ja asettaa julkaisujen yksityisyyden vain seuraajille
+ note: Korkeintaan 160 merkkiä
+ imports:
+ data: CSV tiedosto tuotu toiselta Mastodon palvelimelta
+ labels:
+ defaults:
+ avatar: Avatar
+ confirm_new_password: Varmista uusi salasana
+ confirm_password: Varmista salasana
+ current_password: Nykyinen salasana
+ data: Data
+ display_name: Näykyvä nimi
+ email: Sähköpostiosoite
+ header: Header
+ locale: Kieli
+ locked: Tee tilistä yksityinen
+ new_password: Uusi salasana
+ note: Bio
+ otp_attempt: Kaksivaiheinen koodi
+ password: Salasana
+ setting_default_privacy: Julkaisun yksityisyys
+ type: Tuonti tyyppi
+ username: Käyttäjänimi
+ interactions:
+ must_be_follower: Estä ilmoitukset käyttäjiltä jotka eivät seuraa sinua
+ must_be_following: Estä ilmoitukset käyttäjiltä joita et seuraa
+ notification_emails:
+ digest: Send digest e-mails
+ favourite: Lähetä s-posti kun joku tykkää statuksestasi
+ follow: Lähetä s-posti kun joku seuraa sinua
+ follow_request: Lähetä s-posti kun joku pyytää seurata sinua
+ mention: Lähetä s-posti kun joku mainitsee sinut
+ reblog: Lähetä s-posti kun joku buustaa julkaisusi
+ 'no': 'Ei'
+ required:
+ mark: "*"
+ text: vaaditaan
+ 'yes': 'Kyllä'
diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml
index fd037343..afcaeae8 100644
--- a/config/locales/simple_form.fr.yml
+++ b/config/locales/simple_form.fr.yml
@@ -33,7 +33,7 @@ fr:
must_be_follower: Masquer les notifications des personnes qui ne vous suivent pas
must_be_following: Masquer les notifications des personnes que vous ne suivez pas
notification_emails:
- digest: Envoyer des emails récapitulatifs
+ digest: Envoyer des courriels récapitulatifs
favourite: Envoyer un courriel lorsque quelqu’un ajoute mes statuts à ses favoris
follow: Envoyer un courriel lorsque quelqu’un me suit
follow_request: Envoyer un courriel lorsque quelqu'un demande à me suivre
diff --git a/config/locales/simple_form.ja.yml b/config/locales/simple_form.ja.yml
new file mode 100644
index 00000000..103001b7
--- /dev/null
+++ b/config/locales/simple_form.ja.yml
@@ -0,0 +1,47 @@
+---
+ja:
+ simple_form:
+ hints:
+ defaults:
+ avatar: 2MBまでのPNGやGIF、JPGが利用可能です。120x120pxまで縮小されます。
+ display_name: 名前は30文字まで設定することができます。
+ header: 2MBまでのPNGやGIF、JPGが利用可能です。 700x335pxまで縮小されます。
+ locked: フォロワーを手動で承認する必要があります。デフォルトではトゥートの公開範囲はフォロワーのみです。
+ note: プロフィールは160文字まで設定することができます。
+ imports:
+ data: 他の Mastodon サーバーからエクスポートした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_boost_modal: ブーストする前に確認ダイアログを表示する
+ 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/simple_form.nl.yml b/config/locales/simple_form.nl.yml
new file mode 100644
index 00000000..5bc38a87
--- /dev/null
+++ b/config/locales/simple_form.nl.yml
@@ -0,0 +1,46 @@
+---
+nl:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 120x120px
+ display_name: Maximaal 30 tekens
+ header: PNG, GIF of JPG. Maximaal 2MB. Wordt teruggeschaald naar 700x335px
+ locked: Vereist dat je handmatig volgers accepteert en stelt standaard plaatsen berichten privacy in op alleen-volgers
+ note: Maximaal 160 characters
+ imports:
+ data: CSV file geëxporteerd van een andere Mastodon server
+ labels:
+ defaults:
+ avatar: Avatar
+ confirm_new_password: Bevestig nieuw wachtwoord
+ confirm_password: Bevestig wachtwoord
+ current_password: Huidige wachtwoord
+ data: Gegevens
+ display_name: Weergavenaam
+ email: E-mailadres
+ header: Kop
+ locale: Taal
+ locked: Maak account besloten
+ new_password: Nieuwe wachtwoord
+ note: Bio
+ otp_attempt: Twee-factor code
+ password: Wachtwoord
+ setting_default_privacy: Berichten privacy
+ type: Import type
+ username: gebruikersnaam
+ interactions:
+ must_be_follower: Blokkeermeldingen van niet-volgers
+ must_be_following: Blokkeer meldingen van mensen die je niet volgt
+ notification_emails:
+ digest: Verstuur samenvattingse-mails
+ favourite: Verstuur een e-mail wanneer iemand je status als favoriet markeert
+ follow: Verstuur een e-mail wanneer iemand je volgt
+ follow_request: Verstuur een e-mail wanneer iemand je wil volgen
+ mention: Verstuur een e-mail wanneer iemand je vermeld
+ reblog: Verstuur een e-mail wanneer iemand je status boost
+ 'no': 'Nee'
+ required:
+ mark: "*"
+ text: vereist
+ 'yes': 'Ja'
diff --git a/config/locales/simple_form.no.yml b/config/locales/simple_form.no.yml
new file mode 100644
index 00000000..22df43e7
--- /dev/null
+++ b/config/locales/simple_form.no.yml
@@ -0,0 +1,47 @@
+---
+'no':
+ simple_form:
+ hints:
+ defaults:
+ 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
+ note: Maksimalt 160 tegn
+ imports:
+ data: CSV fil eksportert fra en annen Mastodon instans
+ labels:
+ defaults:
+ avatar: Avatar
+ 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
+ locale: Språk
+ locked: Endre konto til privat
+ new_password: Nytt passord
+ note: Biografi
+ otp_attempt: To-faktor kode
+ password: Passord
+ setting_boost_modal: Vis bekreftelsesdialog før reblogging
+ setting_default_privacy: Leserettigheter for poster
+ type: Importeringstype
+ username: Brukernavn
+ interactions:
+ must_be_follower: Blokker varslinger fra ikke-følgere
+ must_be_following: Blokker varslinger fra folk 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
+ required:
+ mark: "*"
+ text: påkrevd
+ 'yes': Ja
diff --git a/config/locales/simple_form.ru.yml b/config/locales/simple_form.ru.yml
new file mode 100644
index 00000000..b7d8e4e0
--- /dev/null
+++ b/config/locales/simple_form.ru.yml
@@ -0,0 +1,46 @@
+---
+ru:
+ 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, экспортированный с другого узла Mastodon
+ labels:
+ defaults:
+ avatar: Аватар
+ confirm_new_password: Повторите новый пароль
+ confirm_password: Повторите пароль
+ current_password: Текущий пароль
+ data: Данные
+ display_name: Показываемое имя
+ email: Адрес e-mail
+ header: Заголовок
+ locale: Язык
+ locked: Сделать аккаунт приватным
+ new_password: Новый пароль
+ note: О Вас
+ otp_attempt: Двухфакторный код
+ password: Пароль
+ setting_default_privacy: Видимость постов
+ type: Тип импорта
+ username: Имя пользователя
+ interactions:
+ must_be_follower: Заблокировать уведомления не от подписчиков
+ must_be_following: Заблокировать уведомления от людей, на которых Вы не подписаны
+ notification_emails:
+ digest: Присылать дайджест по e-mail
+ favourite: Уведомлять по e-mail, когда кому-то нравится Ваш статус
+ follow: Уведомлять по e-mail, когда кто-то подписался на Вас
+ follow_request: Уведомлять по e-mail, когда кто-то запрашивает разрешение на подписку
+ mention: Уведомлять по e-mail, когда кто-то упомянул Вас
+ reblog: Уведомлять по e-mail, когда кто-то продвинул Ваш статус
+ 'no': 'Нет'
+ required:
+ mark: "*"
+ text: обязательно
+ 'yes': 'Да'
diff --git a/config/locales/simple_form.zh-HK.yml b/config/locales/simple_form.zh-HK.yml
new file mode 100644
index 00000000..07d14522
--- /dev/null
+++ b/config/locales/simple_form.zh-HK.yml
@@ -0,0 +1,46 @@
+---
+zh-HK:
+ 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/uk.yml b/config/locales/uk.yml
index 27e8135d..f7176e86 100644
--- a/config/locales/uk.yml
+++ b/config/locales/uk.yml
@@ -51,5 +51,3 @@ uk:
settings:
edit_profile: Редагувати профіль
preferences: Налаштування
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml
index 78c4d46e..48028d00 100644
--- a/config/locales/zh-CN.yml
+++ b/config/locales/zh-CN.yml
@@ -150,5 +150,3 @@ zh-CN:
users:
invalid_email: 无效的邮箱
invalid_otp_token: 无效的两步验证码
- will_paginate:
- page_gap: "…"
diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml
new file mode 100644
index 00000000..a1c87a7e
--- /dev/null
+++ b/config/locales/zh-HK.yml
@@ -0,0 +1,172 @@
+---
+zh-HK:
+ 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: 取消關注
+ 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}秒"
+ 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: '你希望關注︰'
+ 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: 雙重認證確認碼不正確
diff --git a/config/navigation.rb b/config/navigation.rb
index 77556e5a..b92b8720 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -8,17 +8,17 @@ SimpleNavigation::Configuration.run do |navigation|
settings.item :profile, safe_join([fa_icon('user fw'), t('settings.edit_profile')]), settings_profile_url
settings.item :preferences, safe_join([fa_icon('sliders fw'), t('settings.preferences')]), settings_preferences_url
settings.item :password, safe_join([fa_icon('cog fw'), t('auth.change_password')]), edit_user_registration_url
- settings.item :two_factor_auth, safe_join([fa_icon('mobile fw'), t('settings.two_factor_auth')]), settings_two_factor_auth_url
+ settings.item :two_factor_auth, safe_join([fa_icon('mobile fw'), t('settings.two_factor_auth')]), settings_two_factor_auth_url, highlights_on: %r{/settings/two_factor_auth}
settings.item :import, safe_join([fa_icon('cloud-upload fw'), t('settings.import')]), settings_import_url
settings.item :export, safe_join([fa_icon('cloud-download fw'), t('settings.export')]), settings_export_url
settings.item :authorized_apps, safe_join([fa_icon('list fw'), t('settings.authorized_apps')]), oauth_authorized_applications_url
end
- primary.item :admin, safe_join([fa_icon('cogs fw'), 'Administration']), admin_accounts_url, if: proc { current_user.admin? } do |admin|
+ primary.item :admin, safe_join([fa_icon('cogs fw'), 'Administration']), admin_reports_url, if: proc { current_user.admin? } do |admin|
admin.item :reports, safe_join([fa_icon('flag fw'), 'Reports']), admin_reports_url, highlights_on: %r{/admin/reports}
admin.item :accounts, safe_join([fa_icon('users fw'), 'Accounts']), admin_accounts_url, highlights_on: %r{/admin/accounts}
admin.item :pubsubhubbubs, safe_join([fa_icon('paper-plane-o fw'), 'PubSubHubbub']), admin_pubsubhubbub_index_url
- admin.item :domain_blocks, safe_join([fa_icon('lock fw'), 'Domain Blocks']), admin_domain_blocks_url
+ admin.item :domain_blocks, safe_join([fa_icon('lock fw'), 'Domain Blocks']), admin_domain_blocks_url, highlights_on: %r{/admin/domain_blocks}
admin.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_url
admin.item :pghero, safe_join([fa_icon('database fw'), 'PgHero']), pghero_url
admin.item :settings, safe_join([fa_icon('cogs fw'), 'Site Settings']), admin_settings_url
diff --git a/config/puma.rb b/config/puma.rb
index 550129bd..191f00cc 100644
--- a/config/puma.rb
+++ b/config/puma.rb
@@ -9,7 +9,7 @@ preload_app!
on_worker_boot do
if ENV['HEROKU'] # Spawn the workers from Puma, to only use one dyno
- @sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q mailers -q push')
+ @sidekiq_pid ||= spawn('bundle exec sidekiq -q default -q push -q pull -q mailers ')
end
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
diff --git a/config/routes.rb b/config/routes.rb
index bfca5c73..6e48d3b9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,3 +1,4 @@
+
# frozen_string_literal: true
require 'sidekiq/web'
@@ -11,11 +12,11 @@ Rails.application.routes.draw do
end
use_doorkeeper do
- controllers authorizations: 'oauth/authorizations'
+ controllers authorizations: 'oauth/authorizations', authorized_applications: 'oauth/authorized_applications'
end
- get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta
- get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger
+ get '.well-known/host-meta', to: 'well_known/host_meta#show', as: :host_meta, defaults: { format: 'xml' }
+ get '.well-known/webfinger', to: 'well_known/webfinger#show', as: :webfinger, defaults: { format: 'json' }
devise_for :users, path: 'auth', controllers: {
sessions: 'auth/sessions',
@@ -53,16 +54,15 @@ Rails.application.routes.draw do
resource :preferences, only: [:show, :update]
resource :import, only: [:show, :create]
- resource :export, only: [:show] do
- collection do
- get :follows, to: 'exports#download_following_list'
- get :blocks, to: 'exports#download_blocking_list'
- end
+ resource :export, only: [:show]
+ namespace :exports, constraints: { format: :csv } do
+ resources :follows, only: :index, controller: :following_accounts
+ resources :blocks, only: :index, controller: :blocked_accounts
+ resources :mutes, only: :index, controller: :muted_accounts
end
- resource :two_factor_auth, only: [:show] do
+ resource :two_factor_auth, only: [:show, :new, :create] do
member do
- post :enable
post :disable
end
end
@@ -77,7 +77,7 @@ Rails.application.routes.draw do
namespace :admin do
resources :pubsubhubbub, only: [:index]
- resources :domain_blocks, only: [:index, :create]
+ resources :domain_blocks, only: [:index, :new, :create]
resources :settings, only: [:index, :update]
resources :reports, only: [:index, :show] do
@@ -90,12 +90,8 @@ Rails.application.routes.draw do
end
resources :accounts, only: [:index, :show] do
- member do
- post :silence
- post :unsilence
- post :suspend
- post :unsuspend
- end
+ resource :silence, only: [:create, :destroy]
+ resource :suspension, only: [:create, :destroy]
end
end
@@ -164,6 +160,7 @@ Rails.application.routes.draw do
collection do
get :relationships
get :verify_credentials
+ patch :update_credentials
get :search
end
@@ -189,11 +186,14 @@ Rails.application.routes.draw do
get '/web/(*any)', to: 'home#index', as: :web
- get '/about', to: 'about#index'
+ get '/about', to: 'about#show'
get '/about/more', to: 'about#more'
get '/terms', to: 'about#terms'
root 'home#index'
- match '*unmatched_route', via: :all, to: 'application#raise_not_found'
+ match '*unmatched_route',
+ via: :all,
+ to: 'application#raise_not_found',
+ format: false
end
diff --git a/config/settings.yml b/config/settings.yml
index 6ae9217a..d364120d 100644
--- a/config/settings.yml
+++ b/config/settings.yml
@@ -1,10 +1,20 @@
# config/app.yml for rails-settings-cached
+#
+# This file contains default values, and does not need to be edited
+# when configuring an instance. These settings may be changed by an
+# Administrator using the Web UI.
+#
+# For more information, see docs/Running-Mastodon/Administration-guide.md
+#
defaults: &defaults
site_title: 'Mastodon'
site_description: ''
site_extended_description: ''
site_contact_username: ''
site_contact_email: ''
+ open_registrations: true
+ closed_registrations_message: ''
+ boost_modal: true
notification_emails:
follow: false
reblog: false
@@ -15,6 +25,7 @@ defaults: &defaults
interactions:
must_be_follower: false
must_be_following: false
+
development:
<<: *defaults
diff --git a/db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb b/db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb
new file mode 100644
index 00000000..2d4e1219
--- /dev/null
+++ b/db/migrate/20170403172249_add_action_taken_by_account_id_to_reports.rb
@@ -0,0 +1,5 @@
+class AddActionTakenByAccountIdToReports < ActiveRecord::Migration[5.0]
+ def change
+ add_column :reports, :action_taken_by_account_id, :integer
+ end
+end
diff --git a/db/migrate/20170405112956_add_index_on_mentions_status_id.rb b/db/migrate/20170405112956_add_index_on_mentions_status_id.rb
new file mode 100644
index 00000000..3ed1a20c
--- /dev/null
+++ b/db/migrate/20170405112956_add_index_on_mentions_status_id.rb
@@ -0,0 +1,5 @@
+class AddIndexOnMentionsStatusId < ActiveRecord::Migration[5.0]
+ def change
+ add_index :mentions, :status_id
+ end
+end
diff --git a/db/migrate/20170406215816_add_notifications_and_favourites_indices.rb b/db/migrate/20170406215816_add_notifications_and_favourites_indices.rb
new file mode 100644
index 00000000..00e41bf3
--- /dev/null
+++ b/db/migrate/20170406215816_add_notifications_and_favourites_indices.rb
@@ -0,0 +1,7 @@
+class AddNotificationsAndFavouritesIndices < ActiveRecord::Migration[5.0]
+ def change
+ add_index :notifications, [:activity_id, :activity_type]
+ add_index :accounts, :url
+ add_index :favourites, :status_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5a9ca142..fe9b8dd4 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170330164118) do
+ActiveRecord::Schema.define(version: 20170406215816) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -49,6 +49,7 @@ ActiveRecord::Schema.define(version: 20170330164118) do
t.integer "following_count", default: 0, null: false
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
t.index ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
end
@@ -75,6 +76,7 @@ ActiveRecord::Schema.define(version: 20170330164118) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "status_id"], name: "index_favourites_on_account_id_and_status_id", unique: true, using: :btree
+ t.index ["status_id"], name: "index_favourites_on_status_id", using: :btree
end
create_table "follow_requests", force: :cascade do |t|
@@ -127,6 +129,8 @@ ActiveRecord::Schema.define(version: 20170330164118) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true, using: :btree
+ t.index ["status_id"], name: "index_mentions_on_status_id", using: :btree
+ t.index ["status_id"], name: "mentions_status_id_index", using: :btree
end
create_table "mutes", force: :cascade do |t|
@@ -145,6 +149,7 @@ ActiveRecord::Schema.define(version: 20170330164118) do
t.datetime "updated_at", null: false
t.integer "from_account_id"
t.index ["account_id", "activity_id", "activity_type"], name: "account_activity", unique: true, using: :btree
+ t.index ["activity_id", "activity_type"], name: "index_notifications_on_activity_id_and_activity_type", using: :btree
end
create_table "oauth_access_grants", force: :cascade do |t|
@@ -201,13 +206,14 @@ ActiveRecord::Schema.define(version: 20170330164118) do
end
create_table "reports", force: :cascade do |t|
- t.integer "account_id", null: false
- t.integer "target_account_id", null: false
- t.bigint "status_ids", default: [], null: false, array: true
- t.text "comment", default: "", null: false
- t.boolean "action_taken", default: false, null: false
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.integer "account_id", null: false
+ t.integer "target_account_id", null: false
+ t.bigint "status_ids", default: [], null: false, array: true
+ t.text "comment", default: "", null: false
+ t.boolean "action_taken", default: false, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "action_taken_by_account_id"
end
create_table "settings", force: :cascade do |t|
diff --git a/devise.it.yml b/devise.it.yml
new file mode 100644
index 00000000..813ccaba
--- /dev/null
+++ b/devise.it.yml
@@ -0,0 +1,61 @@
+---
+it:
+ devise:
+ confirmations:
+ confirmed: Il tuo indirizzo email è stato confermato con successo.
+ send_instructions: Riceverai in pochi minuti una email con istruzioni per confermare il tuo account al tuo indirizzo email.
+ send_paranoid_instructions: Se il tuo indirizzo email è presente nel nostro database, riceverai in pochi minuti una email con istruzioni per confermare il tuo account al tuo indirizzo email.
+ failure:
+ already_authenticated: Hai già effettuato l'accesso.
+ inactive: Non hai ancora attivato il tuo account.
+ invalid: %{authentication_keys} o password invalida.
+ last_attempt: Hai un altro tentativo prima che il tuo account venga bloccato.
+ locked: Il tuo account è stato bloccato.
+ not_found_in_database: %{authentication_keys} o password invalida.
+ timeout: La tua sessione è terminata. Per favore, effettua l'accesso o registrati per continuare.
+ unauthenticated: Devi effettuare l'accesso o registrarti per continuare.
+ unconfirmed: Devi confermare il tuo indirizzo email per continuare.
+ mailer:
+ confirmation_instructions:
+ subject: 'Mastodon: Istruzioni di conferma per %{instance}'
+ password_change:
+ subject: 'Mastodon: Password modificata'
+ reset_password_instructions:
+ subject: 'Mastodon: Istruzioni per il reset della password'
+ unlock_instructions:
+ subject: 'Mastodon: Istruzioni di sblocco'
+ omniauth_callbacks:
+ failure: Impossibile autenticarti da %{kind} perché "%{reason}".
+ success: Autenticato con successo con account %{kind}.
+ passwords:
+ no_token: Non puoi accedere a questa pagina senza essere stato reindirizzato da una mail di reset password. Se arrivi da una mail di reset password, per favore controlla di aver copiato per intero l'URL fornito.
+ send_instructions: Se la tua mail è presente nel nostro database, riceverai in pochi minuti un link per recuperare la tua password al tuo indirizzo mail.
+ send_paranoid_instructions: Se la tua mail è presente nel nostro database, riceverai in pochi minuti un link per recuperare la tua password al tuo indirizzo mail.
+ updated: La tua password è stata modificata con successo. Abbiamo già effettuato l'accesso per te.
+ updated_not_active: La tua password è stata modificata con successo.
+ registrations:
+ destroyed: Addio! Il tuo account è stato cancellato. Speriamo di rivederti presto.
+ signed_up: Benvenuto! Ti sei registrato con successo.
+ signed_up_but_inactive: Ti sei registrato con successo. Purtroppo però non possiamo farti accedere perché non hai ancora attivato il tuo account.
+ signed_up_but_locked: Ti sei registrato con successo. Purtroppo però non possiamo farti accedere perché il tuo account è bloccato.
+ signed_up_but_unconfirmed: Un messaggio con un link di conferma è stato inviato al tuo indirizzo email. Per favore, visita il link per attivare il tuo account.
+ update_needs_confirmation: Hai aggiornato correttamente il tuo account, ma abbiamo bisogno di verificare il tuo nuovo indirizzo email. Per favore, controlla la posta in arrivo e visita il link di conferma per verificare il tuo indirizzo email.
+ updated: Il tuo account è stato aggiornato con successo.
+ sessions:
+ already_signed_out: Sei uscito correttamente dal tuo account.
+ signed_in: Accesso eseguito correttamente.
+ signed_out: Sei uscito correttamente dal tuo account.
+ unlocks:
+ send_instructions: Riceverai in pochi minuti una email con istruzioni su come sbloccare il tuo account.
+ send_paranoid_instructions: Se il tuo account esiste, riceverai in pochi minuti una email con istruzioni su come sbloccarlo.
+ unlocked: Il tuo account è stato sbloccato con successo. Per favore, esegui l'accesso per continuare.
+ errors:
+ messages:
+ already_confirmed: era già stata confermata; per favore, prova ad eseguire l'accesso
+ confirmation_period_expired: deve essere confermata entro %{period}; per favore, richiedine una nuova
+ expired: è terminata; per favore, richiedine una nuova
+ not_found: non trovato
+ not_locked: non era stato bloccato
+ not_saved:
+ one: '1 errore ha impedito che %{resource} venisse salvato:'
+ other: "%{count} errori hanno impedito che %{resource} venisse salvato:"
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 68c8ef96..910bf8cf 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,11 +1,20 @@
version: '2'
services:
+
db:
restart: always
image: postgres:alpine
+### Uncomment to enable DB persistance
+# volumes:
+# - ./postgres:/var/lib/postgresql/data
+
redis:
restart: always
image: redis:alpine
+### Uncomment to enable REDIS persistance
+# volumes:
+# - ./redis:/data
+
web:
restart: always
build: .
@@ -19,6 +28,7 @@ services:
volumes:
- ./public/assets:/mastodon/public/assets
- ./public/system:/mastodon/public/system
+
streaming:
restart: always
build: .
@@ -29,11 +39,12 @@ services:
depends_on:
- db
- redis
+
sidekiq:
restart: always
build: .
env_file: .env.production
- command: bundle exec sidekiq -q default -q mailers -q push
+ command: bundle exec sidekiq -q default -q mailers -q pull -q push
depends_on:
- db
- redis
diff --git a/docs/Contributing-to-Mastodon/Sponsors.md b/docs/Contributing-to-Mastodon/Sponsors.md
index 47579168..8fc22b6b 100644
--- a/docs/Contributing-to-Mastodon/Sponsors.md
+++ b/docs/Contributing-to-Mastodon/Sponsors.md
@@ -1,46 +1 @@
-Sponsors
-========
-
-These people make the development of Mastodon possible through [Patreon](https://www.patreon.com/user?u=619786):
-
-**Extra special Patrons**
-
-- [World'sTallestLadder](https://mastodon.social/users/carcinoGeneticist)
-- [Jimmy Tidey](https://mastodon.social/users/jimmytidey)
-- [Kurtis Rainbolt-Greene](https://mastodon.social/users/krainboltgreene)
-- [Kit Redgrave](https://socially.constructed.space/users/KitRedgrave)
-- [Zeipher](https://mastodon.social/users/Zeipher)
-- [Effy Elden](https://toot.zone/users/effy)
-- [Zoë Quinn](https://mastodon.social/users/zoequinn)
-
-**Thank you to the following people**
-
-- [Harris Bomberguy](https://mastodon.social/users/Hbomberguy)
-- [Edward Saperia](https://nwspk.com)
-- [Yoz Grahame](http://yoz.com/)
-- [Jenn Kaplan](https://gay.crime.team/users/jkap)
-- [Natalie Weizenbaum](https://mastodon.social/users/nex3)
-- [Matteo De Micheli](http://matteodem.ch/)
-- [BirdMachine](https://mastodon.social/users/BirdMachine)
-- [Jessica Hayley](https://mastodon.social/users/jayhay)
-- [Niels Roesen Abildgaard](http://hypesystem.dk/)
-- [Zatnosk](https://github.com/Zatnosk)
-- [Spex Bluefox](https://mastodon.social/users/Spex)
-- [J. C. Holder](http://jcholder.com/)
-- [glocal](https://mastodon.social/users/glocal)
-- [jk](https://mastodon.social/users/jk)
-- [C418](https://mastodon.social/users/C418)
-- [halcy](https://icosahedron.website/users/halcy)
-- [Extropic](https://gnusocial.no/extropic)
-- [Pat Monaghan](http://iwrite.software/)
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
-- TBD
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Contributing-to-Mastodon/Sponsors.md)
diff --git a/docs/Contributing-to-Mastodon/Translating.md b/docs/Contributing-to-Mastodon/Translating.md
index 7319375e..1671645e 100644
--- a/docs/Contributing-to-Mastodon/Translating.md
+++ b/docs/Contributing-to-Mastodon/Translating.md
@@ -1,48 +1 @@
-Translating
-===========
-
-If you want to localise Mastodon into your language, here is how.
-
-There are two parts to Mastodon, the server and the web client. The translations for the web client are in `app/assets/javascripts/components/locales`. For the server-side, the translations live in `config/locales` and are divided into different files. Here are all the files you’ll need to translate:
-
-| Original file (English) | Location | Description |
-|---|---|---|
-| [`en.jsx`](/app/assets/javascripts/components/locales/en.jsx) | `app/assets/javascripts/components/locales/en.jsx` | Strings for the web client |
-| [`en.yml`](/config/locales/en.yml) | `config/locales/en.yml` | Strings for general use |
-| [`simple_form.en.yml`](/config/locales/simple_form.en.yml) | `config/locales/simple_form.en.yml` | Strings for the settings area |
-| [`devise.en.yml`](/config/locales/devise.en.yml) | `config/locales/devise.en.yml` | Generic strings for Devise |
-| [`doorkeeper.en.yml`](/config/locales/doorkeeper.en.yml) | `config/locales/doorkeeper.en.yml` | Generic strings for Doorkeeper |
-
-## Translating
-
-If you use Github, first clone the Mastodon repository to your account.
-
-1. Duplicate the files in their folder and replace `en` in the filenames by your language’s standard two-letters code ([ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes)).
- For instance `simple_form.en.yml` becomes `simple_form.es.yml` in the Spanish translation.
-2. Also replace the language code in the first lines of all the files, and the last line of the `.jsx` file.
-3. Translate the right-side values from English to your language. Keep the indentation and punctuation.
-
-Since Devise and Doorkeeper are popular libraries, there may already be translation files for your language available on the Internet.
-
-## Declaring the language
-
-The locales are mentioned in several other files. To activate your translation, add your language code to the different lists present in these files:
-
-| File | Location | Comment |
-|---|---|---|
-| [`index.jsx`](/app/assets/javascripts/components/locales/index.jsx) | `app/assets/javascripts/components/locales/index.jsx` | 2 lines to add |
-|[`mastodon.jsx`](/app/assets/javascripts/components/containers/mastodon.jsx) | `app/assets/javascripts/components/containers/mastodon.jsx` | 1 line to add + 1 list to complete |
-| [`settings_helper.rb`](/app/helpers/settings_helper.rb) | `app/helpers/settings_helper.rb` | 1 line to add + your language’s name |
-| [`application.rb`](/config/application.rb) | `config/application.rb` | 1 list to complete |
-
-## Sending the translation
-
-You can then push the files to git and submit a pull request.
-
-## Testing the translation
-
-Once the pull request is accepted, wait for the code to be deployed on a Mastodon instance. Log-in with your account there, and change the locale in the settings. Browse and use the website. See if everything makes sense in context and if anything seems out of place or breaks the layout. Invite other Mastodon users speaking your language to try it and give feedback. Make changes accordingly and update the translation.
-
-## Updating the translation
-
-Keep an eye on the original English files in `app/assets/javascripts/components/locales` and `config/locales`. When they are updated, pass on the changes to your language files. For new strings, add the new lines to the same position and translate them. Once you’re finished with the updates, you can submit a new pull request.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Contributing-to-Mastodon/Translating.md)
diff --git a/docs/Extensions.md b/docs/Extensions.md
index cf79379e..be4c6eab 100644
--- a/docs/Extensions.md
+++ b/docs/Extensions.md
@@ -1,50 +1 @@
-Protocol extensions
-===================
-
-Some functionality in Mastodon required some additions to the protocols to enable seamless federation of those features:
-
-### Federation of blocks/unblocks
-
-ActivityStreams was lacking verbs for block/unblock. Mastodon creates Salmon slaps for block and unblock events, which are not part of a user's public feed, but are nevertheless delivered to the target user. The intent of these Salmon slaps is not to notify the target user, but to notify the target user's server, so that it can perform any number of UX-related tasks such as removing the target user as a follower of the blocker, and/or displaying a message to the target user such as "You can't follow this person because you've been blocked"
-
-The Salmon slaps have the exact same structure as standard follow/unfollow slaps, the verbs are namespaced:
-
-- `http://mastodon.social/schema/1.0/block`
-- `http://mastodon.social/schema/1.0/unblock`
-
-### Federation of sensitive material
-
-Statuses can be marked as containing sensitive (or not safe for work) media. This is symbolized by a `` on the Atom entry
-
-### Federation of privacy features
-#### Locked accounts and status privacy levels
-
-Accounts and statuses have an access "scope":
-
-Accounts can be "private" or "public". The former requires a follow request to be approved before a follow relationship can be established, the latter can be followed directly.
-
-Statuses can be "private", "unlisted" or "public". Private must only be shown to the followers of the account or people mentioned in the status; public can be displayed publicly. Unlisted statuses may be displayed publicly but preferably outside of any spotlights e.g. "whole known network" or "public" timelines.
-
-Namespace of the scope element is `http://mastodon.social/schema/1.0`. Example:
-
-```xml
-
-
-
-
- private
-
-
- private
-
-```
-
-#### Follow requests
-
-Mastodon uses the following Salmon slaps to signal a follow request, a follow request authorization and a follow request rejection:
-
-- `http://activitystrea.ms/schema/1.0/request-friend`
-- `http://activitystrea.ms/schema/1.0/authorize`
-- `http://activitystrea.ms/schema/1.0/reject`
-
-The activity object of the request-friend slap is the account in question. The activity object of the authorize and reject slaps is the original request-friend activity. Request-friend slap is sent to the locked account, when the end-user of that account decides, the authorize/reject decision slap is sent back to the requester.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Extensions.md)
diff --git a/docs/README.md b/docs/README.md
index abf6fcc4..63bcf5a2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,36 +1 @@
-Index
-=====
-
-**Mastodon** is a free, open-source GNU social-compatible social network server. A decentralized alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly.
-
-### Using Mastodon
-- [Frequently Asked Questions](Using-Mastodon/FAQ.md)
-- [List of Mastodon instances](Using-Mastodon/List-of-Mastodon-instances.md)
-- [Apps](Using-Mastodon/Apps.md)
-- [User Guide](Using-Mastodon/User-guide.md)
-
-### Using the API
-- [API documentation](Using-the-API/API.md)
-- [Streaming API documentation](Using-the-API/Streaming-API.md)
-- [Testing the API with cURL](Using-the-API/Testing-with-cURL.md)
-- [OAuth details](Using-the-API/OAuth-details.md)
-- [Tips for app developers](Using-the-API/Tips-for-app-developers.md)
-- [Push notifications](Using-the-API/Push-notifications.md)
-
-### Running Mastodon
-- [Production guide](Running-Mastodon/Production-guide.md)
-- [Alternative: Running on Heroku](Running-Mastodon/Heroku-guide.md)
-- [Development guide](Running-Mastodon/Development-guide.md)
-- [Alternative: Development with Vagrant](Running-Mastodon/Vagrant-guide.md)
-- [Administration guide](Running-Mastodon/Administration-guide.md)
-- [Tuning Mastodon](Running-Mastodon/Tuning.md)
-
-### Contributing to Mastodon
-- [Sponsors](Contributing-to-Mastodon/Sponsors.md)
-- [Translate Mastodon in your language](Contributing-to-Mastodon/Translating.md)
-- [Report bugs and submit ideas](https://github.com/tootsuite/mastodon/issues)
-
-### Protocols
-
-- [List of used specs and RFCs for the federation](Specs-and-RFCs-used.md)
-- [Extensions of the above protocols](Extensions.md)
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/README.md)
diff --git a/docs/Running-Mastodon/Administration-guide.md b/docs/Running-Mastodon/Administration-guide.md
index af78f623..e7571be3 100644
--- a/docs/Running-Mastodon/Administration-guide.md
+++ b/docs/Running-Mastodon/Administration-guide.md
@@ -1,37 +1 @@
-Administration guide
-====================
-
-So, you have a working Mastodon instance... now what?
-
-## Turning into an admin
-
-The following rake task:
-
- rails mastodon:make_admin USERNAME=alice
-
-Would turn the local user "alice" into an admin.
-
-## Administration web interface
-
-A user that is designated as `admin = TRUE` in the database is able to access a suite of administration tools:
-
-* View, edit, silence, or suspend users - https://yourmastodon.instance/admin/accounts
-* View PubSubHubbub subscriptions - https://yourmastodon.instance/admin/pubsubhubbub
-* View domain blocks - https://yourmastodon.instance/admin/domain_blocks
-* Sidekiq dashboard - https://yourmastodon.instance/sidekiq
-* PGHero dashboard for PostgreSQL - https://yourmastodon.instance/pghero
-* Edit site settings - https://yourmastodon.instance/admin/settings
-
-## Site settings
-
-Your site settings are stored in the `settings` database table, and editable through the admin interface at https://yourmastodon.instance/admin/settings.
-
-You are able to set the following settings:
-
-- Site title
-- Contact username
-- Contact email
-- Site description
-- Site extended description
-
-You may wish to use the extended description (shown at https://yourmastodon.instance/about/more ) to display content guidelines or a user agreement (see https://mastodon.social/about/more for an example).
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Administration-guide.md)
diff --git a/docs/Running-Mastodon/Development-guide.md b/docs/Running-Mastodon/Development-guide.md
index 27efa346..10ed6408 100644
--- a/docs/Running-Mastodon/Development-guide.md
+++ b/docs/Running-Mastodon/Development-guide.md
@@ -1,50 +1 @@
-Development guide
-=================
-
-**Don't use Docker to do development**. It's a quick way to get Mastodon running in production, it's **really really inconvenient for development**. Normally in Rails development environment you get hot reloading of backend code and on-the-fly compilation of assets like JS and CSS, but you lose those benefits by compiling a Docker image. If you want to contribute to Mastodon, it is worth it to simply set up a proper development environment.
-
-In fact, all you need is described in the [production guide](Production-guide.md), **with the following exceptions**. You **don't** need:
-
-- Nginx
-- SystemD
-- An `.env.production` file. If you need to set any environment variables, you can use an `.env` file
-- To prefix any commands with `RAILS_ENV=production` since the default environment is "development" anyway
-- Any cronjobs
-
-The command to install project dependencies does not require any flags, i.e. simply
-
- bundle install
-
-By default the development environment wants to connect to a `mastodon_development` database on localhost using your user/ident to login to Postgres (i.e. not a md5 password)
-
-You can run Mastodon with:
-
- rails s
-
-And open `http://localhost:3000` in your browser. Background jobs run inline (aka synchronously) in the development environment, so you don't need to run a Sidekiq process.
-
-By default, your development environment will have an admin account created for you to use - the email address will be `admin@YOURDOMAIN` (e.g. admin@localhost:3000) and the password will be `mastodonadmin`.
-
-You can run tests with:
-
- rspec
-
-You can check localization status with:
-
- i18n-tasks health
-
-You can check code quality with:
-
- rubocop
-
-## Development tips
-
-You can use a localhost->world tunneling service like ngrok if you want to test federation, **however** that should not be your primary mode of operation. If you want to have a permanently federating server, set up a proper instance on a VPS with a domain name, and simply keep it up to date with your own fork of the project while doing development on localhost.
-
-Ngrok and similar services give you a random domain on each start up. This is good enough to test how the code you're working on handles real-world situations. But as soon as your domain changes, for everybody else concerned you're a different instance than before.
-
-Generally, federation bits are tricky to work on for exactly this reason - it's hard to test. And when you are testing with a disposable instance you are polluting the databases of the real servers you're testing against, usually not a big deal but can be annoying. The way I have handled this so far was thus: I have used ngrok for one session, and recorded the exchanges from its web interface to create fixtures and test suites. From then on I've been working with those rather than live servers.
-
-I advise to study the existing code and the RFCs before trying to implement any federation-related changes. It's not *that* difficult, but I think "here be dragons" applies because it's easy to break.
-
-If your development environment is running remotely (e.g. on a VPS or virtual machine), setting the `REMOTE_DEV` environment variable will swap your instance from using "letter opener" (which launches a local browser) to "letter opener web" (which collects emails and displays them at /letter_opener ).
\ No newline at end of file
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Development-guide.md)
diff --git a/docs/Running-Mastodon/Heroku-guide.md b/docs/Running-Mastodon/Heroku-guide.md
index 799b8a64..aa5abc1f 100644
--- a/docs/Running-Mastodon/Heroku-guide.md
+++ b/docs/Running-Mastodon/Heroku-guide.md
@@ -1,13 +1 @@
-Heroku guide
-============
-
-[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?button-url=https://github.com/tootsuite/mastodon&template=https://github.com/tootsuite/mastodon)
-
-Mastodon can theoretically run indefinitely on a free [Heroku](https://heroku.com) app. It should be noted this has limited testing and could have unpredictable results.
-
-1. Click the above button.
-2. Fill in the options requested.
- * You can use a .herokuapp.com domain, which will be simple to set up, or you can use a custom domain. If you want a custom domain and HTTPS, you will need to upgrade to a paid plan (to use Heroku's SSL features), or set up [CloudFlare](https://cloudflare.com) who offer free "Flexible SSL" (note: CloudFlare have some undefined limits on WebSockets. So far, no one has reported hitting concurrent connection limits).
- * You will want Amazon S3 for file storage. The only exception is for development purposes, where you may not care if files are not saaved. Follow a guide online for creating a free Amazon S3 bucket and Access Key, then enter the details.
- * If you want your Mastodon to be able to send emails, configure SMTP settings here (or later). Consider using [Mailgun](https://mailgun.com) or similar, who offer free plans that should suit your interests.
-3. Deploy! The app should be set up, with a working web interface and database. You can change settings and manage versions from the Heroku dashboard.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md)
diff --git a/docs/Running-Mastodon/Production-guide.md b/docs/Running-Mastodon/Production-guide.md
index f0dd7bd2..08649e9c 100644
--- a/docs/Running-Mastodon/Production-guide.md
+++ b/docs/Running-Mastodon/Production-guide.md
@@ -1,235 +1 @@
-Production guide
-================
-
-## Nginx
-
-Regardless of whether you go with the Docker approach or not, here is an example Nginx server configuration:
-
-```nginx
-map $http_upgrade $connection_upgrade {
- default upgrade;
- '' close;
-}
-
-server {
- listen 443 ssl;
- server_name example.com;
-
- ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
- ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
-
- keepalive_timeout 70;
- sendfile on;
- client_max_body_size 0;
- gzip off;
-
- root /home/mastodon/live/public;
-
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
-
- location / {
- try_files $uri @proxy;
- }
-
- location @proxy {
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto https;
-
- proxy_pass_header Server;
-
- proxy_pass http://localhost:3000;
- proxy_buffering off;
- proxy_redirect off;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
-
- tcp_nodelay on;
- }
-
- location /api/v1/streaming {
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto https;
-
- proxy_pass http://localhost:4000;
- proxy_buffering off;
- proxy_redirect off;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
-
- tcp_nodelay on;
- }
-
- error_page 500 501 502 503 504 /500.html;
-}
-```
-
-## Running in production without Docker
-
-It is recommended to create a special user for mastodon on the server (you could call the user `mastodon`), though remember to disable outside login for it. You should only be able to get into that user through `sudo su - mastodon`.
-
-## General dependencies
-
- curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
- sudo apt-get install imagemagick ffmpeg libpq-dev libxml2-dev libxslt1-dev nodejs
- sudo npm install -g yarn
-
-## Redis
-
- sudo apt-get install redis-server redis-tools
-
-## Postgres
-
- sudo apt-get install postgresql postgresql-contrib
-
-Setup a user and database for Mastodon:
-
- sudo su - postgres
- psql
-
-In the prompt:
-
- CREATE USER mastodon CREATEDB;
- \q
-
-## Rbenv
-
-It is recommended to use rbenv (exclusively from the `mastodon` user) to install the desired Ruby version. Follow the guides to [install rbenv][1] and [rbenv-build][2] (I recommend checking the [prerequisites][3] for your system on the rbenv-build project and installing them beforehand, obviously outside the unprivileged `mastodon` user)
-
-[1]: https://github.com/rbenv/rbenv#installation
-[2]: https://github.com/rbenv/ruby-build#installation
-[3]: https://github.com/rbenv/ruby-build/wiki#suggested-build-environment
-
-Then once `rbenv` is ready, run `rbenv install 2.3.1` to install the Ruby version for Mastodon.
-
-## Git
-
-You need the `git-core` package installed on your system. If it is so, from the `mastodon` user:
-
- cd ~
- git clone https://github.com/Gargron/mastodon.git live
- cd live
-
-Then you can proceed to install project dependencies:
-
- gem install bundler
- bundle install --deployment --without development test
- yarn install
-
-## Configuration
-
-Then you have to configure your instance:
-
- cp .env.production.sample .env.production
- nano .env.production
-
-Fill in the important data, like host/port of the redis database, host/port/username/password of the postgres database, your domain name, SMTP details (e.g. from Mailgun or equivalent transactional e-mail service, many have free tiers), whether you intend to use SSL, etc. If you need to generate secrets, you can use:
-
- rake secret
-
-To get a random string. If you are setting up on one single server (most likely), then REDIS_HOST is localhost and `DB_HOST` is `/var/run/postgresql`, `DB_USER` is `mastodon` and `DB_NAME` is `mastodon_production` while `DB_PASS` is empty because this setup will use the ident authentication method (system user "mastodon" maps to postgres user "mastodon").
-
-## Setup
-
-And setup the database for the first time, this will create the tables and basic data:
-
- RAILS_ENV=production bundle exec rails db:setup
-
-Finally, pre-compile all CSS and JavaScript files:
-
- RAILS_ENV=production bundle exec rails assets:precompile
-
-## Systemd
-
-Example systemd configuration for the web workers, to be placed in `/etc/systemd/system/mastodon-web.service`:
-
-```systemd
-[Unit]
-Description=mastodon-web
-After=network.target
-
-[Service]
-Type=simple
-User=mastodon
-WorkingDirectory=/home/mastodon/live
-Environment="RAILS_ENV=production"
-Environment="PORT=3000"
-ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
-TimeoutSec=15
-Restart=always
-
-[Install]
-WantedBy=multi-user.target
-```
-
-Example systemd configuration for the background workers, to be placed in `/etc/systemd/system/mastodon-sidekiq.service`:
-
-```systemd
-[Unit]
-Description=mastodon-sidekiq
-After=network.target
-
-[Service]
-Type=simple
-User=mastodon
-WorkingDirectory=/home/mastodon/live
-Environment="RAILS_ENV=production"
-Environment="DB_POOL=5"
-ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q push
-TimeoutSec=15
-Restart=always
-
-[Install]
-WantedBy=multi-user.target
-```
-
-Example systemd configuration file for the streaming API, to be placed in `/etc/systemd/system/mastodon-streaming.service`:
-
-```systemd
-[Unit]
-Description=mastodon-streaming
-After=network.target
-
-[Service]
-Type=simple
-User=mastodon
-WorkingDirectory=/home/mastodon/live
-Environment="NODE_ENV=production"
-Environment="PORT=4000"
-ExecStart=/usr/bin/npm run start
-TimeoutSec=15
-Restart=always
-
-[Install]
-WantedBy=multi-user.target
-```
-
-This allows you to `sudo systemctl enable /etc/systemd/system/mastodon-*.service` and `sudo systemctl start mastodon-web.service mastodon-sidekiq.service mastodon-streaming.service` to get things going.
-
-## Cronjobs
-
-I recommend creating a couple cronjobs for the following tasks:
-
-- `RAILS_ENV=production bundle exec rake mastodon:media:clear`
-- `RAILS_ENV=production bundle exec rake mastodon:push:refresh`
-- `RAILS_ENV=production bundle exec rake mastodon:feeds:clear`
-
-You may want to run `which bundle` first and copypaste that full path instead of simply `bundle` in the above commands because cronjobs usually don't have all the paths set. The time and intervals of when to run these jobs are up to you, but once every day should be enough for all.
-
-You can edit the cronjob file for the `mastodon` user by running `sudo crontab -e mastodon` (outside of the mastodon user).
-
-## Things to look out for when upgrading Mastodon
-
-You can upgrade Mastodon with a `git pull` from the repository directory. You may need to run:
-
-- `RAILS_ENV=production bundle exec rails db:migrate`
-- `RAILS_ENV=production bundle exec rails assets:precompile`
-
-Depending on which files changed, e.g. if anything in the `/db/` or `/app/assets` directory changed, respectively. Also, Mastodon runs in memory, so you need to restart it before you see any changes. If you're using systemd, that would be:
-
- sudo systemctl restart mastodon-*.service
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Production-guide.md)
diff --git a/docs/Running-Mastodon/Scalingo-guide.md b/docs/Running-Mastodon/Scalingo-guide.md
new file mode 100644
index 00000000..8c986f75
--- /dev/null
+++ b/docs/Running-Mastodon/Scalingo-guide.md
@@ -0,0 +1 @@
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Scalingo-guide.md)
diff --git a/docs/Running-Mastodon/Tuning.md b/docs/Running-Mastodon/Tuning.md
index c4acb992..5e547419 100644
--- a/docs/Running-Mastodon/Tuning.md
+++ b/docs/Running-Mastodon/Tuning.md
@@ -1,104 +1 @@
-Tuning Mastodon
-===============
-
-Mastodon has three types of processes:
-
-- web
-- streaming API
-- background processing
-
-By default, the web type spawns two worker processes with 5 threads each, the streaming API is a single thread/process with 10 database pool connections, and background processing spawns one process with 5 threads.
-
-### Web
-
-The web process serves short-lived HTTP requests for most of the application. The following environment variables control it:
-
-- `WEB_CONCURRENCY` controls the number of worker processes
-- `MAX_THREADS` controls the number of threads per process
-
-The default is 2 workers with 5 threads each. Threads share the memory of their parent process. Different processes allocate their own memory each. Threads in Ruby are not native threads, so it's more or less: threads equal concurrency, processes equal parallelism. A larger number of threads maxes out your CPU first, a larger number of processes maxes out your RAM first.
-
-These values affect how many HTTP requests can be served at the same time. When not enough threads are available, requests are queued until they can be answered.
-
-For a single-user instance, 1 process with 5 threads should be more than enough.
-
-### Streaming API
-
-The streaming API handles long-lived HTTP and WebSockets connections, through which clients receive real-time updates. It is a single-threaded process. By default it has a database connection pool of 10, which means 10 different database queries can run *at the same time*. The database is not heavily used in the streaming API, only for initial authentication of the request, and for some special receiver-specific filter queries when receiving new messages. At the time of writing this value cannot be reconfigured, but mostly doesn't need to.
-
-If you need to scale up the streaming API, spawn more separate processes on different ports (e.g. 4000, 4001, 4003, etc) and load-balance between them with nginx.
-
-### Background processing
-
-Many tasks in Mastodon are delegated to background processing to ensure the HTTP requests are fast, and to prevent HTTP request aborts from affecting the execution of those tasks. Sidekiq is a single process, with a configurable numbero of threads. By default, it is 5. That means, 5 different jobs can be executed at the same time. Others will be queued until they can be processed.
-
-While the amount of threads in the web process affects the responsiveness of the Mastodon instance to the end-user, the amount of threads allocated to background processing affects how quickly posts can be delivered from the author to anyone else, how soon e-mails are sent out, etc.
-
-The amount of threads is not controlled by an environment variable in this case, but a command line argument in the invocation of Sidekiq:
-
- bundle exec sidekiq -c 15 -q default -q mailers -q push
-
-Would start the sidekiq process with 15 threads. Please mind that each threads needs to be able to connect to the database, which means that the database pool needs to be large enough to support all the threads. The database pool size is controlled with the `DB_POOL` environment variable, and defaults to the value of `MAX_THREADS` (therefore, is 5 by default).
-
-You might notice that the above command specifies three queues to be processed:
-
-- "default" contains most tasks such as delivering messages to followers and processing incoming notifications from other instances
-- "mailers" contains tasks that send e-mails
-- "push" contains tasks that deliver messages to other instances
-
-If you wish, you could start three different processes for each queue, to ensure that even when there is a lot of tasks of one type, important tasks of other types still get executed in a timely manner.
-
-___
-
-### How to set environment variables
-#### With systemd
-
-In the `.service` file:
-
-```systemd
-...
-Environment="WEB_CONCURRENCY=1"
-Environment="MAX_THREADS=5"
-ExecStart="..."
-...
-```
-
-Don't forget to `sudo systemctl daemon-reload` before restarting the services so that the changes would take effect!
-
-#### With docker-compose
-
-Edit `docker-compose.yml`:
-
-```yml
-...
- web:
- restart: always
- build: .
- env_file: .env.production
- environment:
- - WEB_CONCURRENCY=1
- - MAX_THREADS=5
-...
-```
-
-Re-create the containers with `docker-compose up -d` for the changes to take effect.
-
-You can also scale the number of containers per "service" (where service is "web", "sidekiq" and "streaming"):
-
- docker-compose scale web=1 sidekiq=2 streaming=3
-
-Realistically the `docker-compose.yml` file needs to be modified a bit further for the above to work, because by default it wants to bind the web container to host port 3000 and streaming container to host port 4000, of either of which there is only one on the host system. However, if you change:
-
-```yml
-ports:
- - "3000:3000"
-```
-
-to simply:
-
-```yml
-ports:
- - "3000"
-```
-
-for each service respectively, Docker will allocate random host ports of the services, allowing multiple containers to run alongside each other. But it will be on you to look up which host ports those are (e.g. with `docker ps`), and they will be different on each container restart.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Tuning.md)
diff --git a/docs/Running-Mastodon/Vagrant-guide.md b/docs/Running-Mastodon/Vagrant-guide.md
index b24f14e8..c5823b09 100644
--- a/docs/Running-Mastodon/Vagrant-guide.md
+++ b/docs/Running-Mastodon/Vagrant-guide.md
@@ -1,64 +1 @@
-Vagrant guide
-=============
-
-A quick way to get a development environment up and running is with Vagrant. You will need recent versions of [Vagrant](https://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) installed.
-
-## Basic setup
-
-Install the latest versions of Vagrant and VirtualBox for your operating systems, and then run:
-
- vagrant plugin install vagrant-hostsupdater
-
-This is optional, but will update your 'hosts' file when you start the virtual machine, allowing you to access the site at http://mastodon.dev (instead of http://localhost:3000).
-
-To create and provision a new virtual machine for Mastodon development:
-
- git clone git@github.com:tootsuite/mastodon.git
- cd mastodon
- vagrant up
-
-Running `vagrant up` for the first time will run provisioning, which will:
-
-- Download the Ubuntu 14.04 base image, if there isn't already a copy on your machine
-- Create a new VirtualBox virtual machine from that image
-- Run the provisioning script (located inside the Vagrantfile), which installs the system packages, Ruby gems, and JS modules required for Mastodon
-- Run the startup script
-
-## Starting the server
-
-The Vagrant box will automatically start after provisioning. It can be started in future with `vagrant up` from the mastodon directory.
-
-Once the Ubuntu virtual machine has booted, it will run the startup script, which loads the environment variables from `.env.vagrant` and then runs `rails s -d -b 0.0.0.0`. This will start a Rails server. You can then access your development site at http://mastodon.dev (or at http://localhost:3000 if you haven't installed vagrants-hostupdater). By default, your development environment will have an admin account created for you to use - the email address will be `admin@mastodon.dev` and the password will be `mastodonadmin`.
-
-To stop the server, simply run `vagrant halt`.
-
-## Using the server
-
-You should now have a working Mastodon instance, although it will not federate, as it is not publicly accessible. Should you need temporary federation for development and testing, see the Ngrok information in the [Development Guide](Development-guide.md).
-
-By default, your instance's ActionMailer will use "Letter Opener Web" for email. This means that any email that would normally be sent, will instead be stored, and accessible at http://mastodon.dev/letter_opener - you can use this to verify a registered user account.
-
-## Making changes/developing
-
-You are able to set environment variables, which are used for Mastodon configuration, by editing the `.env.vagrant` file. Any changes you make will take effect after a Vagrant restart.
-
-Vagrant has mounted your mastodon folder inside the virtual machine. This means that any change to the files in the folder(e.g. the Rails controllers or the React components in /app) should immediately take effect on the live server. This allows you to make and test changes, and create new commits, without ever needing to access the virtual machine.
-
-Should you need to access the virtual machine (for example, to manually restart the Rails process without restarting the box), run `vagrant ssh` from the mastodon folder. You will now be logged in as the `vagrant` user on the VirtualBox Ubuntu VM. You will want to `cd /vagrant` to see the app folder.
-
-## Debugging
-
-You can find the Rails server logs in in the `log` folder, which will often have the information you need.
-
-If your Mastodon instance or Vagrant box are really not behaving, you can re-run the provisioning process. Stop the box with `vagrant halt`, and then run `vagrant destroy` - this will delete the virtual machine. You may then run `vagrant up` to create a new box, and re-run provisioning.
-
-## Testing
-
-To run the `rspec` tests and `rubocop` style checker, you may either:
-
-* Install the relevant gems locally, or
-* SSH into the virtual machine, `cd /vagrant`, and then run the commands
-
-## Support/help
-
-If you are confused, or having any issues with the above, the Mastodon IRC channel ( irc.freenode.net #mastodon ) is a good place to find assistance.
\ No newline at end of file
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Vagrant-guide.md)
diff --git a/docs/Specs-and-RFCs-used.md b/docs/Specs-and-RFCs-used.md
index 9bb1bb62..89a4dd31 100644
--- a/docs/Specs-and-RFCs-used.md
+++ b/docs/Specs-and-RFCs-used.md
@@ -1,12 +1 @@
-Specs and RFCs used
-===================
-
-* [OStatus](https://www.w3.org/community/ostatus/wiki/images/9/93/OStatus_1.0_Draft_2.pdf)
-* [Salmon](http://www.salmon-protocol.org/salmon-protocol-summary)
-* [Portable Contacts](https://web.archive.org/web/20160305010620/http://portablecontacts.net/draft-spec.html)
-* [Atom](https://tools.ietf.org/html/rfc4287)
-* [Atom ActivityStreams](http://activitystrea.ms/specs/atom/1.0/)
-* [Atom Threading](https://tools.ietf.org/html/rfc4685)
-* [PubSubHubbub](https://pubsubhubbub.github.io/PubSubHubbub/pubsubhubbub-core-0.4.html)
-* [Webfinger](https://tools.ietf.org/html/rfc7033)
-* [Link-based Resource Descriptor Discovery](https://tools.ietf.org/html/rfc6415)
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Specs-and-RFCs-used.md)
diff --git a/docs/Using-Mastodon/2FA.md b/docs/Using-Mastodon/2FA.md
index 663563a2..d5c6985b 100644
--- a/docs/Using-Mastodon/2FA.md
+++ b/docs/Using-Mastodon/2FA.md
@@ -1,44 +1 @@
-# 2-Factor Authentication
-
-2-Factor Authentication is a security mechanism that requires you to enter a computer generated code from your phone every time you log into Mastodon.
-
-We highly recommend that you set up 2-factor authentication as it prevents malicious users from logging into your account if they obtain your password.
-
-## Warning
-
-If you lose access to your 2-factor authentication (such as by losing your phone or performing a factory reset) and you do cannot log in, you will not be able to access your account and will need to contact an instance admin to remove 2-factor authentication from your account.
-
-## Setup
-
-1. Open your [settings page](https://mastodon.social/settings/two_factor_auth) and navigate to the Two-factor Authentication page
-2. Press the big blue "Enable" button that appears on the right ![screenshot](screenshots/2fa/enable.png)
-3. Follow instructions below to install an authenticator for your smartphone
-
-## Android
-
-__Recommended Application:__ [Google
-Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2)
-
-4. Download the above application on your phone
-5. Open the "Authenticator" app
-6. Press the + button in the bottom right-hand corner
-7. Press "Scan a barcode"
-8. Line up the black and white QR code with the target box that appears on your camera
-9. Now, whenever you log in to Mastodon, open the Authenticator app and enter the 6 digit code that appears above the "mastodon.social (email address)" text
-
-## iPhone
-
-__Recommended Application:__ iPhone: [Authenticator by Matt
-Ruben](https://itunes.apple.com/us/app/authenticator/id766157276?mt=8)
-
-4. Download the above application on your phone
-5. Open the "Authenticator" app
-6. Press the + button in the bottom right-hand corner
-7. Authenticator should prompt you for access to your camera- hit "OK"
-8. Line up the black and white QR code with the target box that appears on your camera
-9. Now, whenever you log in to Mastodon, open the Authenticator app and enter the 6 digit code that appears above the "mastodon.social (email address)" text
-
-# Disabling 2-factor Authentication
-
-1. Go to [the 2-factor authentication settings page](https://mastodon.social/settings/two_factor_auth)
-2. Press the big blue "Disable" button underneath your QR code ![disable button screenshot](screenshots/2fa/disable.png)
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/2FA.md)
diff --git a/docs/Using-Mastodon/Apps.md b/docs/Using-Mastodon/Apps.md
index 67b14dc2..c2ced445 100644
--- a/docs/Using-Mastodon/Apps.md
+++ b/docs/Using-Mastodon/Apps.md
@@ -1,17 +1 @@
-List of apps
-============
-
-Some people have started working on apps for the Mastodon API. Here is a list of them:
-
-|App|Platform|Link|Developer(s)|
-|---|--------|----|------------|
-|[Tusky](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky)|Android||[@Vavassor@mastodon.social](https://mastodon.social/users/Vavassor)|
-|mastodroid|Android||[@charlag@mastodon.social](https://mastodon.social/users/charlag)|
-|TootyFruity|Android||[@eggplant@mastodon.social](https://mastodon.social/users/eggplant)|
-|11t|iOS/Android||[@jeroensmeets@mastodon.social](https://mastodon.social/users/jeroensmeets)|
-|[Amaroq](https://itunes.apple.com/us/app/amarok-for-mastodon/id1214116200?ls=1&mt=8)|iOS||[@eurasierboy@mastodon.social](https://mastodon.social/users/eurasierboy)|
-|Albatross|iOS||[@goldie_ice@mastodon.social](https://mastodon.social/users/goldie_ice)|
-|Tooter|Chrome||[@effy@mastodon.social](https://mastodon.social/users/effy)|
-|tootstream|CLI||[@Raccoon@mastodon.social](https://mastodon.social/users/Raccoon)|
-
-If you have a project like this, let me know so I can add it to the list!
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
diff --git a/docs/Using-Mastodon/FAQ.md b/docs/Using-Mastodon/FAQ.md
index daedcbdd..d50e63b9 100644
--- a/docs/Using-Mastodon/FAQ.md
+++ b/docs/Using-Mastodon/FAQ.md
@@ -1,43 +1 @@
-Frequently Asked Questions
-==========================
-
-#### What is a Mastodon?
-
-A prehistoric animal, predecessor of the mammoth.
-
-#### Why the name Mastodon?
-
-There's a progressive metal band with the same name that I'm a fan of that brought the animal to my attention. I thought it's a pretty cool name/animal.
-
-#### How exactly is it decentralized?
-
-There are different ways in which something can be decentralized; in this case, Mastodon is the "federated" kind. Think e-mail, not BitTorrent. There are different servers (instances), users have an account on one of them, but can interact and follow each other regardless of where their account is.
-
-#### Technically, how does the federation work?
-
-We are using the OStatus suite of protocols:
-
-1. Webfinger for user-on-domain lookup
-2. Atom feeds with ActivityStreams, Portable Contacts, Threads extensions for the actual content
-3. PubSubHubbub for subscribing to Atom feeds
-4. Salmon for delivering certain items from the Atom feeds to interested parties such as the mentioned user, author of the status being replied to, person being followed, etc
-
-#### What is mastodon.social?
-
-The "flagship" instance of Mastodon, aka the server I run myself with the latest code. It's not supposed to be the only instance in the end.
-
-#### What else is part of the federated network?
-
-Let's call it the "fediverse". It has existed for a longer while, populated by GNU social servers, Friendica, Hubzilla, Diaspora etc. Not every one of those servers is fully compatible with every other. Mastodon strives to be fully standards-compliant and compatibility with GNU social is higher in priority than the others.
-
-#### I tried logging into a GNU social client app with Mastodon and it didn't work, why?
-
-While Mastodon is compatible with GNU social in terms of server to server communication, the client to server API (aka how you access Mastodon) is different. Therefore, client apps that were made for specifically GNU social will not work with Mastodon. The reason for this is half technical, half ideological.
-
-Because Mastodon has been created from a blank slate, it is much simpler to have the API mirror internal structures as closely as possible, rather than build an emulation layer. Secondly, the GNU social client API is actually a half-way implementation of the legacy Twitter API - that's the reason why it works with some older Twitter client apps. However, many of those apps are not maintained anymore, the GNU social API does not actually keep up with the real Twitter API and never fully implemented all its features; at the same time, the Twitter API was never meant for a federated service and so obscures some of the functionality.
-
-#### How is Mastodon funded?
-
-Development of Mastodon and hosting of mastodon.social is funded through my [Patreon (also BTC/PayPal donations)](https://www.patreon.com/user?u=619786). Beyond that, I am not interested in VC funding, monetizing, advertising, or anything of that sort. I could offer setup/maintenance services on demand.
-
-The software is free and open source and communities should host their own servers if they can, that way the costs are more or less distributed. Obviously it'd be hard for me to pay the bills if literally everyone decided to use the mastodon.social instance only.
\ No newline at end of file
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
diff --git a/docs/Using-Mastodon/List-of-Mastodon-instances.md b/docs/Using-Mastodon/List-of-Mastodon-instances.md
index 7c8a3389..0e2d0864 100644
--- a/docs/Using-Mastodon/List-of-Mastodon-instances.md
+++ b/docs/Using-Mastodon/List-of-Mastodon-instances.md
@@ -1,25 +1 @@
-List of Known Mastodon instances
-==========================
-
-There is also a list at [instances.mastodon.xyz](https://instances.mastodon.xyz) showing realtime information about instances.
-
-| Name | Theme/Notes, if applicable | Open Registrations | IPv6 |
-| -------------|-------------|---|---|
-| [mastodon.social](https://mastodon.social) |Flagship, quick updates|Yes|No|
-| [awoo.space](https://awoo.space) |Intentionally moderated, only federates with mastodon.social|Yes|No|
-| [social.tchncs.de](https://social.tchncs.de)|N/A|Yes|No|
-| [animalliberation.social](https://animalliberation.social) |Animal Rights|Yes|No|
-| [socially.constructed.space](https://socially.constructed.space) |Single user|No|No|
-| [epiktistes.com](https://epiktistes.com) |N/A|Yes|No|
-| [gay.crime.team](https://gay.crime.team) |the place for doin' gay crime online (please don't actually do crime here)|Yes|No|
-| [icosahedron.website](https://icosahedron.website/) |Icosahedron-themed (well, visually), open registration.|Yes|No|
-| [memetastic.space](https://memetastic.space) |Memes|Yes|No|
-| [social.diskseven.com](https://social.diskseven.com) |Single user|No|No (DNS entry but no response)|
-| [social.gestaltzerfall.net](https://social.gestaltzerfall.net) |Single user|No|No|
-| [mastodon.xyz](https://mastodon.xyz) |N/A|Yes|Yes|
-| [social.targaryen.house](https://social.targaryen.house) |N/A|Yes|No|
-| [social.mashek.net](https://social.mashek.net) |Themed and customised for Mashekstein Labs community. Selectively federates.|Yes|No|
-| [masto.themimitoof.fr](https://masto.themimitoof.fr) |N/A|Yes|Yes|
-| [social.imirhil.fr](https://social.imirhil.fr) |N/A|No|Yes|
-
-Let me know if you start running one so I can add it to the list! (Alternatively, add it yourself as a pull request).
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
diff --git a/docs/Using-Mastodon/User-guide.md b/docs/Using-Mastodon/User-guide.md
index f78921c6..7ef5a117 100644
--- a/docs/Using-Mastodon/User-guide.md
+++ b/docs/Using-Mastodon/User-guide.md
@@ -1,196 +1 @@
-Mastodon User's Guide
-=====================
-
-* [Intro](User-guide.md#intro)
- * [Decentralization and Federation](User-guide.md#decentralization-and-federation)
-* [Getting Started](User-guide.md#getting-started)
- * [Setting Up Your Profile](User-guide.md#setting-up-your-profile)
- * [E-Mail Notifications](User-guide.md#e-mail-notifications)
- * [Text Posts](User-guide.md#text-posts)
- * [Content Warnings](User-guide.md#content-warnings)
- * [Hashtags](User-guide.md#hashtags)
- * [Boosts and Favourites](User-guide.md#boosts-and-favourites)
- * [Posting Images](User-guide.md#posting-images)
- * [Following Other Users](User-guide.md#following-other-users)
- * [Notifications](User-guide.md#notifications)
- * [Mobile Apps](User-guide.md#mobile-apps)
- * [The Federated Timeline](User-guide.md#the-federated-timeline)
- * [The Local Timeline](User-guide.md#the-local-timeline)
- * [Searching](User-guide.md#searching)
-* [Privacy, Safety and Security](User-guide.md#privacy-safety-and-security)
- * [Two-Factor Authentication](User-guide.md#two-factor-authentication)
- * [Account Privacy](User-guide.md#account-privacy)
- * [Toot Privacy](User-guide.md#toot-privacy)
- * [Blocking](User-guide.md#blocking)
- * [Reporting Toots or Users](User-guide.md#reporting-toots-or-users)
-
-## Intro
-
-Mastodon is a social network application based on the GNU Social protocol. It behaves a lot like other social networks, especially Twitter, with one key difference - it is open-source and anyone can start their own server (also called an "instance"), and users of any instance can interact freely with those of other instances (called "federation"). Thus, it is possible for small communities to set up their own servers to use amongst themselves while also allowing interaction with other communities.
-
-#### Decentralization and Federation
-
-Mastodon is a system decentralized through a concept called "federation" - rather than depending on a single person or organization to run its infrastructure, anyone can download and run the software and run their own server. Federation means different Mastodon servers can interact with each other seamlessly, similar to e.g. e-mail.
-
-As such, anyone can download Mastodon and e.g. run it for a small community of people, but any user registered on that instance can follow and send and read posts from other Mastodon instances (as well as servers running other GNU Social-compatible services). This means that not only is users' data not inherently owned by a company with an interest in selling it to advertisers, but also that if any given server shuts down its users can set up a new one or migrate to another instance, rather than the entire service being lost.
-
-Within each Mastodon instance, usernames just appear as `@username`, similar to other services such as Twitter. Users from other instances appear, and can be searched for and followed, as `@user@servername.ext` - so e.g. `@gargron` on the `mastodon.social` instance can be followed from other instances as `@gargron@mastodon.social`).
-
-Posts from users on external instances are "federated" into the local one, i.e. if `user1@mastodon1` follows `user2@gnusocial2`, any posts `user2@gnusocial2` makes appear in both `user1@mastodon`'s Home feed and the public timeline on the `mastodon1` server. Mastodon server administrators have some control over this and can exclude users' posts from appearing on the public timeline; post privacy settings from users on Mastodon instances also affect this, see below in the [Toot Privacy](User-guide.md#toot-privacy) section.
-
-## Getting Started
-
-#### Setting Up Your Profile
-
-You can customise your Mastodon profile in a number of ways - you can set a custom "display" name, a profile "avatar" picture, a background image for your profile page header, and a short "bio" that summarises you or your account.
-
-![Preferences icon](screenshots/preferences.png) To edit your profile, click the Preferences icon in the Compose column and select "Edit Profile" on the left-hand menu on the Preferences page. Your display name is limited to 30 characters, your bio to 160. Avatars and header pictures can be uploaded as png, gif or jpg images and cannot be larger than 2MB. They will be resized to standard sizes - 120x120 pixels for avatars, 700x335 pixels for header pictures.
-
-#### E-Mail Notifications
-
-![Preferences icon](screenshots/preferences.png) Mastodon can notify you of activity via e-mail if you so choose. To adjust your settings for receiving e-mail notifications, click the Preferences icon in the Compose column and select the "Preferences" page from the left-hand menu. Here you will find a number of checkboxes to enable or disable e-mail notifications for various types of activity.
-
-#### Text Posts
-
-The most basic way to interact with Mastodon is to make a text post, also called a *Toot*. In order to toot, simply enter the message you want to post into the "What is on your mind?" text box in the Compose column and click "TOOT". There is a limit of up to 500 characters per toot; if you really do need more than this you can reply to your own toots so they will appear like a conversation.
-
-If you want to reply to another user's toot, you can click the "Reply" icon on it. This will add their username to your input box along with a preview of the message you're replying to, and the user will receive a notification of your response.
-
-Similarly, in order to start a conversation with another user, just mention their user name in your toot. When you type the @ symbol followed directly (without a space) by any character in a message, Mastodon will automatically start suggesting users that match the username you're typing. Like with replies, mentioning a user like this will send them a notification.
-
-##### Content Warnings
-
-When you want to post something that you don't want to be immediately visible - for example, spoilers for that film that's just out, or some personal thoughts that contain [triggers](http://www.bbc.co.uk/news/blogs-ouch-26295437), you can "hide" it behind a Content Warning.
-
-To do this, click the ![CW icon](screenshots/compose-cw.png) "CW" switch under the Compose box. This will add another text box labeled "Content warning"; you should enter a short summary of what the "body" of your post contains here while your actual post goes into the "What is on your mind?" box as normal.
-
-![animation showing how to enable content warnings](screenshots/content-warning.gif)
-
-This will cause the body of your post to be hidden behind a "Show More" button in the timeline, with only the content warning visible by default:
-
-![animation showing content warnings in the timeline](screenshots/cw-toot.gif)
-
-**NOTE** that this will not hide images included in your post - images can be marked as "sensitive" separately to hide them from view until clicked on. To find out how to do this, see the [Posting Images](User-guide.md#posting-images) section of this user guide.
-
-##### Hashtags
-
-If you're making a post belonging to a wider subject, it might be worth adding a "hashtag" to it. This can be done simply by adding any alphanumeric term with a # sign in front of it to the toot, e.g. #introductions (which is popular on mastodon.social for new users to introduce themselves to the community), or #politics for political discussions, etc. Clicking on a hashtag in a toot will show a timeline consisting only of toots that include this hashtag (i.e. it's a shortcut to searching for it). This allows users to group messages of similar subjects together, forming a separate "timeline" for people interested in that subject.
-
-##### Boosts and Favourites
-
-You can *favourite* another user's toot by clicking the star icon underneath. This will send the user a notification that you have marked their post as a favourite; the meaning of this varies widely by context from a general "I'm listening" to signalling agreement or offering support for the ideas expressed.
-
-Additionally you can *boost* toots by clicking the "circular arrows" icon. Boosting a toot will show it on your profile timeline and make it appear to all your followers, even if they aren't following the user who made the original post. This is helpful if someone posts a message you think others should see, as it increases the message's reach while keeping the author information intact.
-
-#### Posting Images
-
-![Image icon](screenshots/compose-media.png) In order to post an image, simply click or tap the "image" icon in your Compose column and select a file to upload.
-
-If the image is "not safe for work" or has otherwise sensitive content, you can select the ![NSFW toggle](screenshots/compose-nsfw.png) "NSFW" button which appears once you have added an image. This will hide the image in your post by default, making it clickable to show the preview. This is the "visual" version of [content warnings](User-guide.md#content-warnings) and could be combined with them if there is text to accompany the image - otherwise it's fine to just mark the image as sensitive and make the body of your post the content warning.
-
-You can also attach video files or GIF animations to Toots. However, there is a 4MB file size limit for these files and videos must be in .webm or .mp4 format.
-
-#### Following Other Users
-
-Following another user will make all of their toots as well as other users' toots which they [boost](User-guide.md#boosts-and-favourites) in your Home column. This gives you a separate timeline from the [federated timeline](User-guide.md#the-federated-timeline) in which you can read what particular people are up to without the noise of general conversation.
-
-![Follow icon](screenshots/follow.png) In order to follow a user, click their name or avatar to open their profile, then click the Follow icon in the top left of their profile view.
-
-If their account is locked (which is shown with a padlock icon ![Padlock icon](screenshots/locked-icon.png) next to their user name), they will receive a notification of your request to follow them and need to approve this before you are added to their follower list (and thus see their toots). To show you that you're waiting for someone to approve your follow request, the Follow icon ![Follow icon](screenshots/follow-icon.png) on their profile will be replaced with an hourglass icon ![Pending icon](screenshots/pending-icon.png).
-
-Once you follow a user, the Follow icon will be highlighted in blue on their profile ![Following icon](screenshots/following-icon.png); you can unfollow them again by clicking this.
-
-If you know someone's user name you can also open their profile for following by entering it in the [Search box](User-guide.md#searching) in the Compose column. This also works for remote users, though depending on whether they are known to your home instance you might have to enter their full name including the domain (e.g. `gargron@mastodon.social`) into the search box before their profile will appear in the suggestions.
-
-Alternately, if you already have a user's profile open in a separate browser tab, most GNU Social-related networks should have a "Follow" or "Subscribe" button on their profile page. This will ask you to enter the full user name to follow **from** (ie. if your account is on mastodon.social you would want to enter this as `myaccount@mastodon.social`)
-
-#### Notifications
-
-When someone follows your account or requests to follow you, mentions your user name (either as an initial message or in response to one of your toots) or boosts or favourites one of your toots, you will receive a notification for this. These will appear as desktop notifications on your computer (if your web browser supports this and you've enabled them) as well as in your "Notifications" column.
-
-![Notification Settings icon](screenshots/notifications-settings.png) You can filter what kind of notifications you see in the Notifications column by clicking the Notification Settings icon at the top of the column and ticking or un-ticking what you do or don't want to see notifications for.
-
-![Clear icon](screenshots/notifications-clear.png) If your notifications become cluttered, you can clear the column by clicking the Clear icon at the top of the column; this will wipe its contents.
-
-![Preferences icon](screenshots/preferences.png) You can also disable notifications from people you don't follow or who don't follow you entirely - to do this, click the Preferences icon in the Compose column, select "Preferences" on the left-hand menu and check either of the respective "Block notifications" options.
-
-#### Mobile Apps
-
-There are no official mobile Mastodon apps for iOS or Android at this point. However, there are several third-party apps in development; you can find a list of these [here](Apps.md).
-
-#### The Federated Timeline
-
-Mastodon has a "Federated" timeline, which is a collection of all public toots made by all local users as well as posts from remote users that are federated (because someone on your instance follows the remote user making the post). This is a good way to meet new people to follow or interact with, but can be overwhelming especially if there's a lot of activity.
-
-![Federated Timeline icon](screenshots/federated-timeline.png) To view the federated timeline, click the "Federated Timeline" icon in your Compose column or the respective button on the Getting Started panel. To hide the federated timeline again, simply click the "Back" link at the top of the column while you're viewing it.
-
-#### The Local Timeline
-
-In addition to the Federated Timeline, there's also a "Local" timeline, which only shows public toots made by users on your home instance. This is quieter than the Federated timeline, and useful if you want to stick close to your instance's community without having too much noise from outside. To view the Local Timeline, click the ![Menu icon](screenshots/compose-menu.png) Menu icon on the Compose pane and then select "Local Timeline" on the rightmost column.
-
-#### Searching
-
-Mastodon has a search function - however, this is limited to users and [hashtags](User-guide.md#hashtags) only and cannot be used to search through the full text of toots. In order to start a search, just type into the search box in the Compose column; Mastodon will automatically start showing suggestions of both user names and hashtags in a pop-up after a moment. Selecting any of these will open the user's profile or a view of all toots on the hashtag.
-
-## Privacy, Safety and Security
-
-Mastodon has a number of advanced security, privacy and safety features over more public networks such as Twitter. Particularly the privacy controls are fairly granular; this section will explain how these features work.
-
-#### Two-Factor Authentication
-
-Two-Factor Authentication (2FA) is a mechanism that improves the security of your Mastodon account by requiring a numeric code from another device (most commonly mobile phones) linked to your Mastodon account when you log in - this means that even if someone gets hold of both your e-mail address and your password, they cannot take over your Mastodon account as they would need a physical device you own to log in.
-
-Mastodon's 2FA uses Google Authenticator (or compatible apps). You can install this for free to your [Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2) or [iOS](https://itunes.apple.com/gb/app/google-authenticator/id388497605) device; [this Wikipedia page](https://en.wikipedia.org/wiki/Google_Authenticator#Implementations) lists further versions of the app for other systems.
-
-![Preferences icon](screenshots/preferences.png) In order to enable 2FA for your Mastodon account, click the Preferences icon in the Compose column, click "Two-factor Authentication" in the left menu on the settings page and follow the instructions. Once activated, every time you log in you will need a one-time code generated by the Authenticator app on the device you've linked to your account.
-
-#### Account Privacy
-
-To allow you more control over who can see your toots, Mastodon supports "private" or "locked" accounts. If your account is set to private, you will be notified every time someone tries to follow you, and you will be able to allow or deny the follow request. Additionally, if your account is private, any new toots you compose will default to being private (see the [Toot Privacy](User-guide.md#toot-privacy) section below).
-
-![Preferences icon](screenshots/preferences.png) To make your account private, click the Preferences icon in the Compose pane, select "Edit Profile" and tick the "Make account private" checkbox, then click "Save Changes".
-
-![Screenshot of the "Private Account" setting](screenshots/private.png)
-
-#### Toot Privacy
-
-Toot privacy is handled independently of account privacy, and individually for each toot. The three tiers of visibility for toots are Public (default), Unlisted or Private. In order to select your privacy level, click the ![Globe icon](screenshots/compose-privacy.png) globe icon. Changes to this setting are remembered between posts, i.e. if you make one private toot you will need to disable the switch again to make public toots.
-
-**Public** is the default status of toots on accounts not set to private; a toot is public if neither of the two flags are set. Public toots are visible to any other user on the public timeline, federate to other GNU Social instances without restriction and appear on your user profile page to anyone including search engine bots and visitors who aren't logged into a Mastodon account.
-
-**Unlisted** toots are toggled with the "Do not display in public timeline" option in the Compose pane. They are visible to anyone following you and appear on your profile page to the public even without a Mastodon login, but do *not* appear to anyone viewing the Public Timeline while logged into Mastodon.
-
-**Private** toots, finally, are toggled with the "Mark as private" switch. Private toots do not appear in the public timeline nor on your profile page to anyone viewing it unless they are on your Followers list. This means the option is of very limited use if your account is not also set to be private (as anyone can follow you without confirmation and thus see your private toots). However the separation of this means that if you *do* set your entire account to private, you can switch this option off on a toot to make unlisted or even public toots from your otherwise private account.
-
-Private toots do not federate to other instances, unless you @mention a remote user. In this case, they will federate to their instance *and may appear there PUBLICLY*. A warning will be displayed if you're composing a private toot that will federate to another instance.
-
-Private toots cannot be boosted. If someone you follow makes a private toot, it will appear in your timeline with a padlock icon in place of the Boost icon. **NOTE** that remote instances may not respect this.
-
-**Direct** messages are only visible to users you have @mentioned in them. This does *not* federate to protect your privacy (as other instances may ignore the "Direct" status and display the messages as public if they were to receive them), even if you have @mentioned a remote user.
-
-To summarise:
-
-Toot Privacy | Visible on Profile | Visible on Public Timeline | Federates to other instances
------------- | ------------------ | -------------------------- | ---------------------------
-Public | Anyone incl. anonymous viewers | Yes | Yes
-Unlisted | Anyone incl. anonymous viewers | No | Yes
-Private | Followers only | No | Only remote @mentions
-Direct | No | No | No
-
-#### Blocking
-
-You can block a user to stop them contacting you. To do this, you can click or tap the Menu icon on either a toot of theirs or their profile view and select "Block".
-
-**NOTE** that this will stop them from seeing your public toots while they are logged in, but they *will* be able to see your public toots by simply opening your profile in another browser that isn't logged into Mastodon (or logged into a different account that you have not blocked).
-
-Mentions, favourites, boosts or any other interaction with you from a blocked user will be hidden from your view. You will not see replies to a blocked person, even if the reply mentions you, nor will you see their toots if someone boosts them. You will not see toots mentioning a blocked person except in the public timeline.
-
-The blocked user will not be notified of your blocking them. They will be removed from your followers, *but* will still be able to see any public toots you make. Blocks do not federate across instances.
-
-#### Reporting Toots or Users
-
-If you encounter a toot or a user that is breaking the rules of your instance or that you otherwise want to draw the instance administrators' attention to (e.g. if someone is harassing another user, spamming pornography or posting illegal content), you can click the "..." menu button on the toot or the "hamburger" menu on the profile and select to report this. The rightmost column will then switch over to the following form:
-
-![Report form](screenshots/report.png)
-
-In this form, you can select any toots you would like to report to the instance administrators and fill in any comment that might be helpful in identifying or handling the issue (from "is a spammer" to "this post contains untagged pornography"). The report will be visible to server administrators once it is sent so they can take appropriate action, for example hiding the user's posts from the public timeline or banning their account.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md)
diff --git a/docs/Using-Mastodon/screenshots/2fa/disable.png b/docs/Using-Mastodon/screenshots/2fa/disable.png
deleted file mode 100644
index f008c27b..00000000
Binary files a/docs/Using-Mastodon/screenshots/2fa/disable.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/2fa/enable.png b/docs/Using-Mastodon/screenshots/2fa/enable.png
deleted file mode 100644
index ea82a0af..00000000
Binary files a/docs/Using-Mastodon/screenshots/2fa/enable.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/compose-cw.png b/docs/Using-Mastodon/screenshots/compose-cw.png
deleted file mode 100644
index 584080a5..00000000
Binary files a/docs/Using-Mastodon/screenshots/compose-cw.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/compose-media.png b/docs/Using-Mastodon/screenshots/compose-media.png
deleted file mode 100644
index 7a63c196..00000000
Binary files a/docs/Using-Mastodon/screenshots/compose-media.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/compose-nsfw.png b/docs/Using-Mastodon/screenshots/compose-nsfw.png
deleted file mode 100644
index a4ff5ed7..00000000
Binary files a/docs/Using-Mastodon/screenshots/compose-nsfw.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/compose-privacy.png b/docs/Using-Mastodon/screenshots/compose-privacy.png
deleted file mode 100644
index b18ed204..00000000
Binary files a/docs/Using-Mastodon/screenshots/compose-privacy.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/content-warning.gif b/docs/Using-Mastodon/screenshots/content-warning.gif
deleted file mode 100644
index 2e472061..00000000
Binary files a/docs/Using-Mastodon/screenshots/content-warning.gif and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/cw-toot.gif b/docs/Using-Mastodon/screenshots/cw-toot.gif
deleted file mode 100644
index 5329933a..00000000
Binary files a/docs/Using-Mastodon/screenshots/cw-toot.gif and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/federated-timeline.png b/docs/Using-Mastodon/screenshots/federated-timeline.png
deleted file mode 100644
index d74b089f..00000000
Binary files a/docs/Using-Mastodon/screenshots/federated-timeline.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/follow-icon.png b/docs/Using-Mastodon/screenshots/follow-icon.png
deleted file mode 100644
index ee516c2f..00000000
Binary files a/docs/Using-Mastodon/screenshots/follow-icon.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/following-icon.png b/docs/Using-Mastodon/screenshots/following-icon.png
deleted file mode 100644
index bccdc110..00000000
Binary files a/docs/Using-Mastodon/screenshots/following-icon.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/locked-icon.png b/docs/Using-Mastodon/screenshots/locked-icon.png
deleted file mode 100644
index d199f1f1..00000000
Binary files a/docs/Using-Mastodon/screenshots/locked-icon.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/notifications-clear.png b/docs/Using-Mastodon/screenshots/notifications-clear.png
deleted file mode 100644
index 7d0922cc..00000000
Binary files a/docs/Using-Mastodon/screenshots/notifications-clear.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/notifications-settings.png b/docs/Using-Mastodon/screenshots/notifications-settings.png
deleted file mode 100644
index 3a3417e7..00000000
Binary files a/docs/Using-Mastodon/screenshots/notifications-settings.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/pending-icon.png b/docs/Using-Mastodon/screenshots/pending-icon.png
deleted file mode 100644
index 777b3c39..00000000
Binary files a/docs/Using-Mastodon/screenshots/pending-icon.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/preferences.png b/docs/Using-Mastodon/screenshots/preferences.png
deleted file mode 100644
index 943413fe..00000000
Binary files a/docs/Using-Mastodon/screenshots/preferences.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/private.png b/docs/Using-Mastodon/screenshots/private.png
deleted file mode 100644
index cf338aad..00000000
Binary files a/docs/Using-Mastodon/screenshots/private.png and /dev/null differ
diff --git a/docs/Using-Mastodon/screenshots/report.png b/docs/Using-Mastodon/screenshots/report.png
deleted file mode 100644
index 5ce401ee..00000000
Binary files a/docs/Using-Mastodon/screenshots/report.png and /dev/null differ
diff --git a/docs/Using-the-API/API.md b/docs/Using-the-API/API.md
index 54de0c0c..6de61e19 100644
--- a/docs/Using-the-API/API.md
+++ b/docs/Using-the-API/API.md
@@ -1,327 +1 @@
-API overview
-============
-
-## Contents
-
-- [Available libraries](#available-libraries)
-- [Notes](#notes)
-- [Methods](#methods)
- - Posting a status
- - Uploading media
- - Retrieving a timeline
- - Retrieving notifications
- - Following a remote user
- - Fetching data
- - Deleting a status
- - Reblogging a status
- - Favouriting a status
- - Threads (status context)
- - Who reblogged/favourited a status
- - Following/unfollowing accounts
- - Blocking/unblocking accounts
- - Getting instance information
- - Creating OAuth apps
-- [Entities](#entities)
- - Status
- - Account
-- [Pagination](#pagination)
-
-## Available libraries
-
-- [For Ruby](https://github.com/tootsuite/mastodon-api)
-- [For Python](https://github.com/halcy/Mastodon.py)
-- [For JavaScript](https://github.com/Zatnosk/libodonjs)
-- [For JavaScript (Node.js)](https://github.com/jessicahayley/node-mastodon)
-
-## Notes
-
-When an array parameter is mentioned, the Rails convention of specifying array parameters in query strings is meant. For example, a ruby array like `foo = [1, 2, 3]` can be encoded in the params as `foo[]=1&foo[]=2&foo[]=3`. Square brackets can be indexed but can also be empty.
-
-When a file parameter is mentioned, a form-encoded upload is expected.
-
-## Methods
-### Posting a new status
-
-**POST /api/v1/statuses**
-
-Form data:
-
-- `status`: The text of the status
-- `in_reply_to_id` (optional): local ID of the status you want to reply to
-- `media_ids` (optional): array of media IDs to attach to the status (maximum 4)
-- `sensitive` (optional): set this to mark the media of the status as NSFW
-- `visibility` (optional): either `private`, `unlisted` or `public`
-- `spoiler_text` (optional): text to be shown as a warning before the actual content
-
-Returns the new status.
-
-**POST /api/v1/media**
-
-Form data:
-
-- `file`: Image to be uploaded
-
-Returns a media object with an ID that can be attached when creating a status (see above).
-
-### Retrieving a timeline
-
-**GET /api/v1/timelines/home**
-**GET /api/v1/timelines/public**
-**GET /api/v1/timelines/tag/:hashtag**
-
-Returns statuses, most recent ones first. Home timeline is statuses from people you follow, mentions timeline is all statuses that mention you. Public timeline is "whole known network", and the last is the hashtag timeline.
-
-Query parameters:
-
-- `max_id` (optional): Skip statuses younger than ID (e.g. navigate backwards in time)
-- `since_id` (optional): Skip statuses older than ID (e.g. check for updates)
-
-Query parameters for public and tag timelines only:
-
-- `local` (optional): Only return statuses originating from this instance
-
-### Notifications
-
-**GET /api/v1/notifications**
-
-Returns notifications for the authenticated user. Each notification has an `id`, a `type` (mention, reblog, favourite, follow), an `account` which it came *from*, and in case of mention, reblog and favourite also a `status`.
-
-**GET /api/v1/notifications/:id**
-
-Returns single notification.
-
-**POST /api/v1/notifications/clear**
-
-Clears all of user's notifications.
-
-### Following a remote user
-
-**POST /api/v1/follows**
-
-Form data:
-
-- uri: username@domain of the person you want to follow
-
-Returns the local representation of the followed account.
-
-### Fetching data
-
-**GET /api/v1/statuses/:id**
-
-Returns status.
-
-**GET /api/v1/accounts/:id**
-
-Returns account.
-
-**GET /api/v1/accounts/verify_credentials**
-
-Returns authenticated user's account.
-
-**GET /api/v1/accounts/:id/statuses**
-
-Returns statuses by user.
-
-Query parameters:
-
-- `max_id` (optional): Skip statuses younger than ID (e.g. navigate backwards in time)
-- `since_id` (optional): Skip statuses older than ID (e.g. check for updates)
-- `only_media` (optional): Only return statuses that have media attachments
-- `exclude_replies` (optional): Skip statuses that reply to other statuses
-
-**GET /api/v1/accounts/:id/following**
-
-Returns users the given user is following.
-
-**GET /api/v1/accounts/:id/followers**
-
-Returns users the given user is followed by.
-
-**GET /api/v1/accounts/relationships**
-
-Returns relationships (`following`, `followed_by`, `blocking`, `muting`, `requested`) of the current user to a list of given accounts.
-
-Query parameters:
-
-- `id` (can be array): Account IDs
-
-**GET /api/v1/accounts/search**
-
-Returns matching accounts. Will lookup an account remotely if the search term is in the username@domain format and not yet in the database.
-
-Query parameters:
-
-- `q`: what to search for
-- `limit`: maximum number of matching accounts to return
-
-**GET /api/v1/blocks**
-
-Returns accounts blocked by authenticated user.
-
-**GET /api/v1/mutes**
-
-Returns accounts muted by authenticated user.
-
-**GET /api/v1/follow_requests**
-
-Returns accounts that want to follow the authenticated user but are waiting for approval.
-
-**GET /api/v1/favourites**
-
-Returns statuses favourited by authenticated user.
-
-### Deleting a status
-
-**DELETE /api/v1/statuses/:id**
-
-Returns an empty object.
-
-### Reblogging a status
-
-**POST /api/v1/statuses/:id/reblog**
-
-Returns a new status that wraps around the reblogged one.
-
-### Unreblogging a status
-
-**POST /api/v1/statuses/:id/unreblog**
-
-Returns the status that used to be reblogged.
-
-### Favouriting a status
-
-**POST /api/v1/statuses/:id/favourite**
-
-Returns the target status.
-
-### Unfavouriting a status
-
-**POST /api/v1/statuses/:id/unfavourite**
-
-Returns the target status.
-
-### Threads
-
-**GET /api/v1/statuses/:id/context**
-
-Returns `ancestors` and `descendants` of the status.
-
-### Who reblogged/favourited a status
-
-**GET /api/v1/statuses/:id/reblogged_by**
-**GET /api/v1/statuses/:id/favourited_by**
-
-Returns list of accounts.
-
-### Following and unfollowing users
-
-**POST /api/v1/accounts/:id/follow**
-**POST /api/v1/accounts/:id/unfollow**
-
-Returns the updated relationship to the user.
-
-### Blocking and unblocking users
-
-**POST /api/v1/accounts/:id/block**
-**POST /api/v1/accounts/:id/unblock**
-
-Returns the updated relationship to the user.
-
-### Getting instance information
-
-**GET /api/v1/instance**
-
-Returns an object containing the `title`, `description`, `email` and `uri` of the instance. Does not require authentication.
-
-# Muting and unmuting users
-
-**POST /api/v1/accounts/:id/mute**
-**POST /api/v1/accounts/:id/unmute**
-
-Returns the updated relationship to the user.
-
-### OAuth apps
-
-**POST /api/v1/apps**
-
-Form data:
-
-- `client_name`: Name of your application
-- `redirect_uris`: Where the user should be redirected after authorization (for no redirect, use `urn:ietf:wg:oauth:2.0:oob`)
-- `scopes`: This can be a space-separated list of the following items: "read", "write" and "follow" (see [this page](OAuth-details.md) for details on what the scopes do)
-- `website`: (optional) URL to the homepage of your app
-
-Creates a new OAuth app. Returns `id`, `client_id` and `client_secret` which can be used with [OAuth authentication in your 3rd party app](Testing-with-cURL.md).
-
-These values should be requested in the app itself from the API for each new app install + mastodon domain combo, and stored in the app for future requests.
-
-___
-
-## Entities
-
-### Status
-
-| Attribute | Description |
-|---------------------|-------------|
-| `id` ||
-| `uri` | fediverse-unique resource ID |
-| `url` | URL to the status page (can be remote) |
-| `account` | Account |
-| `in_reply_to_id` | null or ID of status it replies to |
-| `reblog` | null or Status|
-| `content` | Body of the status. This will contain HTML (remote HTML already sanitized) |
-| `created_at` ||
-| `reblogs_count` ||
-| `favourites_count` ||
-| `reblogged` | Boolean for authenticated user |
-| `favourited` | Boolean for authenticated user |
-| `sensitive` | Boolean, true if media attachments should be hidden by default |
-| `spoiler_text` | If not empty, warning text that should be displayed before the actual content |
-| `visibility` | Either `public`, `unlisted` or `private` |
-| `media_attachments` | array of MediaAttachments |
-| `mentions` | array of Mentions |
-| `application` | Application from which the status was posted |
-
-Media Attachment:
-
-| Attribute | Description |
-|---------------------|-------------|
-| `url` | URL of the original image (can be remote) |
-| `preview_url` | URL of the preview image |
-| `type` | Image or video |
-
-Mention:
-
-| Attribute | Description |
-|---------------------|-------------|
-| `url` | URL of user's profile (can be remote) |
-| `acct` | Username for local or username@domain for remote users |
-| `id` | Account ID |
-
-Application:
-
-| Attribute | Description |
-|---------------------|-------------|
-| `name` | Name of the app |
-| `website` | Homepage URL of the app |
-
-### Account
-
-| Attribute | Description |
-|-------------------|-------------|
-| `id` ||
-| `username` ||
-| `acct` | Equals username for local users, includes @domain for remote ones |
-| `display_name` ||
-| `note` | Biography of user |
-| `url` | URL of the user's profile page (can be remote) |
-| `avatar` | URL to the avatar image |
-| `header` | URL to the header image |
-| `locked` | Boolean for when the account cannot be followed without waiting for approval first |
-| `followers_count` ||
-| `following_count` ||
-| `statuses_count` ||
-
-## Pagination
-
-API methods that return collections of items can return a `Link` header containing URLs for the `next` and `prev` pages. [Link header RFC](https://tools.ietf.org/html/rfc5988)
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
diff --git a/docs/Using-the-API/OAuth-details.md b/docs/Using-the-API/OAuth-details.md
index d0b5abd4..6a6926bb 100644
--- a/docs/Using-the-API/OAuth-details.md
+++ b/docs/Using-the-API/OAuth-details.md
@@ -1,12 +1 @@
-OAuth details
-=============
-
-We use the [Doorkeeper gem for OAuth](https://github.com/doorkeeper-gem/doorkeeper/wiki), so you can refer to their docs on specifics of the end-points.
-
-The API is divided up into access scopes:
-
-- `read`: Read data
-- `write`: Post statuses and upload media for statuses
-- `follow`: Follow, unfollow, block, unblock
-
-Multiple scopes can be requested during the authorization phase with the `scope` query param (space-separate the scopes).
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-the-API/OAuth-details.md)
diff --git a/docs/Using-the-API/Push-notifications.md b/docs/Using-the-API/Push-notifications.md
index fc373e72..3292c0a6 100644
--- a/docs/Using-the-API/Push-notifications.md
+++ b/docs/Using-the-API/Push-notifications.md
@@ -1,4 +1 @@
-Push notifications
-==================
-
-See for an example of how to create push notifications for a mobile app. It involves using the Mastodon streaming API on behalf of the app's users, as a sort of proxy.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-the-API/Push-notifications.md)
diff --git a/docs/Using-the-API/Streaming-API.md b/docs/Using-the-API/Streaming-API.md
index b6d41ab0..482f901c 100644
--- a/docs/Using-the-API/Streaming-API.md
+++ b/docs/Using-the-API/Streaming-API.md
@@ -1,40 +1 @@
-Streaming API
-=============
-
-Your application can use a server-sent events endpoint to receive updates in real-time. Server-sent events is an incredibly simple transport method that relies entirely on chunked-encoding transfer, i.e. the HTTP connection is kept open and receives new data periodically.
-
-### Endpoints:
-
-**GET /api/v1/streaming/user**
-
-Returns events that are relevant to the authorized user, i.e. home timeline and notifications
-
-**GET /api/v1/streaming/public**
-
-Returns all public statuses
-
-**GET /api/v1/streaming/hashtag**
-
-Returns all public statuses for a particular hashtag (query param `tag`)
-
-### Stream contents
-
-The stream will contain events as well as heartbeat comments. Lines that begin with a colon (`:`) can be ignored by parsers, they are simply there to keep the connection open. Events have this structure:
-
-```
-event: name
-data: payload
-
-```
-
-[See MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format)
-
-### Event types
-
-|Event|Description|What's in the payload|
-|-----|-----------|---------------------|
-|`update`|A new status has appeared!|Status|
-|`notification`|A new notification|Notification|
-|`delete`|A status has been deleted|ID of the deleted status|
-
-The payload is JSON-encoded.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-the-API/Streaming-API.md)
diff --git a/docs/Using-the-API/Testing-with-cURL.md b/docs/Using-the-API/Testing-with-cURL.md
index dc5f2022..04c7c87b 100644
--- a/docs/Using-the-API/Testing-with-cURL.md
+++ b/docs/Using-the-API/Testing-with-cURL.md
@@ -1,18 +1 @@
-Testing the API with cURL
-=========================
-
-Mastodon builds around the idea of being a server first, rather than a client itself. Similarly to how a XMPP chat server communicates with others and with its own clients, Mastodon takes care of federation to other networks, like other Mastodon or GNU Social instances. So Mastodon provides a REST API, and a 3rd-party app system for using it via OAuth2.
-
-You can get a client ID and client secret required for OAuth [via an API end-point](API.md#oauth-apps).
-
-From these two, you will need to acquire an access token. It is possible to do using your account's e-mail and password like this:
-
- curl -X POST -d "client_id=CLIENT_ID_HERE&client_secret=CLIENT_SECRET_HERE&grant_type=password&username=YOUR_EMAIL&password=YOUR_PASSWORD" -Ss https://mastodon.social/oauth/token
-
-The `/oauth/token` path will attempt to login with the given credentials, and then retrieve the access token for the current user. If the login failed the response will be a 302 redirect to `/auth/sign_in`. Otherwise the response will be a JSON object containing the key `access_token`.
-
-Use that token in any API requests by setting a header like this:
-
- curl --header "Authorization: Bearer ACCESS_TOKEN_HERE" -sS https://mastodon.social/api/statuses/home
-
-Please note that the password-based approach is not recommended especially if you're dealing with other user's accounts and not just your own. Usually you would use the authorization grant approach where you redirect the user to a web page on the original site where they can login and authorize the application and are then redirected back to your application with an access code.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-the-API/Testing-with-cURL.md)
diff --git a/docs/Using-the-API/Tips-for-app-developers.md b/docs/Using-the-API/Tips-for-app-developers.md
index 561f1e27..36a28da2 100644
--- a/docs/Using-the-API/Tips-for-app-developers.md
+++ b/docs/Using-the-API/Tips-for-app-developers.md
@@ -1,16 +1 @@
-Tips for app developers
-=======================
-
-## Authentication
-
-Make sure that you allow your users to specify the domain they want to connect to before login. Use that domain to acquire a client id/secret for OAuth2 and then proceed with normal OAuth2 also using that domain to build the URLs.
-
-In my opinion it is easier for people to understand what is being asked of them if you ask for a `username@domain` type input, since it looks like an e-mail address. Though the username part is not required for anything in the OAuth2 process. Once the user is logged in, you get information about the logged in user from `/api/v1/accounts/verify_credentials`
-
-## Usernames
-
-Make sure that you make it possible to see the `acct` of any user in your app (since it includes the domain part for remote users), people must be able to tell apart users from different domains with the same username.
-
-## Formatting
-
-The API delivers already formatted HTML to your app. This isn't ideal since not all apps are based on HTML, but this is not fixable as its part of the way OStatus federation works. Most importantly, you get some information on linked entities alongside the HTML of the status body. For example, you get a list of mentioned users, and a list of media attachments, and a list of hashtags. It is possible to convert the HTML to whatever you need in your app by parsing the HTML tags and matching their `href`s to the linked entities. If a match cannot be found, the link must stay a clickable link.
+[The documentation has moved to its own repository](https://github.com/tootsuite/documentation/blob/master/Using-the-API/Tips-for-app-developers.md)
diff --git a/doorkeeper.it.yml b/doorkeeper.it.yml
new file mode 100644
index 00000000..2f9b9151
--- /dev/null
+++ b/doorkeeper.it.yml
@@ -0,0 +1,113 @@
+---
+it:
+ activerecord:
+ attributes:
+ doorkeeper/application:
+ name: Nome
+ redirect_uri: URI di reindirizzamento
+ errors:
+ models:
+ doorkeeper/application:
+ attributes:
+ redirect_uri:
+ fragment_present: non può contenere un frammento.
+ invalid_uri: deve essere un URI valido.
+ relative_uri: deve essere un URI assoluto.
+ secured_uri: deve essere un URI HTTPS/SSL.
+ doorkeeper:
+ applications:
+ buttons:
+ authorize: Autorizza
+ cancel: Annulla
+ destroy: Distruggi
+ edit: Modifica
+ submit: Invia
+ confirmations:
+ destroy: Sei sicuro?
+ edit:
+ title: Modifica applicazione
+ form:
+ error: Ooops! Controlla nel modulo la presenza di errori
+ help:
+ native_redirect_uri: Usa %{native_redirect_uri} per test locali
+ redirect_uri: Usa una riga per URI
+ scopes: Dividi gli scopes con spazi. Lascia vuoto per utilizzare gli scopes di default.
+ index:
+ callback_url: Callback URL
+ name: Nome
+ new: Nuova applicazione
+ title: Le tue applicazioni
+ new:
+ title: Nuova applicazione
+ show:
+ actions: Azioni
+ application_id: Id applicazione
+ callback_urls: Callback urls
+ scopes: Scopes
+ secret: Secret
+ title: 'Applicazione: %{name}'
+ authorizations:
+ buttons:
+ authorize: Autorizza
+ deny: Nega
+ error:
+ title: Si è verificato un errore
+ new:
+ able_to: Non sarà in grado di
+ prompt: L'applicazione %{client_name} richiede l'accesso al tuo account
+ title: Autorizzazione richiesta
+ show:
+ title: Codice autorizzazione
+ authorized_applications:
+ buttons:
+ revoke: Disabilita
+ confirmations:
+ revoke: Sei sicuro?
+ index:
+ application: Applicazione
+ created_at: Autorizzato
+ date_format: "%d-%m-%Y %H:%M:%S"
+ scopes: Scopes
+ title: Applicazioni autorizzate
+ errors:
+ messages:
+ access_denied: Il proprietario del servizio o il server di autorizzazione hanno rifiutato la richiesta.
+ credential_flow_not_configured: Il processo Resource Owner Password Credentials è fallito perché Doorkeeper.configure.resource_owner_from_credentials non è stato configurato.
+ invalid_client: Accesso al servizio fallito perché il servizio è sconosciuto, l'accesso al servizio non è stato incluso, o il metodo di accesso non è supportato.
+ invalid_grant: Il permesso d'autorizzazione è non valido, scaduto, disabilitato, non coincide con l'URI di reindirizzamento fornito nella richiesta di autorizzazione, o è stato rilasciato da un altro client.
+ invalid_redirect_uri: L'URI di reindirizzamento fornito non è valido.
+ invalid_request: La richiesta non contiene un parametro necessario, contiene un valore parametrico non supportato, o è altrimenti malformulata.
+ invalid_resource_owner: Le credenziali di accesso fornite per il proprietario non sono corrette, o il proprietario del servizio non è stato trovato
+ invalid_scope: Lo scope richiesto è invalido, sconosciuto, o malformulato.
+ invalid_token:
+ expired: Il token di accesso è scaduto
+ revoked: Il token di accesso è stato disabilitato
+ unknown: Il token di accesso non è valido
+ resource_owner_authenticator_not_configured: Impossibile trovare il proprietario del servizio perché Doorkeeper.configure.resource_owner_authenticator non è stato configurato.
+ server_error: Il server di autorizzazione ha riscontrato un errore imprevisto che non ha permesso di completare la tua richiesta.
+ temporarily_unavailable: Al momento il server di autorizzazione non può completare la tua richiesta a causa di un temporaneo sovraccarico o di manutenzione del server.
+ unauthorized_client: Il client non è autorizzato a eseguire questa operazione con questo metodo.
+ unsupported_grant_type: Questa modalità di trasmissione di autenticazione non è supportata da questo server.
+ unsupported_response_type: Il server autorizzatore non supporta questa modalità di risposta.
+ flash:
+ applications:
+ create:
+ notice: Applicazione creata.
+ destroy:
+ notice: Applicazione cancellata.
+ update:
+ notice: Applicazione aggiornata.
+ authorized_applications:
+ destroy:
+ notice: Applicazione disabilitata.
+ layouts:
+ admin:
+ nav:
+ applications: Applicazioni
+ oauth2_provider: OAuth2 Provider
+ application:
+ title: Autorizzazione OAuth richiesta
+ scopes:
+ follow: seguire, bloccare, sbloccare e smettere di seguire account
+ read: leggere le informazioni del tuo account
+ write: pubblicare post in tua vece
\ No newline at end of file
diff --git a/it.yml b/it.yml
new file mode 100644
index 00000000..453de87a
--- /dev/null
+++ b/it.yml
@@ -0,0 +1,172 @@
+---
+it:
+ about:
+ about_mastodon: Mastodon è un social network gratuito e open-source. Un'alternativa decentralizzata alle piattaforme commerciali che evita che una singola compagnia monopolizzi il tuo modo di comunicare. Scegli un server di cui ti fidi — qualunque sia la tua scelta, potrai interagire con chiunque altro. Chiunque può sviluppare un suo server Mastodon e partecipare alla vita del social network.
+ about_this: A proposito di questo server
+ apps: Applicazioni
+ business_email: 'Email di lavoro:'
+ closed_registrations: Al momento le iscrizioni a questo server sono chiuse.
+ contact: Contatti
+ description_headline: Cos'è %{domain}?
+ domain_count_after: altri server
+ domain_count_before: Connesso a
+ features:
+ api: API aperto per applicazioni e servizi
+ blocks: Potenti strumenti di blocco e silenziamento
+ characters: 500 caratteri per status
+ chronology: Le timeline sono cronologiche
+ ethics: 'Design etico: niente pubblicità, niente tracking'
+ gifv: Set di GIFV e brevi video
+ privacy: Opzioni di privacy mirate per-post
+ public: Timeline pubbliche
+ features_headline: Cosa rende Mastodon migliore
+ get_started: Inizia
+ links: Links
+ other_instances: Altri server
+ source_code: Codice sorgente
+ status_count_after: status
+ status_count_before: Che hanno pubblicato
+ terms: Termini di Utilizzo
+ user_count_after: utenti
+ user_count_before: Casa di
+ accounts:
+ follow: Segui
+ followers: Seguaci
+ following: Seguiti
+ nothing_here: Qui non c'è nulla!
+ people_followed_by: Persone seguite da %{name}
+ people_who_follow: Persone che seguono %{name}
+ posts: Posts
+ remote_follow: Segui da remoto
+ unfollow: Non seguire più
+ application_mailer:
+ settings: 'Cambia le impostazioni per le e-mail: %{link}'
+ signature: Notifiche Mastodon da %{instance}
+ view: 'Guarda:'
+ applications:
+ invalid_url: L'URL fornito non è valido
+ auth:
+ change_password: Credenziali
+ didnt_get_confirmation: Non hai ricevuto le istruzioni di conferma?
+ forgot_password: Hai dimenticato la tua password?
+ login: Entra
+ logout: Logout
+ register: Iscriviti
+ resend_confirmation: Invia di nuovo le istruzioni di conferma
+ reset_password: Resetta la password
+ set_new_password: Imposta una nuova password
+ authorize_follow:
+ error: Sfortunatamente c'è stato un errore nel consultare l'account remoto
+ follow: Segui
+ prompt_html: 'Tu, (%{self}), hai richiesto di seguire:'
+ title: Segui %{acct}
+ datetime:
+ distance_in_words:
+ about_x_hours: "%{count} ore"
+ about_x_months: "%{count} mesi"
+ about_x_years: "%{count} anni"
+ almost_x_years: "%{count} anni"
+ half_a_minute: Adesso
+ less_than_x_minutes: "%{count} minuti"
+ less_than_x_seconds: Adesso
+ over_x_years: "%{count} anni"
+ x_days: "%{count} giorni"
+ x_minutes: "%{count} minuti"
+ x_months: "%{count} mesi"
+ x_seconds: "%{count} secondi"
+ exports:
+ blocks: Stai bloccando
+ csv: CSV
+ follows: Stai seguendo
+ storage: Archiviazione media
+ generic:
+ changes_saved_msg: Modifiche effettuate con successo!
+ powered_by: offerto da %{link}
+ save_changes: Salva modifiche
+ validation_errors:
+ one: Qualcosa ancora non va bene! Per favore, controlla l'errore qui sotto
+ other: Qualcosa ancora non va bene! Per favore, controlla i %{count} errori qui sotto
+ imports:
+ preface: Puoi importare alcune informazioni, come le persone che segui o hai bloccato su questo server, da file creati da un esportazione su un altro server.
+ success: Le tue impostazioni sono state importate correttamente e verranno applicate in breve tempo
+ types:
+ blocking: Lista dei bloccati
+ following: Lista dei seguaci
+ upload: Carica
+ landing_strip_html: %{name} è un utente su %{domain}. Puoi seguirlo o interagire con lui se possiedi un account ovunque nel fediverse. Se non possiedi un account, puoi iscriverti qui.
+ media_attachments:
+ validations:
+ images_and_video: Impossibile allegare video a un post che contiene già immagini
+ too_many: Impossibile allegare più di 4 file
+ notification_mailer:
+ digest:
+ body: 'Ecco un breve riassunto di quello che ti sei perso su %{instance} dalla tua ultima visita del %{since}:'
+ mention: "%{name} ti ha menzionato:"
+ new_followers_summary:
+ one: Hai ricevuto un nuovo seguace! Urrà!
+ other: Hai ricevuto %{count} nuovi seguaci! Incredibile!
+ subject:
+ one: "1 nuova notifica dalla tua ultima visita \U0001F418"
+ other: "%{count} nuove notifiche dalla tua ultima visita \U0001F418"
+ favourite:
+ body: 'Il tuo status è stato apprezzato da %{name}:'
+ subject: "%{name} ha apprezzato il tuo status"
+ follow:
+ body: "%{name} ti sta seguendo!"
+ subject: "%{name} ti sta seguendo"
+ follow_request:
+ body: "%{name} ha chiesto di seguirti"
+ subject: 'Seguace in sospeso: %{name}'
+ mention:
+ body: 'Sei stato menzionato da %{name} su:'
+ subject: Sei stato menzionato da %{name}
+ reblog:
+ body: 'Il tuo status è stato condiviso da %{name}:'
+ subject: "%{name} ha condiviso il tuo status"
+ pagination:
+ next: Avanti
+ prev: Indietro
+ truncate: "…"
+ remote_follow:
+ acct: Inserisci il tuo username@dominio da cui vuoi seguire questo utente
+ missing_resource: Impossibile trovare l'URL di reindirizzamento richiesto per il tuo account
+ proceed: Conferma
+ prompt: 'Stai per seguire:'
+ settings:
+ authorized_apps: Applicazioni autorizzate
+ back: Torna a Mastodon
+ edit_profile: Modifica profilo
+ export: Esporta impostazioni
+ import: Importa
+ preferences: Preferenze
+ settings: Impostazioni
+ two_factor_auth: Autenticazione a Due Fattori
+ statuses:
+ open_in_web: Apri sul Web
+ over_character_limit: Limite caratteri superato di %{max}
+ show_more: Mostra di più
+ visibilities:
+ private: Mostra solo ai tuoi seguaci
+ public: Pubblico
+ unlisted: Pubblico, ma non visibile sulla timeline pubblica
+ stream_entries:
+ click_to_show: Clicca per mostrare
+ reblogged: condiviso
+ sensitive_content: Materiale sensibile
+ time:
+ formats:
+ default: "%b %d, %Y, %H:%M"
+ two_factor_auth:
+ code_hint: Inserisci il codice generato dalla tua app di autenticazione
+ description_html: Se abiliti l'autorizzazione a due fattori, entrare nel tuo account ti richiederà di avere vicino il tuo telefono, il quale ti genererà un codice per eseguire l'accesso.
+ disable: Disabilita
+ enable: Abilita
+ enabled_success: Autenticazione a due fattori attivata con successo
+ instructions_html: "Scannerizza questo QR code con Google Authenticator o un'app TOTP simile sul tuo telefono. Da ora in poi, quell'applicazione genererà codici da inserire necessariamente per eseguire l'accesso."
+ manual_instructions: 'Se non puoi scannerizzare il QR code e hai bisogno di inserirlo manualmente, questo è il codice segreto in chiaro:'
+ setup: Configura
+ warning: Se non puoi convalidare immediatamente la tua app di autenticazione, dovresti selezionare "disabilita" o non sarai più in grado di eseguire l'accesso.
+ wrong_code: Il codice inserito non è corretto! Assicurati che l'orario del server e l'orario del telefono siano corretti.
+ users:
+ invalid_email: L'indirizzo e-mail inserito non è valido
+ invalid_otp_token: Codice d'accesso non valido
diff --git a/lib/tasks/mastodon.rake b/lib/tasks/mastodon.rake
index 79dcb722..4761b291 100644
--- a/lib/tasks/mastodon.rake
+++ b/lib/tasks/mastodon.rake
@@ -10,6 +10,18 @@ namespace :mastodon do
puts "Congrats! #{user.account.username} is now an admin. \\o/\nNavigate to #{admin_settings_url} to get started"
end
+ desc 'Manually confirms a user with associated user email address stored in USER_EMAIL environment variable.'
+ task confirm_email: :environment do
+ email = ENV.fetch('USER_EMAIL')
+ user = User.where(email: email).first
+ if user
+ user.update(confirmed_at: Time.now.utc)
+ puts "User #{email} confirmed."
+ else
+ abort "User #{email} not found."
+ end
+ end
+
namespace :media do
desc 'Removes media attachments that have not been assigned to any status for longer than a day'
task clear: :environment do
@@ -63,6 +75,13 @@ namespace :mastodon do
end
end
+ namespace :users do
+ desc 'clear unconfirmed users'
+ task clear: :environment do
+ User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).find_each(&:destroy)
+ end
+ end
+
namespace :maintenance do
desc 'Update counter caches'
task update_counter_caches: :environment do
@@ -80,5 +99,17 @@ namespace :mastodon do
Rails.logger.debug 'Done!'
end
+
+ desc 'Generate static versions of GIF avatars/headers'
+ task add_static_avatars: :environment do
+ Rails.logger.debug 'Generating static avatars/headers for GIF ones...'
+
+ Account.unscoped.where(avatar_content_type: 'image/gif').or(Account.unscoped.where(header_content_type: 'image/gif')).find_each do |account|
+ account.avatar.reprocess!
+ account.header.reprocess!
+ end
+
+ Rails.logger.debug 'Done!'
+ end
end
end
diff --git a/package.json b/package.json
index 14c8abe7..df496c55 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,7 @@
"escape-html": "^1.0.3",
"eventsource": "^0.2.1",
"express": "^4.14.1",
- "http-link-header": "^0.5.0",
+ "http-link-header": "^0.8.0",
"immutable": "^3.8.1",
"intl": "^1.2.5",
"jsdom": "^9.11.0",
@@ -72,5 +72,10 @@
"webpack": "^2.2.1",
"websocket.js": "^0.1.7",
"ws": "^2.1.0"
+ },
+ "devDependencies": {
+ "babel-eslint": "^7.2.1",
+ "eslint": "^3.19.0",
+ "eslint-plugin-react": "^6.10.3"
}
}
diff --git a/scalingo.json b/scalingo.json
new file mode 100644
index 00000000..4afaa6b4
--- /dev/null
+++ b/scalingo.json
@@ -0,0 +1,99 @@
+{
+ "name": "Mastodon",
+ "description": "A GNU Social-compatible microblogging server",
+ "repository": "https://github.com/tootsuite/mastodon",
+ "logo": "https://github.com/tootsuite/mastodon/raw/master/app/assets/images/logo.png",
+ "env": {
+ "LOCAL_DOMAIN": {
+ "description": "The domain that your Mastodon instance will run on (this can be appname.scalingo.io or a custom domain)",
+ "required": true
+ },
+ "LOCAL_HTTPS": {
+ "description": "Will your domain support HTTPS? (Automatic for *.scalingo.io, requires manual configuration for custom domains)",
+ "value": "true",
+ "required": true
+ },
+ "PAPERCLIP_SECRET": {
+ "description": "The secret key for storing media files",
+ "generator": "secret"
+ },
+ "SECRET_KEY_BASE": {
+ "description": "The secret key base",
+ "generator": "secret"
+ },
+ "SINGLE_USER_MODE": {
+ "description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)",
+ "value": "false",
+ "required": true
+ },
+ "S3_ENABLED": {
+ "description": "Should Mastodon use Amazon S3 for storage? This is highly recommended, as Scalingo does not have persistent file storage (files will be lost).",
+ "value": "true",
+ "required": false
+ },
+ "S3_BUCKET": {
+ "description": "Amazon S3 Bucket",
+ "required": false
+ },
+ "S3_REGION": {
+ "description": "Amazon S3 region that the bucket is located in",
+ "required": false
+ },
+ "AWS_ACCESS_KEY_ID": {
+ "description": "Amazon S3 Access Key",
+ "required": false
+ },
+ "AWS_SECRET_ACCESS_KEY": {
+ "description": "Amazon S3 Secret Key",
+ "required": false
+ },
+ "SMTP_SERVER": {
+ "description": "Hostname for SMTP server, if you want to enable email",
+ "required": false
+ },
+ "SMTP_PORT": {
+ "description": "Port for SMTP server",
+ "required": false
+ },
+ "SMTP_LOGIN": {
+ "description": "Username for SMTP server",
+ "required": false
+ },
+ "SMTP_PASSWORD": {
+ "description": "Password for SMTP server",
+ "required": false
+ },
+ "SMTP_DOMAIN": {
+ "description": "Domain for SMTP server. Will default to instance domain if blank.",
+ "required": false
+ },
+ "SMTP_FROM_ADDRESS": {
+ "description": "Address to send emails from",
+ "required": false
+ },
+ "SMTP_AUTH_METHOD": {
+ "description": "Authentication method to use with SMTP server. Default is 'plain'.",
+ "required": false
+ },
+ "SMTP_OPENSSL_VERIFY_MODE": {
+ "description": "SMTP server certificate verification mode. Defaults is 'peer'.",
+ "required": false
+ },
+ "SMTP_ENABLE_STARTTLS_AUTO": {
+ "description": "Enable STARTTLS if SMTP server supports it? Default is true.",
+ "required": false
+ },
+ "BUILDPACK_URL": {
+ "description": "Internal scalingo configuration",
+ "required": true,
+ "value": "https://github.com/Scalingo/multi-buildpack.git"
+ }
+ },
+ "scripts": {
+ "postdeploy": "bundle exec rails db:migrate && bundle exec rails db:seed"
+ },
+ "addons": [
+ "scalingo-postgresql",
+ "scalingo-redis"
+ ]
+}
diff --git a/simple_form.it.yml b/simple_form.it.yml
new file mode 100644
index 00000000..1c5026e3
--- /dev/null
+++ b/simple_form.it.yml
@@ -0,0 +1,47 @@
+---
+it:
+ simple_form:
+ hints:
+ defaults:
+ avatar: PNG, GIF o JPG. Al massimo 2MB. Sarà ridotto a 120x120px
+ display_name: Al massimo 30 characters
+ header: PNG, GIF or JPG. Al massimo 2MB. Sarà ridotto a 700x335px
+ locked: Richiede la tua approvazione per i nuovi seguaci e rende i nuovi post automaticamente visibili solo ai seguaci
+ note: Al massimo 160 caratteri
+ imports:
+ data: CSV esportato da un altro server Mastodon
+ labels:
+ defaults:
+ avatar: Avatar
+ confirm_new_password: Conferma la nuova password
+ confirm_password: Conferma la password
+ current_password: Password corrente
+ data: Data
+ display_name: Nome pubblico
+ email: Indirizzo e-mail
+ header: Header
+ locale: Lingua
+ locked: Rendi l'account privato
+ new_password: Nuova password
+ note: Biografia
+ otp_attempt: Codice d'accesso
+ password: Password
+ setting_default_privacy: Privacy del post
+ type: Importa
+ username: Username
+ setting_boost_modal: Mostra finestra di conferma prima di condividere
+ interactions:
+ must_be_follower: Blocca notifiche da chi non ti segue
+ must_be_following: Blocca notifiche da chi non segui
+ notification_emails:
+ digest: Invia riassunto via e-mail
+ favourite: Invia e-mail quando qualcuno apprezza i tuoi status
+ follow: Invia e-mail quando qualcuno ti segue
+ follow_request: Invia e-mail quando qualcuno ti richiede di seguirti
+ mention: Invia e-mail quando qualcuno ti menziona
+ reblog: Invia e-mail quando qualcuno condivide i tuoi status
+ 'no': 'No'
+ required:
+ mark: "*"
+ text: richiesto
+ 'yes': 'Sì'
\ No newline at end of file
diff --git a/spec/controllers/about_controller_spec.rb b/spec/controllers/about_controller_spec.rb
index 4282649e..f49de962 100644
--- a/spec/controllers/about_controller_spec.rb
+++ b/spec/controllers/about_controller_spec.rb
@@ -3,9 +3,16 @@ require 'rails_helper'
RSpec.describe AboutController, type: :controller do
render_views
- describe 'GET #index' do
+ describe 'GET #show' do
it 'returns http success' do
- get :index
+ get :show
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'GET #more' do
+ it 'returns http success' do
+ get :more
expect(response).to have_http_status(:success)
end
end
diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
new file mode 100644
index 00000000..622ea87c
--- /dev/null
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+RSpec.describe Admin::ReportsController, type: :controller do
+ describe 'GET #index' do
+ before do
+ sign_in Fabricate(:user, admin: true), scope: :user
+ end
+
+ it 'returns http success' do
+ get :index
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/admin/settings_controller_spec.rb b/spec/controllers/admin/settings_controller_spec.rb
new file mode 100644
index 00000000..c126b645
--- /dev/null
+++ b/spec/controllers/admin/settings_controller_spec.rb
@@ -0,0 +1,14 @@
+require 'rails_helper'
+
+RSpec.describe Admin::SettingsController, type: :controller do
+ describe 'GET #index' do
+ before do
+ sign_in Fabricate(:user, admin: true), scope: :user
+ end
+
+ it 'returns http success' do
+ get :index
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/admin/silences_controller_spec.rb b/spec/controllers/admin/silences_controller_spec.rb
new file mode 100644
index 00000000..7c541d79
--- /dev/null
+++ b/spec/controllers/admin/silences_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe Admin::SilencesController do
+ let(:account) { Fabricate(:account) }
+ 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
+
+ describe 'DELETE #destroy' do
+ it 'redirects to admin accounts page' do
+ delete :destroy, params: { account_id: account.id }
+
+ expect(response).to redirect_to(admin_accounts_path)
+ end
+ end
+end
diff --git a/spec/controllers/admin/suspensions_controller_spec.rb b/spec/controllers/admin/suspensions_controller_spec.rb
new file mode 100644
index 00000000..9096f067
--- /dev/null
+++ b/spec/controllers/admin/suspensions_controller_spec.rb
@@ -0,0 +1,24 @@
+require 'rails_helper'
+
+describe Admin::SuspensionsController do
+ let(:account) { Fabricate(:account) }
+ 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
+
+ describe 'DELETE #destroy' do
+ it 'redirects to admin accounts page' do
+ delete :destroy, params: { account_id: account.id }
+
+ expect(response).to redirect_to(admin_accounts_path)
+ end
+ end
+end
diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb
index 5d36b015..ed49779b 100644
--- a/spec/controllers/api/v1/accounts_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts_controller_spec.rb
@@ -24,6 +24,45 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
end
end
+ describe 'PATCH #update_credentials' do
+ describe 'with valid data' do
+ before do
+ avatar = File.read(Rails.root.join('app', 'assets', 'images', 'logo.png'))
+ header = File.read(Rails.root.join('app', 'assets', 'images', 'mastodon-getting-started.png'))
+
+ patch :update_credentials, params: {
+ display_name: "Alice Isn't Dead",
+ note: "Hi!\n\nToot toot!",
+ avatar: "data:image/png;base64,#{Base64.encode64(avatar)}",
+ header: "data:image/png;base64,#{Base64.encode64(header)}",
+ }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'updates account info' do
+ user.account.reload
+
+ expect(user.account.display_name).to eq("Alice Isn't Dead")
+ expect(user.account.note).to eq("Hi!\n\nToot toot!")
+ expect(user.account.avatar).to exist
+ expect(user.account.header).to exist
+ end
+ end
+
+ describe 'with invalid data' do
+ before do
+ patch :update_credentials, params: { note: 'This is too long. ' * 10 }
+ end
+
+ it 'returns http unprocessable entity' do
+ expect(response).to have_http_status(:unprocessable_entity)
+ end
+ end
+ end
+
describe 'GET #statuses' do
it 'returns http success' do
get :statuses, params: { id: user.account.id }
diff --git a/spec/controllers/api/v1/notifications_controller_spec.rb b/spec/controllers/api/v1/notifications_controller_spec.rb
index e5f7eec7..e3c95309 100644
--- a/spec/controllers/api/v1/notifications_controller_spec.rb
+++ b/spec/controllers/api/v1/notifications_controller_spec.rb
@@ -5,15 +5,72 @@ RSpec.describe Api::V1::NotificationsController, type: :controller do
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { double acceptable?: true, resource_owner_id: user.id }
+ let(:other) { Fabricate(:user, account: Fabricate(:account, username: 'bob')) }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
- it 'returns http success' do
- get :index
- expect(response).to have_http_status(:success)
+ before do
+ first_status = PostStatusService.new.call(user.account, 'Test')
+ @reblog_of_first_status = ReblogService.new.call(other.account, first_status)
+ mentioning_status = PostStatusService.new.call(other.account, 'Hello @alice')
+ @mention_from_status = mentioning_status.mentions.first
+ @favourite = FavouriteService.new.call(other.account, first_status)
+ @follow = FollowService.new.call(other.account, 'alice')
+ end
+
+ describe 'with no options' do
+ before do
+ get :index
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'includes reblog' do
+ expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status)
+ end
+
+ it 'includes mention' do
+ expect(assigns(:notifications).map(&:activity)).to include(@mention_from_status)
+ end
+
+ it 'includes favourite' do
+ expect(assigns(:notifications).map(&:activity)).to include(@favourite)
+ end
+
+ it 'includes follow' do
+ expect(assigns(:notifications).map(&:activity)).to include(@follow)
+ end
+ end
+
+ describe 'with excluded mentions' do
+ before do
+ get :index, params: { exclude_types: ['mention'] }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'includes reblog' do
+ expect(assigns(:notifications).map(&:activity)).to include(@reblog_of_first_status)
+ end
+
+ it 'excludes mention' do
+ expect(assigns(:notifications).map(&:activity)).to_not include(@mention_from_status)
+ end
+
+ it 'includes favourite' do
+ expect(assigns(:notifications).map(&:activity)).to include(@favourite)
+ end
+
+ it 'includes follow' do
+ expect(assigns(:notifications).map(&:activity)).to include(@follow)
+ end
end
end
end
diff --git a/spec/controllers/auth/registrations_controller_spec.rb b/spec/controllers/auth/registrations_controller_spec.rb
index 27ad6cbd..6b26e669 100644
--- a/spec/controllers/auth/registrations_controller_spec.rb
+++ b/spec/controllers/auth/registrations_controller_spec.rb
@@ -5,6 +5,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
describe 'GET #new' do
before do
+ Setting.open_registrations = true
request.env["devise.mapping"] = Devise.mappings[:user]
end
@@ -16,6 +17,7 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
describe 'POST #create' do
before do
+ Setting.open_registrations = true
request.env["devise.mapping"] = Devise.mappings[:user]
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678' } }
end
diff --git a/spec/controllers/settings/exports/blocked_accounts_controller_spec.rb b/spec/controllers/settings/exports/blocked_accounts_controller_spec.rb
new file mode 100644
index 00000000..c815bdfe
--- /dev/null
+++ b/spec/controllers/settings/exports/blocked_accounts_controller_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe Settings::Exports::BlockedAccountsController do
+ before do
+ sign_in Fabricate(:user), scope: :user
+ end
+
+ describe 'GET #index' do
+ it 'returns a csv of the blocking accounts' do
+ get :index, format: :csv
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq 'text/csv'
+ expect(response.headers['Content-Disposition']).to eq 'attachment; filename="blocked_accounts.csv"'
+ end
+ end
+end
diff --git a/spec/controllers/settings/exports/following_accounts_controller_spec.rb b/spec/controllers/settings/exports/following_accounts_controller_spec.rb
new file mode 100644
index 00000000..a7029709
--- /dev/null
+++ b/spec/controllers/settings/exports/following_accounts_controller_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe Settings::Exports::FollowingAccountsController do
+ before do
+ sign_in Fabricate(:user), scope: :user
+ end
+
+ describe 'GET #index' do
+ it 'returns a csv of the following accounts' do
+ get :index, format: :csv
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq 'text/csv'
+ expect(response.headers['Content-Disposition']).to eq 'attachment; filename="following_accounts.csv"'
+ end
+ end
+end
diff --git a/spec/controllers/settings/exports/muted_accounts_controller_spec.rb b/spec/controllers/settings/exports/muted_accounts_controller_spec.rb
new file mode 100644
index 00000000..bb52b6fc
--- /dev/null
+++ b/spec/controllers/settings/exports/muted_accounts_controller_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe Settings::Exports::MutedAccountsController do
+ before do
+ sign_in Fabricate(:user), scope: :user
+ end
+
+ describe 'GET #index' do
+ it 'returns a csv of the muting accounts' do
+ get :index, format: :csv
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq 'text/csv'
+ expect(response.headers['Content-Disposition']).to eq 'attachment; filename="muted_accounts.csv"'
+ end
+ end
+end
diff --git a/spec/controllers/settings/exports_controller_spec.rb b/spec/controllers/settings/exports_controller_spec.rb
new file mode 100644
index 00000000..2be6e474
--- /dev/null
+++ b/spec/controllers/settings/exports_controller_spec.rb
@@ -0,0 +1,17 @@
+require 'rails_helper'
+
+describe Settings::ExportsController do
+ render_views
+
+ before do
+ sign_in Fabricate(:user), scope: :user
+ end
+
+ describe 'GET #show' do
+ it 'returns http success' do
+ get :show
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb
new file mode 100644
index 00000000..d57350a1
--- /dev/null
+++ b/spec/controllers/settings/imports_controller_spec.rb
@@ -0,0 +1,43 @@
+require 'rails_helper'
+
+RSpec.describe Settings::ImportsController, type: :controller do
+
+ before do
+ sign_in Fabricate(:user), scope: :user
+ end
+
+ describe "GET #show" do
+ it "returns http success" do
+ get :show
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+ describe 'POST #create' do
+ it 'redirects to settings path with successful following import' do
+ service = double(call: nil)
+ allow(FollowRemoteAccountService).to receive(:new).and_return(service)
+ post :create, params: {
+ import: {
+ type: 'following',
+ data: fixture_file_upload('files/imports.txt')
+ }
+ }
+
+ expect(response).to redirect_to(settings_import_path)
+ end
+
+ it 'redirects to settings path with successful blocking import' do
+ service = double(call: nil)
+ allow(FollowRemoteAccountService).to receive(:new).and_return(service)
+ post :create, params: {
+ import: {
+ type: 'blocking',
+ data: fixture_file_upload('files/imports.txt')
+ }
+ }
+
+ expect(response).to redirect_to(settings_import_path)
+ end
+ end
+end
diff --git a/spec/controllers/stream_entries_controller_spec.rb b/spec/controllers/stream_entries_controller_spec.rb
index 6f270af9..71de6060 100644
--- a/spec/controllers/stream_entries_controller_spec.rb
+++ b/spec/controllers/stream_entries_controller_spec.rb
@@ -17,4 +17,14 @@ RSpec.describe StreamEntriesController, type: :controller do
expect(response).to have_http_status(:success)
end
end
+
+ describe 'GET #embed' do
+ it 'returns embedded view of status' do
+ get :embed, params: { account_username: alice.username, id: status.stream_entry.id }
+
+ expect(response).to have_http_status(:success)
+ expect(response.headers['X-Frame-Options']).to eq 'ALLOWALL'
+ expect(response).to render_template(layout: 'embedded')
+ end
+ end
end
diff --git a/spec/controllers/well_known/host_meta_controller_spec.rb b/spec/controllers/well_known/host_meta_controller_spec.rb
new file mode 100644
index 00000000..8a040021
--- /dev/null
+++ b/spec/controllers/well_known/host_meta_controller_spec.rb
@@ -0,0 +1,13 @@
+require 'rails_helper'
+
+describe WellKnown::HostMetaController, type: :controller do
+ render_views
+
+ describe 'GET #show' do
+ it 'returns http success' do
+ get :show, format: :xml
+
+ expect(response).to have_http_status(:success)
+ end
+ end
+end
diff --git a/spec/controllers/well_known/webfinger_controller_spec.rb b/spec/controllers/well_known/webfinger_controller_spec.rb
new file mode 100644
index 00000000..6e493b03
--- /dev/null
+++ b/spec/controllers/well_known/webfinger_controller_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe WellKnown::WebfingerController, type: :controller do
+ render_views
+
+ describe 'GET #show' do
+ let(:alice) { Fabricate(:account, username: 'alice') }
+
+ it 'returns http success when account can be found' do
+ get :show, params: { resource: alice.to_webfinger_s }, format: :json
+
+ expect(response).to have_http_status(:success)
+ end
+
+ it 'returns http not found when account cannot be found' do
+ get :show, params: { resource: 'acct:not@existing.com' }, format: :json
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
+end
diff --git a/spec/controllers/xrd_controller_spec.rb b/spec/controllers/xrd_controller_spec.rb
deleted file mode 100644
index e687cf9e..00000000
--- a/spec/controllers/xrd_controller_spec.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe XrdController, type: :controller do
- render_views
-
- describe 'GET #host_meta' do
- it 'returns http success' do
- get :host_meta
- expect(response).to have_http_status(:success)
- end
- end
-
- describe 'GET #webfinger' do
- let(:alice) { Fabricate(:account, username: 'alice') }
-
- it 'returns http success when account can be found' do
- get :webfinger, params: { resource: "acct:#{alice.username}@#{Rails.configuration.x.local_domain}" }
- expect(response).to have_http_status(:success)
- end
-
- it 'returns http not found when account cannot be found' do
- get :webfinger, params: { resource: 'acct:not@existing.com' }
- expect(response).to have_http_status(:not_found)
- end
- end
-end
diff --git a/spec/fabricators/account_fabricator.rb b/spec/fabricators/account_fabricator.rb
index 3a7c00bf..567de05f 100644
--- a/spec/fabricators/account_fabricator.rb
+++ b/spec/fabricators/account_fabricator.rb
@@ -1,3 +1,3 @@
Fabricator(:account) do
- username "alice"
+ username { Faker::Internet.user_name(nil, %w(_)) }
end
diff --git a/spec/fabricators/block_fabricator.rb b/spec/fabricators/block_fabricator.rb
index 9a5a6808..379931ba 100644
--- a/spec/fabricators/block_fabricator.rb
+++ b/spec/fabricators/block_fabricator.rb
@@ -1,3 +1,4 @@
Fabricator(:block) do
-
+ account
+ target_account { Fabricate(:account) }
end
diff --git a/spec/fabricators/follow_fabricator.rb b/spec/fabricators/follow_fabricator.rb
index 9d9d06f1..9b25dc54 100644
--- a/spec/fabricators/follow_fabricator.rb
+++ b/spec/fabricators/follow_fabricator.rb
@@ -1,3 +1,4 @@
Fabricator(:follow) do
-
+ account
+ target_account { Fabricate(:account) }
end
diff --git a/spec/fabricators/follow_request_fabricator.rb b/spec/fabricators/follow_request_fabricator.rb
index 9c3733ce..78a05791 100644
--- a/spec/fabricators/follow_request_fabricator.rb
+++ b/spec/fabricators/follow_request_fabricator.rb
@@ -1,3 +1,4 @@
Fabricator(:follow_request) do
-
+ account
+ target_account { Fabricate(:account) }
end
diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb
index 59db2440..dc91d708 100644
--- a/spec/fabricators/media_attachment_fabricator.rb
+++ b/spec/fabricators/media_attachment_fabricator.rb
@@ -1,3 +1,3 @@
Fabricator(:media_attachment) do
-
+ account
end
diff --git a/spec/fabricators/mention_fabricator.rb b/spec/fabricators/mention_fabricator.rb
new file mode 100644
index 00000000..cb5fe429
--- /dev/null
+++ b/spec/fabricators/mention_fabricator.rb
@@ -0,0 +1,4 @@
+Fabricator(:mention) do
+ account
+ status
+end
diff --git a/spec/fabricators/status_fabricator.rb b/spec/fabricators/status_fabricator.rb
index df222fc9..8ec5f4ba 100644
--- a/spec/fabricators/status_fabricator.rb
+++ b/spec/fabricators/status_fabricator.rb
@@ -1,3 +1,4 @@
Fabricator(:status) do
+ account
text "Lorem ipsum dolor sit amet"
end
diff --git a/spec/fabricators/user_fabricator.rb b/spec/fabricators/user_fabricator.rb
index c0855913..16b3b1f6 100644
--- a/spec/fabricators/user_fabricator.rb
+++ b/spec/fabricators/user_fabricator.rb
@@ -1,6 +1,6 @@
Fabricator(:user) do
account
- email "alice@example.com"
+ email { Faker::Internet.email }
password "123456789"
confirmed_at { Time.now }
end
diff --git a/spec/fixtures/files/avatar.gif b/spec/fixtures/files/avatar.gif
new file mode 100644
index 00000000..d929801e
Binary files /dev/null and b/spec/fixtures/files/avatar.gif differ
diff --git a/spec/fixtures/files/imports.txt b/spec/fixtures/files/imports.txt
new file mode 100644
index 00000000..dac295c4
--- /dev/null
+++ b/spec/fixtures/files/imports.txt
@@ -0,0 +1,3 @@
+user@example.com
+
+user@test.com
diff --git a/spec/helpers/about_helper_spec.rb b/spec/helpers/about_helper_spec.rb
deleted file mode 100644
index 6efc9f5b..00000000
--- a/spec/helpers/about_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe AboutHelper, type: :helper do
-
-end
diff --git a/spec/helpers/accounts_helper_spec.rb b/spec/helpers/accounts_helper_spec.rb
deleted file mode 100644
index 3aea1f90..00000000
--- a/spec/helpers/accounts_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe AccountsHelper, type: :helper do
-
-end
diff --git a/spec/helpers/admin/domain_blocks_helper_spec.rb b/spec/helpers/admin/domain_blocks_helper_spec.rb
deleted file mode 100644
index cc7ead84..00000000
--- a/spec/helpers/admin/domain_blocks_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Admin::DomainBlocksHelper, type: :helper do
-
-end
diff --git a/spec/helpers/admin/pubsubhubbub_helper_spec.rb b/spec/helpers/admin/pubsubhubbub_helper_spec.rb
deleted file mode 100644
index 673236a7..00000000
--- a/spec/helpers/admin/pubsubhubbub_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe Admin::PubsubhubbubHelper, type: :helper do
-
-end
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index c2063c99..a2eeb443 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -1,5 +1,19 @@
require 'rails_helper'
-RSpec.describe ApplicationHelper, type: :helper do
+describe ApplicationHelper do
+ describe 'active_nav_class' do
+ it 'returns active when on the current page' do
+ allow(helper).to receive(:current_page?).and_return(true)
+ result = helper.active_nav_class("/test")
+ expect(result).to eq "active"
+ end
+
+ it 'returns empty string when not on current page' do
+ allow(helper).to receive(:current_page?).and_return(false)
+
+ result = helper.active_nav_class("/test")
+ expect(result).to eq ""
+ end
+ end
end
diff --git a/spec/helpers/atom_builder_helper_spec.rb b/spec/helpers/atom_builder_helper_spec.rb
deleted file mode 100644
index 0aca58ee..00000000
--- a/spec/helpers/atom_builder_helper_spec.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe AtomBuilderHelper, type: :helper do
- describe '#stream_updated_at' do
- pending
- end
-
- describe '#entry' do
- it 'creates an entry' do
- expect(used_in_builder { |xml| helper.entry(xml) }).to match ''
- end
- end
-
- describe '#feed' do
- it 'creates a feed' do
- expect(used_in_builder { |xml| helper.feed(xml) }).to match ''
- end
- end
-
- describe '#unique_id' do
- it 'creates an id' do
- time = Time.now
- expect(used_in_builder { |xml| helper.unique_id(xml, time, 1, 'Status') }).to match "#{TagManager.instance.unique_tag(time, 1, 'Status')}"
- end
- end
-
- describe '#simple_id' do
- it 'creates an id' do
- expect(used_in_builder { |xml| helper.simple_id(xml, 1) }).to match '1'
- end
- end
-
- describe '#published_at' do
- it 'creates a published tag' do
- time = Time.now
- expect(used_in_builder { |xml| helper.published_at(xml, time) }).to match "#{time.iso8601}"
- end
- end
-
- describe '#updated_at' do
- it 'creates an updated tag' do
- time = Time.now
- expect(used_in_builder { |xml| helper.updated_at(xml, time) }).to match "#{time.iso8601}"
- end
- end
-
- describe '#verb' do
- it 'creates an entry' do
- expect(used_with_namespaces { |xml| helper.verb(xml, :post) }).to match 'http://activitystrea.ms/schema/1.0/post'
- end
- end
-
- describe '#content' do
- it 'creates a content' do
- expect(used_in_builder { |xml| helper.content(xml, 'foo') }).to match 'foo'
- end
- end
-
- describe '#title' do
- it 'creates a title' do
- expect(used_in_builder { |xml| helper.title(xml, 'foo') }).to match 'foo'
- end
- end
-
- describe '#author' do
- it 'creates an author' do
- expect(used_in_builder { |xml| helper.author(xml) }).to match ''
- end
- end
-
- describe '#target' do
- it 'creates a target' do
- expect(used_with_namespaces { |xml| helper.target(xml) }).to match ''
- end
- end
-
- describe '#object_type' do
- it 'creates an object type' do
- expect(used_with_namespaces { |xml| helper.object_type(xml, :person) }).to match 'http://activitystrea.ms/schema/1.0/person'
- end
- end
-
- describe '#uri' do
- it 'creates a uri' do
- expect(used_in_builder { |xml| helper.uri(xml, 1) }).to match '1'
- end
- end
-
- describe '#name' do
- it 'creates a name' do
- expect(used_in_builder { |xml| helper.name(xml, 1) }).to match '1'
- end
- end
-
- describe '#summary' do
- it 'creates a summary' do
- expect(used_in_builder { |xml| helper.summary(xml, 1) }).to match '1'
- end
- end
-
- describe '#subtitle' do
- it 'creates a subtitle' do
- expect(used_in_builder { |xml| helper.subtitle(xml, 1) }).to match '1'
- end
- end
-
- describe '#link_alternate' do
- it 'creates a link' do
- expect(used_in_builder { |xml| helper.link_alternate(xml, 1) }).to match ''
- end
- end
-
- describe '#link_self' do
- it 'creates a link' do
- expect(used_in_builder { |xml| helper.link_self(xml, 1) }).to match ''
- end
- end
-
- describe '#link_hub' do
- it 'creates a link' do
- expect(used_in_builder { |xml| helper.link_hub(xml, 1) }).to match ''
- end
- end
-
- describe '#link_salmon' do
- it 'creates a link' do
- expect(used_in_builder { |xml| helper.link_salmon(xml, 1) }).to match ''
- end
- end
-
- describe '#portable_contact' do
- let(:account) { Fabricate(:account, username: 'alice', display_name: 'Alice in Wonderland') }
-
- it 'creates portable contacts entries' do
- expect(used_with_namespaces { |xml| helper.portable_contact(xml, account) }).to match 'Alice in Wonderland'
- end
- end
-
- describe '#in_reply_to' do
- it 'creates a thread' do
- expect(used_with_namespaces { |xml| helper.in_reply_to(xml, 'uri', 'url') }).to match ''
- end
- end
-
- describe '#link_mention' do
- let(:account) { Fabricate(:account, username: 'alice') }
-
- it 'creates a link' do
- expect(used_in_builder { |xml| helper.link_mention(xml, account) }).to match ''
- end
- end
-
- describe '#include_author' do
- pending
- end
-
- describe '#include_entry' do
- pending
- end
-
- describe '#link_avatar' do
- let(:account) { Fabricate(:account, username: 'alice') }
-
- it 'creates a link' do
- expect(used_with_namespaces { |xml| helper.link_avatar(xml, account) }).to match ''
- end
- end
-
- describe '#link_enclosure' do
- pending
- end
-
- describe '#logo' do
- it 'creates a logo' do
- expect(used_in_builder { |xml| helper.logo(xml, 1) }).to match '1'
- end
- end
-
- def used_in_builder(&block)
- builder = Nokogiri::XML::Builder.new(&block)
- builder.doc.root.to_xml
- end
-
- def used_with_namespaces(&block)
- used_in_builder { |xml| helper.entry(xml, true, &block) }
- end
-end
diff --git a/spec/helpers/authorize_follow_helper_spec.rb b/spec/helpers/authorize_follow_helper_spec.rb
deleted file mode 100644
index ba5b0a70..00000000
--- a/spec/helpers/authorize_follow_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe AuthorizeFollowHelper, type: :helper do
-
-end
diff --git a/spec/helpers/site_title_helper_spec.rb b/spec/helpers/site_title_helper_spec.rb
new file mode 100644
index 00000000..8cfd9cba
--- /dev/null
+++ b/spec/helpers/site_title_helper_spec.rb
@@ -0,0 +1,15 @@
+require "rails_helper"
+
+describe "site_title" do
+ it "Uses the Setting.site_title value when it exists" do
+ Setting.site_title = "New site title"
+
+ expect(helper.site_title).to eq "New site title"
+ end
+
+ it "returns empty string when Setting.site_title is nil" do
+ Setting.site_title = nil
+
+ expect(helper.site_title).to eq ""
+ end
+end
diff --git a/spec/helpers/stream_entries_helper_spec.rb b/spec/helpers/stream_entries_helper_spec.rb
index 6227f928..e2544e31 100644
--- a/spec/helpers/stream_entries_helper_spec.rb
+++ b/spec/helpers/stream_entries_helper_spec.rb
@@ -2,26 +2,16 @@ require 'rails_helper'
RSpec.describe StreamEntriesHelper, type: :helper do
describe '#display_name' do
- pending
- end
+ it 'uses the display name when it exists' do
+ account = Account.new(display_name: "Display", username: "Username")
- describe '#avatar_for_status_url' do
- pending
- end
+ expect(helper.display_name(account)).to eq "Display"
+ end
- describe '#entry_classes' do
- pending
- end
+ it 'uses the username when display name is nil' do
+ account = Account.new(display_name: nil, username: "Username")
- describe '#relative_time' do
- pending
- end
-
- describe '#reblogged_by_me_class' do
- pending
- end
-
- describe '#favourited_by_me_class' do
- pending
+ expect(helper.display_name(account)).to eq "Username"
+ end
end
end
diff --git a/spec/helpers/tags_helper_spec.rb b/spec/helpers/tags_helper_spec.rb
deleted file mode 100644
index f661e44a..00000000
--- a/spec/helpers/tags_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe TagsHelper, type: :helper do
-
-end
diff --git a/spec/helpers/xrd_helper_spec.rb b/spec/helpers/xrd_helper_spec.rb
deleted file mode 100644
index 0bc71b65..00000000
--- a/spec/helpers/xrd_helper_spec.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe XrdHelper, type: :helper do
-
-end
diff --git a/spec/javascript/components/avatar.test.jsx b/spec/javascript/components/avatar.test.jsx
index 852e13a8..7131bbec 100644
--- a/spec/javascript/components/avatar.test.jsx
+++ b/spec/javascript/components/avatar.test.jsx
@@ -6,16 +6,10 @@ import Avatar from '../../../app/assets/javascripts/components/components/avatar
describe('', () => {
const src = '/path/to/image.jpg';
const size = 100;
- const wrapper = render();
+ const wrapper = render();
- it('renders an img element with the given src', () => {
- expect(wrapper.find('img')).to.have.attr('src', `${src}`);
- });
-
- it('renders an img element of the given size', () => {
- ['width', 'height'].map((attr) => {
- expect(wrapper.find('img')).to.have.attr(attr, `${size}`);
- });
+ it('renders a div element with the given src as background', () => {
+ expect(wrapper.find('div')).to.have.style('background-image', `url(${src})`);
});
it('renders a div element of the given size', () => {
diff --git a/spec/javascript/components/features/ui/components/column.test.jsx b/spec/javascript/components/features/ui/components/column.test.jsx
new file mode 100644
index 00000000..b7e25775
--- /dev/null
+++ b/spec/javascript/components/features/ui/components/column.test.jsx
@@ -0,0 +1,30 @@
+import { expect } from 'chai';
+import { mount } from 'enzyme';
+import sinon from 'sinon';
+
+import Column from '../../../../../../app/assets/javascripts/components/features/ui/components/column';
+import ColumnHeader from '../../../../../../app/assets/javascripts/components/features/ui/components/column_header';
+
+describe('', () => {
+ describe(' click handler', () => {
+ beforeEach(() => {
+ global.requestAnimationFrame = sinon.spy();
+ });
+
+ it('runs the scroll animation if the column contains scrollable content', () => {
+ const wrapper = mount(
+
+
+
+ );
+ wrapper.find(ColumnHeader).simulate('click');
+ expect(global.requestAnimationFrame.called).to.equal(true);
+ });
+
+ it('does not try to scroll if there is no scrollable content', () => {
+ const wrapper = mount();
+ wrapper.find(ColumnHeader).simulate('click');
+ expect(global.requestAnimationFrame.called).to.equal(false);
+ });
+ });
+});
diff --git a/spec/lib/webfinger_resource_spec.rb b/spec/lib/webfinger_resource_spec.rb
new file mode 100644
index 00000000..dfd23062
--- /dev/null
+++ b/spec/lib/webfinger_resource_spec.rb
@@ -0,0 +1,94 @@
+require 'rails_helper'
+
+describe WebfingerResource do
+ around do |example|
+ before = Rails.configuration.x.local_domain
+ example.run
+ Rails.configuration.x.local_domain = before
+ end
+
+ describe '#username' do
+ describe 'with a URL value' do
+ it 'raises with an unrecognized route' do
+ resource = 'https://example.com/users/alice/other'
+
+ expect {
+ WebfingerResource.new(resource).username
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'raises with a string that doesnt start with URL' do
+ resource = 'website for http://example.com/users/alice/other'
+
+ expect {
+ WebfingerResource.new(resource).username
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'finds the username in a valid https route' do
+ resource = 'https://example.com/users/alice'
+
+ result = WebfingerResource.new(resource).username
+ expect(result).to eq 'alice'
+ end
+
+ it 'finds the username in a mixed case http route' do
+ resource = 'HTTp://exAMPLEe.com/users/alice'
+
+ result = WebfingerResource.new(resource).username
+ expect(result).to eq 'alice'
+ end
+
+ it 'finds the username in a valid http route' do
+ resource = 'http://example.com/users/alice'
+
+ result = WebfingerResource.new(resource).username
+ expect(result).to eq 'alice'
+ end
+ end
+
+ describe 'with a username and hostname value' do
+ it 'raises on a non-local domain' do
+ resource = 'user@remote-host.com'
+
+ expect {
+ WebfingerResource.new(resource).username
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'finds username for a local domain' do
+ Rails.configuration.x.local_domain = 'example.com'
+ resource = 'alice@example.com'
+
+ result = WebfingerResource.new(resource).username
+ expect(result).to eq 'alice'
+ end
+ end
+
+ describe 'with an acct value' do
+ it 'raises on a non-local domain' do
+ resource = 'acct:user@remote-host.com'
+
+ expect {
+ WebfingerResource.new(resource).username
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'raises on a nonsense domain' do
+ resource = 'acct:user@remote-host@remote-hostess.remote.local@remote'
+
+ expect {
+ WebfingerResource.new(resource).username
+ }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it 'finds the username for a local account' do
+ Rails.configuration.x.local_domain = 'example.com'
+ resource = 'acct:alice@example.com'
+
+ result = WebfingerResource.new(resource).username
+ expect(result).to eq 'alice'
+ end
+ end
+ end
+end
diff --git a/spec/models/account_filter_spec.rb b/spec/models/account_filter_spec.rb
new file mode 100644
index 00000000..1599c5ae
--- /dev/null
+++ b/spec/models/account_filter_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+describe AccountFilter do
+ describe 'with empty params' do
+ it 'defaults to alphabetic account list' do
+ filter = AccountFilter.new({})
+
+ expect(filter.results).to eq Account.alphabetic
+ end
+ end
+
+ describe 'with invalid params' do
+ it 'raises with key error' do
+ filter = AccountFilter.new(wrong: true)
+
+ expect { filter.results }.to raise_error(/wrong/)
+ end
+ end
+
+ describe 'with valid params' do
+ it 'combines filters on Account' do
+ filter = AccountFilter.new(by_domain: 'test.com', silenced: true)
+
+ allow(Account).to receive(:where).and_return(Account.none)
+ allow(Account).to receive(:silenced).and_return(Account.none)
+ filter.results
+ expect(Account).to have_received(:where).with(domain: 'test.com')
+ expect(Account).to have_received(:silenced)
+ end
+ end
+end
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index 91c8d75c..46c1ff63 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -54,6 +54,30 @@ RSpec.describe Account, type: :model do
end
end
+ describe 'Local domain user methods' do
+ around do |example|
+ before = Rails.configuration.x.local_domain
+ example.run
+ Rails.configuration.x.local_domain = before
+ end
+
+ describe '#to_webfinger_s' do
+ it 'returns a webfinger string for the account' do
+ Rails.configuration.x.local_domain = 'example.com'
+
+ expect(subject.to_webfinger_s).to eq 'acct:alice@example.com'
+ end
+ end
+
+ describe '#local_username_and_domain' do
+ it 'returns the username and local domain for the account' do
+ Rails.configuration.x.local_domain = 'example.com'
+
+ expect(subject.local_username_and_domain).to eq 'alice@example.com'
+ end
+ end
+ end
+
describe '#acct' do
it 'returns username for local users' do
expect(subject.acct).to eql 'alice'
@@ -94,16 +118,131 @@ RSpec.describe Account, type: :model do
end
end
- describe '#ping!' do
- pending
- end
-
describe '#favourited?' do
- pending
+ let(:original_status) do
+ author = Fabricate(:account, username: 'original')
+ Fabricate(:status, account: author)
+ end
+
+ context 'when the status is a reblog of another status' do
+ let(:original_reblog) do
+ author = Fabricate(:account, username: 'original_reblogger')
+ Fabricate(:status, reblog: original_status, account: author)
+ end
+
+ it 'is is true when this account has favourited it' do
+ Fabricate(:favourite, status: original_reblog, account: subject)
+
+ expect(subject.favourited?(original_status)).to eq true
+ end
+
+ it 'is false when this account has not favourited it' do
+ expect(subject.favourited?(original_status)).to eq false
+ end
+ end
+
+ context 'when the status is an original status' do
+ it 'is is true when this account has favourited it' do
+ Fabricate(:favourite, status: original_status, account: subject)
+
+ expect(subject.favourited?(original_status)).to eq true
+ end
+
+ it 'is false when this account has not favourited it' do
+ expect(subject.favourited?(original_status)).to eq false
+ end
+ end
end
describe '#reblogged?' do
- pending
+ let(:original_status) do
+ author = Fabricate(:account, username: 'original')
+ Fabricate(:status, account: author)
+ end
+
+ context 'when the status is a reblog of another status'do
+ let(:original_reblog) do
+ author = Fabricate(:account, username: 'original_reblogger')
+ Fabricate(:status, reblog: original_status, account: author)
+ end
+
+ it 'is true when this account has reblogged it' do
+ Fabricate(:status, reblog: original_reblog, account: subject)
+
+ expect(subject.reblogged?(original_reblog)).to eq true
+ end
+
+ it 'is false when this account has not reblogged it' do
+ expect(subject.reblogged?(original_reblog)).to eq false
+ end
+ end
+
+ context 'when the status is an original status' do
+ it 'is true when this account has reblogged it' do
+ Fabricate(:status, reblog: original_status, account: subject)
+
+ expect(subject.reblogged?(original_status)).to eq true
+ end
+
+ it 'is false when this account has not reblogged it' do
+ expect(subject.reblogged?(original_status)).to eq false
+ end
+ end
+ end
+
+ describe '.search_for' do
+ before do
+ @match = Fabricate(
+ :account,
+ display_name: "Display Name",
+ username: "username",
+ domain: "example.com"
+ )
+ _missing = Fabricate(
+ :account,
+ display_name: "Missing",
+ username: "missing",
+ domain: "missing.com"
+ )
+ end
+
+ it 'finds accounts with matching display_name' do
+ results = Account.search_for("display")
+ expect(results).to eq [@match]
+ end
+
+ it 'finds accounts with matching username' do
+ results = Account.search_for("username")
+ expect(results).to eq [@match]
+ end
+
+ it 'finds accounts with matching domain' do
+ results = Account.search_for("example")
+ expect(results).to eq [@match]
+ end
+
+ it 'ranks multiple matches higher' do
+ account = Fabricate(
+ :account,
+ username: "username",
+ display_name: "username"
+ )
+ results = Account.search_for("username")
+ expect(results).to eq [account, @match]
+ end
+ end
+
+ describe '.advanced_search_for' do
+ it 'ranks followed accounts higher' do
+ account = Fabricate(:account)
+ match = Fabricate(:account, username: "Matching")
+ followed_match = Fabricate(:account, username: "Matcher")
+ Fabricate(:follow, account: account, target_account: followed_match)
+
+ results = Account.advanced_search_for("match", account)
+ expect(results).to eq [followed_match, match]
+ expect(results.first.rank).to be > results.last.rank
+ end
end
describe '.find_local' do
@@ -209,4 +348,93 @@ RSpec.describe Account, type: :model do
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
end
end
+
+ describe 'validations' do
+ it 'has a valid fabricator' do
+ account = Fabricate.build(:account)
+ account.valid?
+ expect(account).to be_valid
+ end
+
+ it 'is invalid without a username' do
+ account = Fabricate.build(:account, username: nil)
+ account.valid?
+ expect(account).to model_have_error_on_field(:username)
+ end
+
+ it 'is invalid if the username already exists' do
+ account_1 = Fabricate(:account, username: 'the_doctor')
+ account_2 = Fabricate.build(:account, username: 'the_doctor')
+ account_2.valid?
+ expect(account_2).to model_have_error_on_field(:username)
+ end
+
+ context 'when is local' do
+ it 'is invalid if the username doesn\'t only contains letters, numbers and underscores' do
+ account = Fabricate.build(:account, username: 'the-doctor')
+ account.valid?
+ expect(account).to model_have_error_on_field(:username)
+ end
+
+ it 'is invalid if the username is longer then 30 characters' do
+ account = Fabricate.build(:account, username: Faker::Lorem.characters(31))
+ account.valid?
+ expect(account).to model_have_error_on_field(:username)
+ end
+ end
+ end
+
+ describe 'scopes' do
+ describe 'remote' do
+ it 'returns an array of accounts who have a domain' do
+ account_1 = Fabricate(:account, domain: nil)
+ account_2 = Fabricate(:account, domain: 'example.com')
+ expect(Account.remote).to match_array([account_2])
+ end
+ end
+
+ describe 'local' do
+ it 'returns an array of accounts who do not have a domain' do
+ account_1 = Fabricate(:account, domain: nil)
+ account_2 = Fabricate(:account, domain: 'example.com')
+ expect(Account.local).to match_array([account_1])
+ end
+ end
+
+ describe 'silenced' do
+ it 'returns an array of accounts who are silenced' do
+ account_1 = Fabricate(:account, silenced: true)
+ account_2 = Fabricate(:account, silenced: false)
+ expect(Account.silenced).to match_array([account_1])
+ end
+ end
+
+ describe 'suspended' do
+ it 'returns an array of accounts who are suspended' do
+ account_1 = Fabricate(:account, suspended: true)
+ account_2 = Fabricate(:account, suspended: false)
+ expect(Account.suspended).to match_array([account_1])
+ end
+ end
+ end
+
+ describe 'static avatars' do
+ describe 'when GIF' do
+ it 'creates a png static style' do
+ subject.avatar = attachment_fixture('avatar.gif')
+ subject.save
+
+ expect(subject.avatar_static_url).to_not eq subject.avatar_original_url
+ end
+ end
+
+ describe 'when non-GIF' do
+ it 'does not create extra static style' do
+ subject.avatar = attachment_fixture('attachment.jpg')
+ subject.save
+
+ expect(subject.avatar_static_url).to eq subject.avatar_original_url
+ end
+ end
+ end
end
diff --git a/spec/models/block_spec.rb b/spec/models/block_spec.rb
index 6862de6f..cabb41c3 100644
--- a/spec/models/block_spec.rb
+++ b/spec/models/block_spec.rb
@@ -1,5 +1,22 @@
require 'rails_helper'
RSpec.describe Block, type: :model do
+ describe 'validations' do
+ it 'has a valid fabricator' do
+ block = Fabricate.build(:block)
+ expect(block).to be_valid
+ end
+ it 'is invalid without an account' do
+ block = Fabricate.build(:block, account: nil)
+ block.valid?
+ expect(block).to model_have_error_on_field(:account)
+ end
+
+ it 'is invalid without a target_account' do
+ block = Fabricate.build(:block, target_account: nil)
+ block.valid?
+ expect(block).to model_have_error_on_field(:target_account)
+ end
+ end
end
diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb
index ad540311..b19c8083 100644
--- a/spec/models/domain_block_spec.rb
+++ b/spec/models/domain_block_spec.rb
@@ -1,5 +1,23 @@
require 'rails_helper'
RSpec.describe DomainBlock, type: :model do
+ describe 'validations' do
+ it 'has a valid fabricator' do
+ domain_block = Fabricate.build(:domain_block)
+ expect(domain_block).to be_valid
+ end
+ it 'is invalid without a domain' do
+ domain_block = Fabricate.build(:domain_block, domain: nil)
+ domain_block.valid?
+ expect(domain_block).to model_have_error_on_field(:domain)
+ end
+
+ it 'is invalid if the domain already exists' do
+ domain_block_1 = Fabricate(:domain_block, domain: 'dalek.com')
+ domain_block_2 = Fabricate.build(:domain_block, domain: 'dalek.com')
+ domain_block_2.valid?
+ expect(domain_block_2).to model_have_error_on_field(:domain)
+ end
+ end
end
diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb
new file mode 100644
index 00000000..3ee042fb
--- /dev/null
+++ b/spec/models/export_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+describe Export do
+ describe 'to_csv' do
+ before do
+ one = Account.new(username: 'one', domain: 'local.host')
+ two = Account.new(username: 'two', domain: 'local.host')
+ accounts = [one, two]
+
+ @account = double(blocking: accounts, muting: accounts, following: accounts)
+ end
+
+ it 'returns a csv of the blocked accounts' do
+ export = Export.new(@account).to_blocked_accounts_csv
+ results = export.strip.split
+
+ expect(results.size).to eq 2
+ expect(results.first).to eq 'one@local.host'
+ end
+
+ it 'returns a csv of the muted accounts' do
+ export = Export.new(@account).to_muted_accounts_csv
+ results = export.strip.split
+
+ expect(results.size).to eq 2
+ expect(results.first).to eq 'one@local.host'
+ end
+
+ it 'returns a csv of the following accounts' do
+ export = Export.new(@account).to_following_accounts_csv
+ results = export.strip.split
+
+ expect(results.size).to eq 2
+ expect(results.first).to eq 'one@local.host'
+ end
+ end
+end
diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb
index f2ec642d..cc6f8ee6 100644
--- a/spec/models/follow_request_spec.rb
+++ b/spec/models/follow_request_spec.rb
@@ -3,4 +3,23 @@ require 'rails_helper'
RSpec.describe FollowRequest, type: :model do
describe '#authorize!'
describe '#reject!'
+
+ describe 'validations' do
+ it 'has a valid fabricator' do
+ follow_request = Fabricate.build(:follow_request)
+ expect(follow_request).to be_valid
+ end
+
+ it 'is invalid without an account' do
+ follow_request = Fabricate.build(:follow_request, account: nil)
+ follow_request.valid?
+ expect(follow_request).to model_have_error_on_field(:account)
+ end
+
+ it 'is invalid without a target account' do
+ follow_request = Fabricate.build(:follow_request, target_account: nil)
+ follow_request.valid?
+ expect(follow_request).to model_have_error_on_field(:target_account)
+ end
+ end
end
diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb
index eb21f3e1..0fae2535 100644
--- a/spec/models/follow_spec.rb
+++ b/spec/models/follow_spec.rb
@@ -5,4 +5,23 @@ RSpec.describe Follow, type: :model do
let(:bob) { Fabricate(:account, username: 'bob') }
subject { Follow.new(account: alice, target_account: bob) }
+
+ describe 'validations' do
+ it 'has a valid fabricator' do
+ follow = Fabricate.build(:follow)
+ expect(follow).to be_valid
+ end
+
+ it 'is invalid without an account' do
+ follow = Fabricate.build(:follow, account: nil)
+ follow.valid?
+ expect(follow).to model_have_error_on_field(:account)
+ end
+
+ it 'is invalid without a target_account' do
+ follow = Fabricate.build(:follow, target_account: nil)
+ follow.valid?
+ expect(follow).to model_have_error_on_field(:target_account)
+ end
+ end
end
diff --git a/spec/models/mention_spec.rb b/spec/models/mention_spec.rb
index 5c91fda0..dbcf6a32 100644
--- a/spec/models/mention_spec.rb
+++ b/spec/models/mention_spec.rb
@@ -1,5 +1,22 @@
require 'rails_helper'
RSpec.describe Mention, type: :model do
+ describe 'validations' do
+ it 'has a valid fabricator' do
+ mention = Fabricate.build(:mention)
+ expect(mention).to be_valid
+ end
+ it 'is invalid without an account' do
+ mention = Fabricate.build(:mention, account: nil)
+ mention.valid?
+ expect(mention).to model_have_error_on_field(:account)
+ end
+
+ it 'is invalid without a status' do
+ mention = Fabricate.build(:mention, status: nil)
+ mention.valid?
+ expect(mention).to model_have_error_on_field(:status)
+ end
+ end
end
diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb
index b9d07952..000bee0f 100644
--- a/spec/models/status_spec.rb
+++ b/spec/models/status_spec.rb
@@ -91,10 +91,31 @@ RSpec.describe Status, type: :model do
end
describe '#reblogs_count' do
- pending
+ it 'is the number of reblogs' do
+ Fabricate(:status, account: bob, reblog: subject)
+ Fabricate(:status, account: alice, reblog: subject)
+
+ expect(subject.reblogs_count).to eq 2
+ end
end
describe '#favourites_count' do
- pending
+ it 'is the number of favorites' do
+ Fabricate(:favourite, account: bob, status: subject)
+ Fabricate(:favourite, account: alice, status: subject)
+
+ expect(subject.favourites_count).to eq 2
+ end
+ end
+
+ describe '#proper' do
+ it 'is itself for original statuses' do
+ expect(subject.proper).to eq subject
+ end
+
+ it 'is the source status for reblogs' do
+ subject.reblog = other
+ expect(subject.proper).to eq other
+ end
end
end
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index 360bbc16..7a5b8ec8 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -12,4 +12,15 @@ RSpec.describe Tag, type: :model do
expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil
end
end
+
+ describe '.search_for' do
+ it 'finds tag records with matching names' do
+ tag = Fabricate(:tag, name: "match")
+ _miss_tag = Fabricate(:tag, name: "miss")
+
+ results = Tag.search_for("match")
+
+ expect(results).to eq [tag]
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 64de0674..eb2a4aae 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1,5 +1,88 @@
require 'rails_helper'
RSpec.describe User, type: :model do
+ describe 'validations' do
+ it 'is invalid without an account' do
+ user = Fabricate.build(:user, account: nil)
+ user.valid?
+ expect(user).to model_have_error_on_field(:account)
+ end
+ it 'is invalid without a valid locale' do
+ user = Fabricate.build(:user, locale: 'toto')
+ user.valid?
+ expect(user).to model_have_error_on_field(:locale)
+ end
+
+ it 'is invalid without a valid email' do
+ user = Fabricate.build(:user, email: 'john@')
+ user.valid?
+ expect(user).to model_have_error_on_field(:email)
+ end
+ end
+
+ describe 'scopes' do
+ describe 'recent' do
+ it 'returns an array of recent users ordered by id' do
+ user_1 = Fabricate(:user)
+ user_2 = Fabricate(:user)
+ expect(User.recent).to match_array([user_2, user_1])
+ end
+ end
+
+ describe 'admins' do
+ it 'returns an array of users who are admin' do
+ user_1 = Fabricate(:user, admin: false)
+ user_2 = Fabricate(:user, admin: true)
+ expect(User.admins).to match_array([user_2])
+ end
+ end
+
+ describe 'confirmed' do
+ it 'returns an array of users who are confirmed' do
+ user_1 = Fabricate(:user, confirmed_at: nil)
+ user_2 = Fabricate(:user, confirmed_at: Time.now)
+ expect(User.confirmed).to match_array([user_2])
+ end
+ end
+ end
+
+ let(:account) { Fabricate(:account, username: 'alice') }
+ let(:password) { 'abcd1234' }
+
+ describe 'blacklist' do
+ it 'should allow a non-blacklisted user to be created' do
+ user = User.new(email: 'foo@example.com', account: account, password: password)
+
+ expect(user.valid?).to be_truthy
+ end
+
+ it 'should not allow a blacklisted user to be created' do
+ user = User.new(email: 'foo@mvrht.com', account: account, password: password)
+
+ expect(user.valid?).to be_falsey
+ end
+ end
+
+ describe 'whitelist' do
+ around(:each) do |example|
+ old_whitelist = Rails.configuration.x.email_whitelist
+
+ Rails.configuration.x.email_domains_whitelist = 'mastodon.space'
+
+ example.run
+
+ Rails.configuration.x.email_domains_whitelist = old_whitelist
+ end
+
+ it 'should not allow a user to be created unless they are whitelisted' do
+ user = User.new(email: 'foo@example.com', account: account, password: password)
+ expect(user.valid?).to be_falsey
+ end
+
+ it 'should allow a user to be created if they are whitelisted' do
+ user = User.new(email: 'foo@mastodon.space', account: account, password: password)
+ expect(user.valid?).to be_truthy
+ end
+ end
end
diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb
new file mode 100644
index 00000000..0f318d9c
--- /dev/null
+++ b/spec/presenters/instance_presenter_spec.rb
@@ -0,0 +1,74 @@
+require 'rails_helper'
+
+describe InstancePresenter do
+ let(:instance_presenter) { InstancePresenter.new }
+
+ it "delegates site_description to Setting" do
+ Setting.site_description = "Site desc"
+
+ expect(instance_presenter.site_description).to eq "Site desc"
+ end
+
+ it "delegates site_extended_description to Setting" do
+ Setting.site_extended_description = "Extended desc"
+
+ expect(instance_presenter.site_extended_description).to eq "Extended desc"
+ end
+
+ it "delegates open_registrations to Setting" do
+ Setting.open_registrations = false
+
+ expect(instance_presenter.open_registrations).to eq false
+ end
+
+ it "delegates closed_registrations_message to Setting" do
+ Setting.closed_registrations_message = "Closed message"
+
+ expect(instance_presenter.closed_registrations_message).to eq "Closed message"
+ end
+
+ it "delegates contact_email to Setting" do
+ Setting.contact_email = "admin@example.com"
+
+ expect(instance_presenter.contact_email).to eq "admin@example.com"
+ end
+
+ describe "contact_account" do
+ it "returns the account for the site contact username" do
+ Setting.site_contact_username = "aaa"
+ account = Fabricate(:account, username: "aaa")
+
+ expect(instance_presenter.contact_account).to eq(account)
+ end
+ end
+
+ describe "user_count" do
+ it "returns the number of site users" do
+ cache = double
+ allow(Rails).to receive(:cache).and_return(cache)
+ allow(cache).to receive(:fetch).with("user_count").and_return(123)
+
+ expect(instance_presenter.user_count).to eq(123)
+ end
+ end
+
+ describe "status_count" do
+ it "returns the number of local statuses" do
+ cache = double
+ allow(Rails).to receive(:cache).and_return(cache)
+ allow(cache).to receive(:fetch).with("local_status_count").and_return(234)
+
+ expect(instance_presenter.status_count).to eq(234)
+ end
+ end
+
+ describe "domain_count" do
+ it "returns the number of known domains" do
+ cache = double
+ allow(Rails).to receive(:cache).and_return(cache)
+ allow(cache).to receive(:fetch).with("distinct_domain_count").and_return(345)
+
+ expect(instance_presenter.domain_count).to eq(345)
+ end
+ end
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 977c7bdc..faac9698 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -8,6 +8,8 @@ require 'rspec/rails'
require 'webmock/rspec'
require 'paperclip/matchers'
+Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
+
ActiveRecord::Migration.maintain_test_schema!
WebMock.disable_net_connect!(allow: 'localhost:7575')
Sidekiq::Testing.inline!
diff --git a/spec/requests/catch_all_route_request_spec.rb b/spec/requests/catch_all_route_request_spec.rb
new file mode 100644
index 00000000..22ce1cf5
--- /dev/null
+++ b/spec/requests/catch_all_route_request_spec.rb
@@ -0,0 +1,21 @@
+require "rails_helper"
+
+describe "The catch all route" do
+ describe "with a simple value" do
+ it "returns a 404 page as html" do
+ get "/test"
+
+ expect(response.status).to eq 404
+ expect(response.content_type).to eq "text/html"
+ end
+ end
+
+ describe "with an implied format" do
+ it "returns a 404 page as html" do
+ get "/test.test"
+
+ expect(response.status).to eq 404
+ expect(response.content_type).to eq "text/html"
+ end
+ end
+end
diff --git a/spec/requests/host_meta_request_spec.rb b/spec/requests/host_meta_request_spec.rb
new file mode 100644
index 00000000..0c51b5f4
--- /dev/null
+++ b/spec/requests/host_meta_request_spec.rb
@@ -0,0 +1,12 @@
+require "rails_helper"
+
+describe "The host_meta route" do
+ describe "requested without accepts headers" do
+ it "returns an xml response" do
+ get host_meta_url
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq "application/xrd+xml"
+ end
+ end
+end
diff --git a/spec/requests/webfinger_request_spec.rb b/spec/requests/webfinger_request_spec.rb
new file mode 100644
index 00000000..b5690d22
--- /dev/null
+++ b/spec/requests/webfinger_request_spec.rb
@@ -0,0 +1,33 @@
+require "rails_helper"
+
+describe "The webfinger route" do
+ let(:alice) { Fabricate(:account, username: 'alice') }
+
+ describe "requested without accepts headers" do
+ it "returns a json response" do
+ get webfinger_url, params: { resource: alice.to_webfinger_s }
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq "application/jrd+json"
+ end
+ end
+
+ describe "requested with html in accepts headers" do
+ it "returns a json response" do
+ headers = { 'HTTP_ACCEPT' => 'text/html' }
+ get webfinger_url, params: { resource: alice.to_webfinger_s }, headers: headers
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq "application/jrd+json"
+ end
+ end
+
+ describe "requested with xml format" do
+ it "returns an xml response" do
+ get webfinger_url(resource: alice.to_webfinger_s, format: :xml)
+
+ expect(response).to have_http_status(:success)
+ expect(response.content_type).to eq "application/xrd+xml"
+ end
+ end
+end
diff --git a/spec/routing/well_known_routes_spec.rb b/spec/routing/well_known_routes_spec.rb
new file mode 100644
index 00000000..9540c3de
--- /dev/null
+++ b/spec/routing/well_known_routes_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+describe 'the host-meta route' do
+ it 'routes to correct place with xml format' do
+ expect(get('/.well-known/host-meta')).
+ to route_to('well_known/host_meta#show', format: 'xml')
+ end
+end
+
+describe 'the webfinger route' do
+ it 'routes to correct place with json format' do
+ expect(get('/.well-known/webfinger')).
+ to route_to('well_known/webfinger#show', format: 'json')
+ end
+end
diff --git a/spec/services/block_domain_service_spec.rb b/spec/services/block_domain_service_spec.rb
index d88b3b55..8e71d454 100644
--- a/spec/services/block_domain_service_spec.rb
+++ b/spec/services/block_domain_service_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe BlockDomainService do
bad_status2
bad_attachment
- subject.call('evil.org', :suspend)
+ subject.call(DomainBlock.create!(domain: 'evil.org', severity: :suspend))
end
it 'creates a domain block' do
diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb
index 07f8c2dc..6ee225c4 100644
--- a/spec/services/fan_out_on_write_service_spec.rb
+++ b/spec/services/fan_out_on_write_service_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe FanOutOnWriteService do
end
it 'delivers status to local followers' do
+ pending 'some sort of problem in test environment causes this to sometimes fail'
expect(Feed.new(:home, follower).get(10).map(&:id)).to include status.id
end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index 9ee4daf6..0e39cd96 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -3,8 +3,168 @@ require 'rails_helper'
RSpec.describe PostStatusService do
subject { PostStatusService.new }
- it 'creates a new status'
- it 'creates a new response status'
- it 'processes mentions'
- it 'pings PuSH hubs'
+ it 'creates a new status' do
+ account = Fabricate(:account)
+ text = "test status update"
+
+ status = subject.call(account, text)
+
+ expect(status).to be_persisted
+ expect(status.text).to eq text
+ end
+
+ it 'creates a new response status' do
+ in_reply_to_status = Fabricate(:status)
+ account = Fabricate(:account)
+ text = "test status update"
+
+ status = subject.call(account, text, in_reply_to_status)
+
+ expect(status).to be_persisted
+ expect(status.text).to eq text
+ expect(status.thread).to eq in_reply_to_status
+ end
+
+ it 'creates a sensitive status' do
+ status = create_status_with_options(sensitive: true)
+
+ expect(status).to be_persisted
+ expect(status).to be_sensitive
+ end
+
+ it 'creates a status with spoiler text' do
+ spoiler_text = "spoiler text"
+
+ status = create_status_with_options(spoiler_text: spoiler_text)
+
+ expect(status).to be_persisted
+ expect(status.spoiler_text).to eq spoiler_text
+ end
+
+ it 'creates a status with empty default spoiler text' do
+ status = create_status_with_options(spoiler_text: nil)
+
+ expect(status).to be_persisted
+ expect(status.spoiler_text).to eq ''
+ end
+
+ it 'creates a status with the given visibility' do
+ status = create_status_with_options(visibility: :private)
+
+ expect(status).to be_persisted
+ expect(status.visibility).to eq "private"
+ end
+
+ it 'creates a status for the given application' do
+ application = Fabricate(:application)
+
+ status = create_status_with_options(application: application)
+
+ expect(status).to be_persisted
+ expect(status.application).to eq application
+ end
+
+ it 'processes mentions' do
+ mention_service = double(:process_mentions_service)
+ allow(mention_service).to receive(:call)
+ allow(ProcessMentionsService).to receive(:new).and_return(mention_service)
+ account = Fabricate(:account)
+
+ status = subject.call(account, "test status update")
+
+ expect(ProcessMentionsService).to have_received(:new)
+ expect(mention_service).to have_received(:call).with(status)
+ end
+
+ it 'processes hashtags' do
+ hashtags_service = double(:process_hashtags_service)
+ allow(hashtags_service).to receive(:call)
+ allow(ProcessHashtagsService).to receive(:new).and_return(hashtags_service)
+ account = Fabricate(:account)
+
+ status = subject.call(account, "test status update")
+
+ expect(ProcessHashtagsService).to have_received(:new)
+ expect(hashtags_service).to have_received(:call).with(status)
+ end
+
+ it 'pings PuSH hubs' do
+ allow(DistributionWorker).to receive(:perform_async)
+ allow(Pubsubhubbub::DistributionWorker).to receive(:perform_async)
+ account = Fabricate(:account)
+
+ status = subject.call(account, "test status update")
+
+ expect(DistributionWorker).to have_received(:perform_async).with(status.id)
+ expect(Pubsubhubbub::DistributionWorker).
+ to have_received(:perform_async).with(status.stream_entry.id)
+ end
+
+ it 'crawls links' do
+ allow(LinkCrawlWorker).to receive(:perform_async)
+ account = Fabricate(:account)
+
+ status = subject.call(account, "test status update")
+
+ expect(LinkCrawlWorker).to have_received(:perform_async).with(status.id)
+ end
+
+ it 'attaches the given media to the created status' do
+ account = Fabricate(:account)
+ media = Fabricate(:media_attachment)
+
+ status = subject.call(
+ account,
+ "test status update",
+ nil,
+ media_ids: [media.id],
+ )
+
+ expect(media.reload.status).to eq status
+ end
+
+ it 'does not allow attaching more than 4 files' do
+ account = Fabricate(:account)
+
+ expect do
+ subject.call(
+ account,
+ "test status update",
+ nil,
+ media_ids: [
+ Fabricate(:media_attachment, account: account),
+ Fabricate(:media_attachment, account: account),
+ Fabricate(:media_attachment, account: account),
+ Fabricate(:media_attachment, account: account),
+ Fabricate(:media_attachment, account: account),
+ ].map(&:id),
+ )
+ end.to raise_error(
+ Mastodon::ValidationError,
+ I18n.t('media_attachments.validations.too_many'),
+ )
+ end
+
+ it 'does not allow attaching both videos and images' do
+ account = Fabricate(:account)
+
+ expect do
+ subject.call(
+ account,
+ "test status update",
+ nil,
+ media_ids: [
+ Fabricate(:media_attachment, type: :video, account: account),
+ Fabricate(:media_attachment, type: :image, account: account),
+ ].map(&:id),
+ )
+ end.to raise_error(
+ Mastodon::ValidationError,
+ I18n.t('media_attachments.validations.images_and_video'),
+ )
+ end
+
+ def create_status_with_options(options = {})
+ subject.call(Fabricate(:account), "test", nil, options)
+ end
end
diff --git a/spec/services/process_feed_service_spec.rb b/spec/services/process_feed_service_spec.rb
index 5e57d823..b15284fe 100644
--- a/spec/services/process_feed_service_spec.rb
+++ b/spec/services/process_feed_service_spec.rb
@@ -16,6 +16,7 @@ RSpec.describe ProcessFeedService do
end
it 'updates remote user\'s account information' do
+ account.reload
expect(account.display_name).to eq '::1'
expect(account).to have_attached_file(:avatar)
end
diff --git a/spec/services/update_remote_profile_service_spec.rb b/spec/services/update_remote_profile_service_spec.rb
index c3d76c65..f96f2f6b 100644
--- a/spec/services/update_remote_profile_service_spec.rb
+++ b/spec/services/update_remote_profile_service_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
RSpec.describe UpdateRemoteProfileService do
- let(:xml) { Nokogiri::XML(File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom'))).at_xpath('//xmlns:feed') }
+ let(:xml) { File.read(File.join(Rails.root, 'spec', 'fixtures', 'push', 'feed.atom')) }
subject { UpdateRemoteProfileService.new }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index a5dce977..7038efce 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,7 +1,9 @@
require 'simplecov'
SimpleCov.start 'rails' do
- add_group "Services", "app/services"
+ add_group 'Services', 'app/services'
+ add_group 'Presenters', 'app/presenters'
+ add_group 'Validators', 'app/validators'
end
RSpec.configure do |config|
diff --git a/spec/support/matchers/model/model_have_error_on_field.rb b/spec/support/matchers/model/model_have_error_on_field.rb
new file mode 100644
index 00000000..5d5fe1c7
--- /dev/null
+++ b/spec/support/matchers/model/model_have_error_on_field.rb
@@ -0,0 +1,15 @@
+RSpec::Matchers.define :model_have_error_on_field do |expected|
+ match do |record|
+ if record.errors.empty?
+ record.valid?
+ end
+
+ record.errors.has_key?(expected)
+ end
+
+ failure_message do |record|
+ keys = record.errors.keys
+
+ "expect record.errors(#{keys}) to include #{expected}"
+ end
+end
diff --git a/streaming/index.js b/streaming/index.js
index 7edf6203..a1e7eaca 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -87,21 +87,24 @@ const setRequestId = (req, res, next) => {
const accountFromToken = (token, req, next) => {
pgPool.connect((err, client, done) => {
if (err) {
- return next(err)
+ next(err)
+ return
}
client.query('SELECT oauth_access_tokens.resource_owner_id, users.account_id FROM oauth_access_tokens INNER JOIN users ON oauth_access_tokens.resource_owner_id = users.id WHERE oauth_access_tokens.token = $1 LIMIT 1', [token], (err, result) => {
done()
if (err) {
- return next(err)
+ next(err)
+ return
}
if (result.rows.length === 0) {
err = new Error('Invalid access token')
err.statusCode = 401
- return next(err)
+ next(err)
+ return
}
req.accountId = result.rows[0].account_id
@@ -113,7 +116,8 @@ const accountFromToken = (token, req, next) => {
const authenticationMiddleware = (req, res, next) => {
if (req.method === 'OPTIONS') {
- return next()
+ next()
+ return
}
const authorization = req.get('Authorization')
@@ -122,7 +126,8 @@ const authenticationMiddleware = (req, res, next) => {
const err = new Error('Missing access token')
err.statusCode = 401
- return next(err)
+ next(err)
+ return
}
const token = authorization.replace(/^Bearer /, '')
diff --git a/yarn.lock b/yarn.lock
index 6a3a3627..7f1c48db 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -140,6 +140,12 @@ acorn-globals@^3.1.0:
dependencies:
acorn "^4.0.4"
+acorn-jsx@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b"
+ dependencies:
+ acorn "^3.0.4"
+
acorn@^1.0.3:
version "1.2.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014"
@@ -148,7 +154,7 @@ acorn@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-2.7.0.tgz#ab6e7d9d886aaca8b085bc3312b79a198433f0e7"
-acorn@^3.0.0:
+acorn@^3.0.0, acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
@@ -156,6 +162,10 @@ acorn@^4.0.3, acorn@^4.0.4:
version "4.0.11"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0"
+acorn@^5.0.1:
+ version "5.0.3"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d"
+
airbnb-js-shims@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-1.0.1.tgz#7d5a7d772c8c6fdeb624ea3cef62506091b180b5"
@@ -169,7 +179,7 @@ airbnb-js-shims@^1.0.1:
string.prototype.padend "^3.0.0"
string.prototype.padstart "^3.0.0"
-ajv-keywords@^1.1.1:
+ajv-keywords@^1.0.0, ajv-keywords@^1.1.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
@@ -196,6 +206,10 @@ amdefine@>=0.0.4:
version "1.0.0"
resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.0.tgz#fd17474700cb5cc9c2b709f0be9d23ce3c198c33"
+ansi-escapes@^1.1.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e"
+
ansi-html@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
@@ -284,10 +298,27 @@ array-reduce@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b"
+array-union@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
+ dependencies:
+ array-uniq "^1.0.1"
+
+array-uniq@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
+
array-unique@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
+array.prototype.find@^2.0.1:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90"
+ dependencies:
+ define-properties "^1.1.2"
+ es-abstract "^1.7.0"
+
arrify@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
@@ -424,7 +455,7 @@ babel-code-frame@^6.11.0:
esutils "^2.0.2"
js-tokens "^2.0.0"
-babel-code-frame@^6.22.0:
+babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
dependencies:
@@ -480,6 +511,15 @@ 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"
+ dependencies:
+ babel-code-frame "^6.22.0"
+ babel-traverse "^6.23.1"
+ babel-types "^6.23.0"
+ babylon "^6.16.1"
+
babel-generator@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.22.0.tgz#d642bf4961911a8adc7c692b0c9297f325cda805"
@@ -1241,7 +1281,7 @@ babel-template@^6.3.0:
babylon "^6.11.0"
lodash "^4.2.0"
-babel-traverse@^6.16.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-traverse@^6.23.1:
+babel-traverse@^6.16.0, babel-traverse@^6.22.0, babel-traverse@^6.22.1, babel-traverse@^6.23.0, babel-traverse@^6.23.1:
version "6.23.1"
resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48"
dependencies:
@@ -1255,30 +1295,7 @@ babel-traverse@^6.16.0, babel-traverse@^6.22.0, babel-traverse@^6.23.0, babel-tr
invariant "^2.2.0"
lodash "^4.2.0"
-babel-traverse@^6.22.1:
- version "6.22.1"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.22.1.tgz#3b95cd6b7427d6f1f757704908f2fc9748a5f59f"
- dependencies:
- babel-code-frame "^6.22.0"
- babel-messages "^6.22.0"
- babel-runtime "^6.22.0"
- babel-types "^6.22.0"
- babylon "^6.15.0"
- debug "^2.2.0"
- globals "^9.0.0"
- invariant "^2.2.0"
- lodash "^4.2.0"
-
-babel-types@^6.16.0, babel-types@^6.22.0:
- version "6.22.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.22.0.tgz#2a447e8d0ea25d2512409e4175479fd78cc8b1db"
- dependencies:
- babel-runtime "^6.22.0"
- esutils "^2.0.2"
- lodash "^4.2.0"
- to-fast-properties "^1.0.1"
-
-babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.9.0:
+babel-types@^6.16.0, babel-types@^6.19.0, babel-types@^6.22.0, babel-types@^6.23.0, babel-types@^6.9.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf"
dependencies:
@@ -1294,13 +1311,9 @@ babelify@^7.3.0:
babel-core "^6.0.14"
object-assign "^4.0.0"
-babylon@^6.11.0:
- version "6.11.4"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.11.4.tgz#75e1f52187efa0cde5a541a7f7fdda38f6eb5bd2"
-
-babylon@^6.15.0:
- version "6.15.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.15.0.tgz#ba65cfa1a80e1759b0e89fb562e27dccae70348e"
+babylon@^6.11.0, babylon@^6.15.0, babylon@^6.16.1:
+ version "6.16.1"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3"
babylon@~5.8.3:
version "5.8.38"
@@ -1586,6 +1599,16 @@ cached-path-relative@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7"
+caller-path@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
+ dependencies:
+ callsites "^0.2.0"
+
+callsites@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
+
camelcase-keys@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
@@ -1639,7 +1662,7 @@ chai@^3.5.0:
deep-eql "^0.1.3"
type-detect "^1.0.0"
-chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -1695,6 +1718,10 @@ cipher-base@^1.0.0, cipher-base@^1.0.1:
dependencies:
inherits "^2.0.1"
+circular-json@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d"
+
clap@^1.0.9:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.1.tgz#a8a93e0bfb7581ac199c4f001a5525a724ce696d"
@@ -1705,6 +1732,16 @@ classnames@^2.1.2, classnames@^2.2.3, classnames@~2.2:
version "2.2.5"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.5.tgz#fb3801d453467649ef3603c7d61a02bd129bde6d"
+cli-cursor@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987"
+ dependencies:
+ restore-cursor "^1.0.1"
+
+cli-width@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
+
cliui@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
@@ -1824,7 +1861,7 @@ concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-concat-stream@^1.4.7, concat-stream@~1.5.0, concat-stream@~1.5.1:
+concat-stream@^1.4.7, concat-stream@^1.5.2, concat-stream@~1.5.0, concat-stream@~1.5.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
dependencies:
@@ -2085,11 +2122,11 @@ currently-unhandled@^0.4.1:
dependencies:
array-find-index "^1.0.1"
-d@^0.1.1, d@~0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309"
+d@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
dependencies:
- es5-ext "~0.10.2"
+ es5-ext "^0.10.9"
dashdash@^1.12.0:
version "1.14.0"
@@ -2140,6 +2177,18 @@ defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+del@^2.0.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
+ dependencies:
+ globby "^5.0.0"
+ is-path-cwd "^1.0.0"
+ is-path-in-cwd "^1.0.0"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+ rimraf "^2.2.8"
+
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@@ -2197,6 +2246,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
+doctrine@^1.2.2:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
+ dependencies:
+ esutils "^2.0.2"
+ isarray "^1.0.0"
+
doctrine@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
@@ -2368,7 +2424,7 @@ error-ex@^1.2.0:
dependencies:
is-arrayish "^0.2.1"
-es-abstract@^1.3.2, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1:
+es-abstract@^1.3.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.6.1.tgz#bb8a2064120abcf928a086ea3d9043114285ec99"
dependencies:
@@ -2377,6 +2433,15 @@ es-abstract@^1.3.2, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1:
is-callable "^1.1.3"
is-regex "^1.0.3"
+es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c"
+ dependencies:
+ es-to-primitive "^1.1.1"
+ function-bind "^1.1.0"
+ is-callable "^1.1.3"
+ is-regex "^1.0.3"
+
es-to-primitive@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
@@ -2385,9 +2450,9 @@ es-to-primitive@^1.1.1:
is-date-object "^1.0.1"
is-symbol "^1.0.1"
-es5-ext@^0.10.7, es5-ext@~0.10.11, es5-ext@~0.10.2:
- version "0.10.12"
- resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047"
+es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
+ version "0.10.15"
+ resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.15.tgz#c330a5934c1ee21284a7c081a86e5fd937c91ea6"
dependencies:
es6-iterator "2"
es6-symbol "~3.1"
@@ -2396,28 +2461,58 @@ es5-shim@^4.5.9:
version "4.5.9"
resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.9.tgz#2a1e2b9e583ff5fed0c20a3ee2cbf3f75230a5c0"
-es6-iterator@2:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac"
+es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
dependencies:
- d "^0.1.1"
- es5-ext "^0.10.7"
- es6-symbol "3"
+ d "1"
+ es5-ext "^0.10.14"
+ es6-symbol "^3.1"
+
+es6-map@^0.1.3:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-set "~0.1.5"
+ es6-symbol "~3.1.1"
+ event-emitter "~0.3.5"
es6-promise@^3.2.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
+es6-set@~0.1.5:
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+ es6-iterator "~2.0.1"
+ es6-symbol "3.1.1"
+ event-emitter "~0.3.5"
+
es6-shim@^0.35.1:
version "0.35.1"
resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.1.tgz#a23524009005b031ab4a352ac196dfdfd1144ab7"
-es6-symbol@3, es6-symbol@^3.0.2, es6-symbol@~3.1:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa"
+es6-symbol@3.1.1, es6-symbol@^3.0.2, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
dependencies:
- d "~0.1.1"
- es5-ext "~0.10.11"
+ d "1"
+ es5-ext "~0.10.14"
+
+es6-weak-map@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
+ dependencies:
+ d "1"
+ es5-ext "^0.10.14"
+ es6-iterator "^2.0.1"
+ es6-symbol "^3.1.1"
escape-html@^1.0.3, escape-html@~1.0.3:
version "1.0.3"
@@ -2438,6 +2533,72 @@ escodegen@^1.6.1:
optionalDependencies:
source-map "~0.2.0"
+escope@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
+ dependencies:
+ es6-map "^0.1.3"
+ es6-weak-map "^2.0.1"
+ esrecurse "^4.1.0"
+ estraverse "^4.1.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"
+ dependencies:
+ array.prototype.find "^2.0.1"
+ doctrine "^1.2.2"
+ has "^1.0.1"
+ jsx-ast-utils "^1.3.4"
+ object.assign "^4.0.4"
+
+eslint@^3.19.0:
+ version "3.19.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc"
+ dependencies:
+ babel-code-frame "^6.16.0"
+ chalk "^1.1.3"
+ concat-stream "^1.5.2"
+ debug "^2.1.1"
+ doctrine "^2.0.0"
+ escope "^3.6.0"
+ espree "^3.4.0"
+ esquery "^1.0.0"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ file-entry-cache "^2.0.0"
+ glob "^7.0.3"
+ globals "^9.14.0"
+ ignore "^3.2.0"
+ imurmurhash "^0.1.4"
+ inquirer "^0.12.0"
+ is-my-json-valid "^2.10.0"
+ is-resolvable "^1.0.0"
+ js-yaml "^3.5.1"
+ json-stable-stringify "^1.0.0"
+ levn "^0.3.0"
+ lodash "^4.0.0"
+ mkdirp "^0.5.0"
+ natural-compare "^1.4.0"
+ optionator "^0.8.2"
+ path-is-inside "^1.0.1"
+ pluralize "^1.2.1"
+ progress "^1.1.8"
+ require-uncached "^1.0.2"
+ shelljs "^0.7.5"
+ strip-bom "^3.0.0"
+ strip-json-comments "~2.0.1"
+ table "^3.7.8"
+ text-table "~0.2.0"
+ user-home "^2.0.0"
+
+espree@^3.4.0:
+ version "3.4.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.1.tgz#28a83ab4aaed71ed8fe0f5efe61b76a05c13c4d2"
+ dependencies:
+ acorn "^5.0.1"
+ acorn-jsx "^3.0.0"
+
esprima@^2.6.0, esprima@^2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
@@ -2446,10 +2607,31 @@ esprima@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
+esquery@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa"
+ dependencies:
+ estraverse "^4.0.0"
+
+esrecurse@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220"
+ dependencies:
+ estraverse "~4.1.0"
+ object-assign "^4.0.1"
+
estraverse@^1.9.1:
version "1.9.3"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
+estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
+
+estraverse@~4.1.0:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2"
+
esutils@^2.0.0, esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@@ -2458,6 +2640,13 @@ etag@~1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8"
+event-emitter@~0.3.5:
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
+ dependencies:
+ d "1"
+ es5-ext "~0.10.14"
+
events@^1.0.0, events@^1.1.1, events@~1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
@@ -2478,6 +2667,10 @@ exenv@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.0.tgz#3835f127abf075bfe082d0aed4484057c78e3c89"
+exit-hook@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8"
+
expand-brackets@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
@@ -2559,6 +2752,20 @@ fbjs@^0.8.1, fbjs@^0.8.4:
promise "^7.1.1"
ua-parser-js "^0.7.9"
+figures@^1.3.5:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
+ dependencies:
+ escape-string-regexp "^1.0.5"
+ object-assign "^4.1.0"
+
+file-entry-cache@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361"
+ dependencies:
+ flat-cache "^1.2.1"
+ object-assign "^4.0.1"
+
file-loader@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-0.9.0.tgz#1d2daddd424ce6d1b07cfe3f79731bed3617ab42"
@@ -2604,6 +2811,15 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
+flat-cache@^1.2.1:
+ version "1.2.2"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96"
+ dependencies:
+ circular-json "^0.3.1"
+ del "^2.0.2"
+ graceful-fs "^4.1.2"
+ write "^0.2.1"
+
flatten@1.0.2, flatten@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782"
@@ -2815,10 +3031,21 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@~7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
-globals@^9.0.0:
+globals@^9.0.0, globals@^9.14.0:
version "9.14.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034"
+globby@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d"
+ dependencies:
+ array-union "^1.0.1"
+ arrify "^1.0.0"
+ glob "^7.0.3"
+ object-assign "^4.0.1"
+ pify "^2.0.0"
+ pinkie-promise "^2.0.0"
+
globule@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/globule/-/globule-1.1.0.tgz#c49352e4dc183d85893ee825385eb994bb6df45f"
@@ -2958,9 +3185,9 @@ http-errors@~1.5.1:
setprototypeof "1.0.2"
statuses ">= 1.3.1 < 2"
-http-link-header@^0.5.0:
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-0.5.0.tgz#68598d92c55d3dac7d3e6ae405142fecf7bd3303"
+http-link-header@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/http-link-header/-/http-link-header-0.8.0.tgz#a22b41a0c9b1e2d8fac1bf1b697c6bd532d5f5e4"
http-signature@~1.1.0:
version "1.1.1"
@@ -2986,6 +3213,10 @@ ieee754@^1.1.4:
version "1.1.8"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
+ignore@^3.2.0:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd"
+
immutable@^3.7.6, immutable@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"
@@ -3037,6 +3268,24 @@ inline-source-map@~0.6.0:
dependencies:
source-map "~0.5.3"
+inquirer@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e"
+ dependencies:
+ ansi-escapes "^1.1.0"
+ ansi-regex "^2.0.0"
+ chalk "^1.0.0"
+ cli-cursor "^1.0.1"
+ cli-width "^2.0.0"
+ figures "^1.3.5"
+ lodash "^4.3.0"
+ readline2 "^1.0.1"
+ run-async "^0.1.0"
+ rx-lite "^3.1.2"
+ string-width "^1.0.1"
+ strip-ansi "^3.0.0"
+ through "^2.3.6"
+
insert-module-globals@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3"
@@ -3162,13 +3411,17 @@ is-fullwidth-code-point@^1.0.0:
dependencies:
number-is-nan "^1.0.0"
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
dependencies:
is-extglob "^1.0.0"
-is-my-json-valid@^2.12.4:
+is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4:
version "2.15.0"
resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b"
dependencies:
@@ -3187,6 +3440,22 @@ is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
+is-path-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
+
+is-path-in-cwd@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc"
+ dependencies:
+ is-path-inside "^1.0.0"
+
+is-path-inside@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f"
+ dependencies:
+ path-is-inside "^1.0.1"
+
is-plain-obj@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
@@ -3217,6 +3486,12 @@ is-regexp@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
+is-resolvable@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62"
+ dependencies:
+ tryit "^1.0.1"
+
is-stream@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
@@ -3298,7 +3573,7 @@ js-tokens@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7"
-js-yaml@^3.4.3, js-yaml@~3.6.1:
+js-yaml@^3.4.3, js-yaml@^3.5.1, js-yaml@~3.6.1:
version "3.6.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30"
dependencies:
@@ -3349,7 +3624,7 @@ json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-json-stable-stringify@^1.0.1:
+json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
dependencies:
@@ -3397,6 +3672,12 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.3.6"
+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:
+ object-assign "^4.1.0"
+
keycode@^2.1.1:
version "2.1.7"
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.1.7.tgz#7b9255919f6cff562b09a064d222dca70b020f5c"
@@ -3435,7 +3716,7 @@ lcid@^1.0.0:
dependencies:
invert-kv "^1.0.0"
-levn@~0.3.0:
+levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
dependencies:
@@ -3634,7 +3915,7 @@ lodash.tail@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
-lodash@4.x.x, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.1:
+lodash@4.x.x, lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.1, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
@@ -3865,10 +4146,18 @@ ms@0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+mute-stream@0.0.5:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0"
+
nan@^2.3.0, nan@^2.3.2, nan@~2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2"
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+
negotiator@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
@@ -4156,6 +4445,10 @@ once@~1.3.0, once@~1.3.3:
dependencies:
wrappy "1"
+onetime@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789"
+
optimist@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
@@ -4163,7 +4456,7 @@ optimist@~0.6.0:
minimist "~0.0.1"
wordwrap "~0.0.2"
-optionator@^0.8.1:
+optionator@^0.8.1, optionator@^0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
dependencies:
@@ -4284,6 +4577,10 @@ path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+path-is-inside@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+
path-platform@~0.11.15:
version "0.11.15"
resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2"
@@ -4373,6 +4670,10 @@ pkg-dir@^1.0.0:
dependencies:
find-up "^1.0.0"
+pluralize@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45"
+
podda@^1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/podda/-/podda-1.2.2.tgz#15b0edbd334ade145813343f5ecf9c10a71cf500"
@@ -4718,6 +5019,10 @@ process@^0.11.0, process@~0.11.0:
version "0.11.9"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
+progress@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+
promise@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf"
@@ -5140,6 +5445,14 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
+readline2@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35"
+ dependencies:
+ code-point-at "^1.0.0"
+ is-fullwidth-code-point "^1.0.0"
+ mute-stream "0.0.5"
+
recast@^0.11.5:
version "0.11.22"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.22.tgz#dedeb18fb001a2bbc6ac34475fda53dfe3d47dfa"
@@ -5341,6 +5654,13 @@ require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
+require-uncached@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
+ dependencies:
+ caller-path "^0.1.0"
+ resolve-from "^1.0.0"
+
requires-port@1.0.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
@@ -5349,17 +5669,28 @@ reselect@^2.5.4:
version "2.5.4"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-2.5.4.tgz#b7d23fdf00b83fa7ad0279546f8dbbbd765c7047"
+resolve-from@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+
resolve@1.1.7, resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+restore-cursor@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
+ dependencies:
+ exit-hook "^1.0.0"
+ onetime "^1.0.0"
+
right-align@^0.1.1:
version "0.1.3"
resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
dependencies:
align-text "^0.1.1"
-rimraf@2, rimraf@~2.5.0, rimraf@~2.5.1:
+rimraf@2, rimraf@^2.2.8, rimraf@~2.5.0, rimraf@~2.5.1:
version "2.5.4"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04"
dependencies:
@@ -5373,6 +5704,16 @@ ripemd160@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-1.0.1.tgz#93a4bbd4942bc574b69a8fa57c71de10ecca7d6e"
+run-async@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389"
+ dependencies:
+ once "^1.3.0"
+
+rx-lite@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
+
samsam@1.1.2, samsam@~1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.1.2.tgz#bec11fdc83a9fda063401210e40176c3024d1567"
@@ -5523,6 +5864,14 @@ shelljs@^0.7.4:
interpret "^1.0.0"
rechoir "^0.6.2"
+shelljs@^0.7.5:
+ version "0.7.7"
+ resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.7.tgz#b2f5c77ef97148f4b4f6e22682e10bba8667cff1"
+ dependencies:
+ glob "^7.0.0"
+ interpret "^1.0.0"
+ rechoir "^0.6.2"
+
signal-exit@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.1.tgz#5a4c884992b63a7acd9badb7894c3ee9cfccad81"
@@ -5548,6 +5897,10 @@ slash@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+slice-ansi@0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
+
slide@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707"
@@ -5692,6 +6045,13 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
+string-width@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^3.0.0"
+
string.prototype.padend@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
@@ -5735,6 +6095,10 @@ strip-bom@^2.0.0:
dependencies:
is-utf8 "^0.2.0"
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+
strip-indent@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
@@ -5745,6 +6109,10 @@ strip-json-comments@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91"
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+
style-loader@0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.13.1.tgz#468280efbc0473023cd3a6cd56e33b5a1d7fc3a9"
@@ -5809,6 +6177,17 @@ syntax-error@^1.1.1:
dependencies:
acorn "^2.7.0"
+table@^3.7.8:
+ version "3.8.3"
+ resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f"
+ dependencies:
+ ajv "^4.7.0"
+ ajv-keywords "^1.0.0"
+ chalk "^1.1.1"
+ lodash "^4.0.0"
+ slice-ansi "0.0.4"
+ string-width "^2.0.0"
+
tapable@^0.1.8, tapable@~0.1.8:
version "0.1.10"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
@@ -5856,6 +6235,10 @@ tar@^2.0.0, tar@~2.2.0, tar@~2.2.1:
fstream "^1.0.2"
inherits "2"
+text-table@~0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+
through2@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9"
@@ -5863,7 +6246,7 @@ through2@^2.0.0:
readable-stream "~2.0.0"
xtend "~4.0.0"
-through@2, "through@>=2.2.7 <3":
+through@2, "through@>=2.2.7 <3", through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
@@ -5909,6 +6292,10 @@ trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
+tryit@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
+
tty-browserify@0.0.0, tty-browserify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -6022,6 +6409,12 @@ user-home@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
+user-home@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
+ dependencies:
+ os-homedir "^1.0.0"
+
utf-8-validate@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-3.0.1.tgz#5d2b8656b4ddcfded47217b647a98941b63cf213"
@@ -6280,6 +6673,12 @@ write-file-atomic@^1.1.2:
imurmurhash "^0.1.4"
slide "^1.1.5"
+write@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757"
+ dependencies:
+ mkdirp "^0.5.1"
+
ws@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-2.1.0.tgz#b24eaed9609f8632dd51e3f7698619a90fddcc92"