about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/components/components/status_action_bar.jsx20
-rw-r--r--app/assets/javascripts/components/components/video_player.jsx8
-rw-r--r--app/assets/javascripts/components/containers/mastodon.jsx6
-rw-r--r--app/assets/javascripts/components/features/account/components/action_bar.jsx26
-rw-r--r--app/assets/javascripts/components/features/account/components/header.jsx3
-rw-r--r--app/assets/javascripts/components/features/compose/components/compose_form.jsx13
-rw-r--r--app/assets/javascripts/components/features/compose/components/reply_indicator.jsx8
-rw-r--r--app/assets/javascripts/components/features/compose/components/search.jsx10
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_button.jsx8
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_form.jsx8
-rw-r--r--app/assets/javascripts/components/features/followers/components/account.jsx8
-rw-r--r--app/assets/javascripts/components/features/home_timeline/index.jsx8
-rw-r--r--app/assets/javascripts/components/features/mentions_timeline/index.jsx8
-rw-r--r--app/assets/javascripts/components/features/public_timeline/index.jsx8
-rw-r--r--app/assets/javascripts/components/features/status/components/action_bar.jsx20
-rw-r--r--app/assets/javascripts/components/locales/de.jsx48
-rw-r--r--app/assets/javascripts/components/locales/en.jsx49
-rw-r--r--app/assets/javascripts/components/locales/index.jsx11
-rw-r--r--app/assets/javascripts/components/reducers/search.jsx4
-rw-r--r--config/application.rb2
-rw-r--r--config/locales/de.yml59
-rw-r--r--config/locales/devise.de.yml61
-rw-r--r--config/locales/doorkeeper.de.yml112
-rw-r--r--config/locales/en.yml2
-rw-r--r--config/locales/simple_form.de.yml27
-rw-r--r--config/locales/simple_form.en.yml2
26 files changed, 488 insertions, 51 deletions
diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx
index 8883d0806..051b898bd 100644
--- a/app/assets/javascripts/components/components/status_action_bar.jsx
+++ b/app/assets/javascripts/components/components/status_action_bar.jsx
@@ -2,7 +2,15 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import IconButton from './icon_button';
 import DropdownMenu from './dropdown_menu';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  delete: { id: 'status.delete', defaultMessage: 'Delete' },
+  mention: { id: 'status.mention', defaultMessage: 'Mention' },
+  reply: { id: 'status.reply', defaultMessage: 'Reply' },
+  reblog: { id: 'status.reblog', defaultMessage: 'Reblog' },
+  favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }
+});
 
 const StatusActionBar = React.createClass({
   propTypes: {
@@ -41,16 +49,16 @@ const StatusActionBar = React.createClass({
     let menu = [];
 
     if (status.getIn(['account', 'id']) === me) {
-      menu.push({ text: intl.formatMessage({ id: 'status.delete', defaultMessage: 'Delete' }), action: this.handleDeleteClick });
+      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
     } else {
-      menu.push({ text: intl.formatMessage({ id: 'status.mention', defaultMessage: 'Mention' }), action: this.handleMentionClick });
+      menu.push({ text: intl.formatMessage(messages.mention), action: this.handleMentionClick });
     }
 
     return (
       <div style={{ marginTop: '10px', overflow: 'hidden' }}>
-        <div style={{ float: 'left', marginRight: '18px'}}><IconButton title={intl.formatMessage({ id: 'status.reply', defaultMessage: 'Reply' })} icon='reply' onClick={this.handleReplyClick} /></div>
-        <div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('reblogged')} title={intl.formatMessage({ id: 'status.reblog', defaultMessage: 'Reblog' })} icon='retweet' onClick={this.handleReblogClick} /></div>
-        <div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('favourited')} title={intl.formatMessage({ id: 'status.favourite', defaultMessage: 'Favourite' })} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
+        <div style={{ float: 'left', marginRight: '18px'}}><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
+        <div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon='retweet' onClick={this.handleReblogClick} /></div>
+        <div style={{ float: 'left', marginRight: '18px'}}><IconButton active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
 
         <div style={{ width: '18px', height: '18px', float: 'left' }}>
           <DropdownMenu items={menu} icon='ellipsis-h' size={18} />
diff --git a/app/assets/javascripts/components/components/video_player.jsx b/app/assets/javascripts/components/components/video_player.jsx
index 2c236b996..9b9b0a2e4 100644
--- a/app/assets/javascripts/components/components/video_player.jsx
+++ b/app/assets/javascripts/components/components/video_player.jsx
@@ -1,7 +1,11 @@
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import IconButton from './icon_button';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' }
+});
 
 const videoStyle = {
   position: 'relative',
@@ -64,7 +68,7 @@ const VideoPlayer = React.createClass({
 
     return (
       <div style={{ cursor: 'default', marginTop: '8px', overflow: 'hidden', width: `${width}px`, height: `${height}px`, boxSizing: 'border-box', background: '#000', position: 'relative' }}>
-        <div style={muteStyle}><IconButton title={intl.formatMessage({ id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' })} icon={this.state.muted ? 'volume-up' : 'volume-off'} onClick={this.handleClick} /></div>
+        <div style={muteStyle}><IconButton title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-up' : 'volume-off'} onClick={this.handleClick} /></div>
         <video src={media.get('url')} autoPlay='true' loop={true} muted={this.state.muted} style={videoStyle} onClick={this.handleVideoClick} />
       </div>
     );
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
index a12b19746..e61107cd1 100644
--- a/app/assets/javascripts/components/containers/mastodon.jsx
+++ b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -34,6 +34,8 @@ import Favourites from '../features/favourites';
 import HashtagTimeline from '../features/hashtag_timeline';
 import { IntlProvider, addLocaleData } from 'react-intl';
 import en from 'react-intl/locale-data/en';
+import de from 'react-intl/locale-data/de';
+import getMessagesForLocale from '../locales';
 
 const store = configureStore();
 
@@ -41,7 +43,7 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
   basename: '/web'
 });
 
-addLocaleData([...en]);
+addLocaleData([...en, ...de]);
 
 const Mastodon = React.createClass({
 
@@ -89,7 +91,7 @@ const Mastodon = React.createClass({
     const { locale } = this.props;
 
     return (
-      <IntlProvider locale={locale}>
+      <IntlProvider locale={locale} messages={getMessagesForLocale(locale)}>
         <Provider store={store}>
           <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
             <Route path='/' component={UI}>
diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx
index f8d051439..cd01de2e2 100644
--- a/app/assets/javascripts/components/features/account/components/action_bar.jsx
+++ b/app/assets/javascripts/components/features/account/components/action_bar.jsx
@@ -2,7 +2,17 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import DropdownMenu from '../../../components/dropdown_menu';
 import { Link } from 'react-router';
-import { injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
+
+const messages = defineMessages({
+  mention: { id: 'account.mention', defaultMessage: 'Mention' },
+  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+  unblock: { id: 'account.unblock', defaultMessage: 'Unblock' },
+  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
+  block: { id: 'account.block', defaultMessage: 'Block' },
+  follow: { id: 'account.follow', defaultMessage: 'Follow' },
+  block: { id: 'account.block', defaultMessage: 'Block' }
+});
 
 const outerStyle = {
   borderTop: '1px solid #363c4b',
@@ -41,18 +51,18 @@ const ActionBar = React.createClass({
 
     let menu = [];
 
-    menu.push({ text: intl.formatMessage({ id: 'account.mention', defaultMessage: 'Mention' }), action: this.props.onMention });
+    menu.push({ text: intl.formatMessage(messages.mention), action: this.props.onMention });
 
     if (account.get('id') === me) {
-      menu.push({ text: intl.formatMessage({ id: 'account.edit_profile', defaultMessage: 'Edit profile' }), href: '/settings/profile' });
+      menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
     } else if (account.getIn(['relationship', 'blocking'])) {
-      menu.push({ text: intl.formatMessage({ id: 'account.unblock', defaultMessage: 'Unblock' }), action: this.props.onBlock });
+      menu.push({ text: intl.formatMessage(messages.unblock), action: this.props.onBlock });
     } else if (account.getIn(['relationship', 'following'])) {
-      menu.push({ text: intl.formatMessage({ id: 'account.unfollow', defaultMessage: 'Unfollow' }), action: this.props.onFollow });
-      menu.push({ text: intl.formatMessage({ id: 'account.block', defaultMessage: 'Block' }), action: this.props.onBlock });
+      menu.push({ text: intl.formatMessage(messages.unfollow), action: this.props.onFollow });
+      menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
     } else {
-      menu.push({ text: intl.formatMessage({ id: 'account.follow', defaultMessage: 'Follow' }), action: this.props.onFollow });
-      menu.push({ text: intl.formatMessage({ id: 'account.block', defaultMessage: 'Block' }), action: this.props.onBlock });
+      menu.push({ text: intl.formatMessage(messages.follow), action: this.props.onFollow });
+      menu.push({ text: intl.formatMessage(messages.block), action: this.props.onBlock });
     }
 
     return (
diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx
index 7a086c99a..b3e9e2a9f 100644
--- a/app/assets/javascripts/components/features/account/components/header.jsx
+++ b/app/assets/javascripts/components/features/account/components/header.jsx
@@ -2,6 +2,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import emojify from '../../../emoji';
 import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser';
+import { FormattedMessage } from 'react-intl';
 
 const Header = React.createClass({
 
@@ -23,7 +24,7 @@ const Header = React.createClass({
     }
 
     if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
-      info = <span style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', color: '#fff', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}>Follows you</span>
+      info = <span style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', color: '#fff', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>
     }
 
     const content         = { __html: emojify(account.get('note')) };
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
index 5aa041f09..32bdeaeca 100644
--- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx
+++ b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
@@ -8,7 +8,12 @@ import Autosuggest from 'react-autosuggest';
 import AutosuggestAccountContainer from '../../compose/containers/autosuggest_account_container';
 import { debounce } from 'react-decoration';
 import UploadButtonContainer from '../containers/upload_button_container';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
+  publish: { id: 'compose_form.publish', defaultMessage: 'Publish' }
+});
 
 const getTokenForSuggestions = (str, caretPosition) => {
   let word;
@@ -53,7 +58,7 @@ const textareaStyle = {
 };
 
 const renderInputComponent = inputProps => (
-  <textarea {...inputProps} placeholder='What is on your mind?'  className='compose-form__textarea' style={textareaStyle} />
+  <textarea {...inputProps} className='compose-form__textarea' style={textareaStyle} />
 );
 
 const ComposeForm = React.createClass({
@@ -144,7 +149,7 @@ const ComposeForm = React.createClass({
     }
 
     const inputProps = {
-      placeholder: intl.formatMessage({ id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }),
+      placeholder: intl.formatMessage(messages.placeholder),
       value: this.props.text,
       onKeyUp: this.handleKeyUp,
       onChange: this.handleChange,
@@ -169,7 +174,7 @@ const ComposeForm = React.createClass({
         />
 
         <div style={{ marginTop: '10px', overflow: 'hidden' }}>
-          <div style={{ float: 'right' }}><Button text={intl.formatMessage({ id: 'compose_form.publish', defaultMessage: 'Publish' })} onClick={this.handleSubmit} disabled={disabled} /></div>
+          <div style={{ float: 'right' }}><Button text={intl.formatMessage(messages.publish)} onClick={this.handleSubmit} disabled={disabled} /></div>
           <div style={{ float: 'right', marginRight: '16px', lineHeight: '36px' }}><CharacterCounter max={500} text={this.props.text} /></div>
           <UploadButtonContainer style={{ paddingTop: '4px' }} />
         </div>
diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
index 4b34f09bf..e913ddfa9 100644
--- a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
+++ b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
@@ -4,7 +4,11 @@ import Avatar from '../../../components/avatar';
 import IconButton from '../../../components/icon_button';
 import DisplayName from '../../../components/display_name';
 import emojify from '../../../emoji';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }
+});
 
 const ReplyIndicator = React.createClass({
 
@@ -37,7 +41,7 @@ const ReplyIndicator = React.createClass({
     return (
       <div style={{ background: '#9baec8', padding: '10px' }}>
         <div style={{ overflow: 'hidden', marginBottom: '5px' }}>
-          <div style={{ float: 'right', lineHeight: '24px' }}><IconButton title={intl.formatMessage({ id: 'reply_indicator.cancel', defaultMessage: 'Cancel' })} icon='times' onClick={this.handleClick} /></div>
+          <div style={{ float: 'right', lineHeight: '24px' }}><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
 
           <a href={this.props.status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name' style={{ display: 'block', maxWidth: '100%', paddingRight: '25px', color: '#282c37', textDecoration: 'none', overflow: 'hidden', lineHeight: '24px' }}>
             <div style={{ float: 'left', marginRight: '5px' }}><Avatar size={24} src={this.props.status.getIn(['account', 'avatar'])} /></div>
diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx
index 65df336cc..b4e618820 100644
--- a/app/assets/javascripts/components/features/compose/components/search.jsx
+++ b/app/assets/javascripts/components/features/compose/components/search.jsx
@@ -3,7 +3,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 import Autosuggest from 'react-autosuggest';
 import AutosuggestAccountContainer from '../containers/autosuggest_account_container';
 import { debounce } from 'react-decoration';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+const messages = defineMessages({
+  placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }
+});
 
 const getSuggestionValue = suggestion => suggestion.value;
 
@@ -16,7 +20,7 @@ const renderSuggestion = suggestion => {
 };
 
 const renderSectionTitle = section => (
-  <strong>{section.title}</strong>
+  <strong><FormattedMessage id={`search.${section.title}`} defaultMessage={section.title} /></strong>
 );
 
 const getSectionSuggestions = section => section.items;
@@ -95,7 +99,7 @@ const Search = React.createClass({
 
   render () {
     const inputProps = {
-      placeholder: this.props.intl.formatMessage({ id: 'search.placeholder', defaultMessage: 'Search' }),
+      placeholder: this.props.intl.formatMessage(messages.placeholder),
       value: this.props.value,
       onChange: this.onChange,
       style: inputStyle
diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx
index cc251835f..5250ff748 100644
--- a/app/assets/javascripts/components/features/compose/components/upload_button.jsx
+++ b/app/assets/javascripts/components/features/compose/components/upload_button.jsx
@@ -1,6 +1,10 @@
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import IconButton from '../../../components/icon_button';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  upload: { id: 'upload_button.label', defaultMessage: 'Add media' }
+});
 
 const UploadButton = React.createClass({
 
@@ -31,7 +35,7 @@ const UploadButton = React.createClass({
 
     return (
       <div style={this.props.style}>
-        <IconButton icon='photo' title={intl.formatMessage({ id: 'upload_button.label', defaultMessage: 'Add media' })} disabled={this.props.disabled} onClick={this.handleClick} size={24} />
+        <IconButton icon='photo' title={intl.formatMessage(messages.upload)} disabled={this.props.disabled} onClick={this.handleClick} size={24} />
         <input ref={this.setRef} type='file' multiple={false} onChange={this.handleChange} disabled={this.props.disabled} style={{ display: 'none' }} />
       </div>
     );
diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx
index 72c2b9535..ac548033c 100644
--- a/app/assets/javascripts/components/features/compose/components/upload_form.jsx
+++ b/app/assets/javascripts/components/features/compose/components/upload_form.jsx
@@ -1,7 +1,11 @@
 import PureRenderMixin from 'react-addons-pure-render-mixin';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import IconButton from '../../../components/icon_button';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
+});
 
 const UploadForm = React.createClass({
 
@@ -19,7 +23,7 @@ const UploadForm = React.createClass({
     const uploads = this.props.media.map(attachment => (
       <div key={attachment.get('id')} style={{ borderRadius: '4px', marginBottom: '10px' }} className='transparent-background'>
         <div style={{ width: '100%', height: '100px', borderRadius: '4px', background: `url(${attachment.get('preview_url')}) no-repeat center`, backgroundSize: 'cover' }}>
-          <IconButton icon='times' title={intl.formatMessage({ id: 'upload_form.undo', defaultMessage: 'Undo' })} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
+          <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
         </div>
       </div>
     ));
diff --git a/app/assets/javascripts/components/features/followers/components/account.jsx b/app/assets/javascripts/components/features/followers/components/account.jsx
index 123a40cab..4a1fca6da 100644
--- a/app/assets/javascripts/components/features/followers/components/account.jsx
+++ b/app/assets/javascripts/components/features/followers/components/account.jsx
@@ -4,7 +4,11 @@ import Avatar from '../../../components/avatar';
 import DisplayName from '../../../components/display_name';
 import { Link } from 'react-router';
 import IconButton from '../../../components/icon_button';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  follow: { id: 'account.follow', defaultMessage: 'Follow' }
+});
 
 const outerStyle = {
   padding: '10px',
@@ -69,7 +73,7 @@ const Account = React.createClass({
 
       buttons = (
         <div style={buttonsStyle}>
-          <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage({ id: 'account.follow', defaultMessage: 'Follow' })} onClick={this.handleFollow} active={following} />
+          <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(messages.follow)} onClick={this.handleFollow} active={following} />
         </div>
       );
     }
diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx
index 117a4a72d..e4f4fa7c7 100644
--- a/app/assets/javascripts/components/features/home_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/home_timeline/index.jsx
@@ -3,7 +3,11 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 import StatusListContainer from '../ui/containers/status_list_container';
 import Column from '../ui/components/column';
 import { refreshTimeline } from '../../actions/timelines';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  title: { id: 'column.home', defaultMessage: 'Home' }
+});
 
 const HomeTimeline = React.createClass({
 
@@ -21,7 +25,7 @@ const HomeTimeline = React.createClass({
     const { intl } = this.props;
 
     return (
-      <Column icon='home' heading={intl.formatMessage({ id: 'column.home', defaultMessage: 'Home' })}>
+      <Column icon='home' heading={intl.formatMessage(messages.title)}>
         <StatusListContainer {...this.props} type='home' />
       </Column>
     );
diff --git a/app/assets/javascripts/components/features/mentions_timeline/index.jsx b/app/assets/javascripts/components/features/mentions_timeline/index.jsx
index 9f1caa235..8583f59a6 100644
--- a/app/assets/javascripts/components/features/mentions_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/mentions_timeline/index.jsx
@@ -3,7 +3,11 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 import StatusListContainer from '../ui/containers/status_list_container';
 import Column from '../ui/components/column';
 import { refreshTimeline } from '../../actions/timelines';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  title: { id: 'column.mentions', defaultMessage: 'Mentions' }
+});
 
 const MentionsTimeline = React.createClass({
 
@@ -21,7 +25,7 @@ const MentionsTimeline = React.createClass({
     const { intl } = this.props;
 
     return (
-      <Column icon='at' heading={intl.formatMessage({ id: 'column.mentions', defaultMessage: 'Mentions' })}>
+      <Column icon='at' heading={intl.formatMessage(messages.title)}>
         <StatusListContainer {...this.props} type='mentions' />
       </Column>
     );
diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx
index 445a4fc63..c3da09a09 100644
--- a/app/assets/javascripts/components/features/public_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/public_timeline/index.jsx
@@ -7,7 +7,11 @@ import {
   updateTimeline,
   deleteFromTimelines
 } from '../../actions/timelines';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  title: { id: 'column.public', defaultMessage: 'Public' }
+});
 
 const PublicTimeline = React.createClass({
 
@@ -48,7 +52,7 @@ const PublicTimeline = React.createClass({
     const { intl } = this.props;
 
     return (
-      <Column icon='globe' heading={intl.formatMessage({ id: 'column.public', defaultMessage: 'Public' })}>
+      <Column icon='globe' heading={intl.formatMessage(messages.title)}>
         <StatusListContainer type='public' />
       </Column>
     );
diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx
index d855176f2..1f46b956e 100644
--- a/app/assets/javascripts/components/features/status/components/action_bar.jsx
+++ b/app/assets/javascripts/components/features/status/components/action_bar.jsx
@@ -2,7 +2,15 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
 import IconButton from '../../../components/icon_button';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import DropdownMenu from '../../../components/dropdown_menu';
-import { injectIntl } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+
+const messages = defineMessages({
+  delete: { id: 'status.delete', defaultMessage: 'Delete' },
+  mention: { id: 'status.mention', defaultMessage: 'Mention' },
+  reply: { id: 'status.reply', defaultMessage: 'Reply' },
+  reblog: { id: 'status.reblog', defaultMessage: 'Reblog' },
+  favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }
+});
 
 const ActionBar = React.createClass({
 
@@ -44,16 +52,16 @@ const ActionBar = React.createClass({
     let menu = [];
 
     if (me === status.getIn(['account', 'id'])) {
-      menu.push({ text: intl.formatMessage({ id: 'status.delete', defaultMessage: 'Delete' }), action: this.handleDeleteClick });
+      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
     } else {
-      menu.push({ text: intl.formatMessage({ id: 'status.mention', defaultMessage: 'Mention' }), action: this.handleMentionClick });
+      menu.push({ text: intl.formatMessage(messages.mention), action: this.handleMentionClick });
     }
 
     return (
       <div style={{ background: '#2f3441', display: 'flex', flexDirection: 'row', borderTop: '1px solid #363c4b', borderBottom: '1px solid #363c4b', padding: '10px 0' }}>
-        <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title={intl.formatMessage({ id: 'status.reply', defaultMessage: 'Reply' })} icon='reply' onClick={this.handleReplyClick} /></div>
-        <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('reblogged')} title={intl.formatMessage({ id: 'status.reblog', defaultMessage: 'Reblog' })} icon='retweet' onClick={this.handleReblogClick} /></div>
-        <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('favourited')} title={intl.formatMessage({ id: 'status.favourite', defaultMessage: 'Favourite' })} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
+        <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton title={intl.formatMessage(messages.reply)} icon='reply' onClick={this.handleReplyClick} /></div>
+        <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('reblogged')} title={intl.formatMessage(messages.reblog)} icon='retweet' onClick={this.handleReblogClick} /></div>
+        <div style={{ flex: '1 1 auto', textAlign: 'center' }}><IconButton active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
         <div style={{ flex: '1 1 auto', textAlign: 'center' }}><DropdownMenu size={18} icon='ellipsis-h' items={menu} /></div>
       </div>
     );
diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx
new file mode 100644
index 000000000..e6f4a2491
--- /dev/null
+++ b/app/assets/javascripts/components/locales/de.jsx
@@ -0,0 +1,48 @@
+const en = {
+  "column_back_button.label": "Zurück",
+  "lightbox.close": "Schließen",
+  "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",
+  "video_player.toggle_sound": "Ton umschalten",
+  "account.mention": "Erwähnen",
+  "account.edit_profile": "Profil bearbeiten",
+  "account.unblock": "Entblocken",
+  "account.unfollow": "Entfolgen",
+  "account.block": "Blocken",
+  "account.follow": "Folgen",
+  "account.posts": "Beiträge",
+  "account.follows": "Folgt",
+  "account.followers": "Folger",
+  "account.follows_you": "Folgt dir",
+  "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_developer": "Der Entwickler des Projekts kann unter Gargron@mastodon.social gefunden werden",
+  "column.home": "Home",
+  "column.mentions": "Erwähnungen",
+  "column.public": "Gesamtes Bekanntes Netz",
+  "tabs_bar.compose": "Schreiben",
+  "tabs_bar.home": "Home",
+  "tabs_bar.mentions": "Erwähnungen",
+  "tabs_bar.public": "Gesamtes Netz",
+  "compose_form.placeholder": "Worüber möchstest du schreiben?",
+  "compose_form.publish": "Veröffentlichen",
+  "navigation_bar.settings": "Einstellungen",
+  "navigation_bar.public_timeline": "Öffentlich",
+  "navigation_bar.logout": "Abmelden",
+  "reply_indicator.cancel": "Abbrechen",
+  "search.placeholder": "Suche",
+  "search.account": "Konto",
+  "search.hashtag": "Hashtag",
+  "suggestions_box.who_to_follow": "Wem folgen",
+  "suggestions_box.refresh": "Aktualisieren",
+  "upload_button.label": "Media-Datei anfügen",
+  "upload_form.undo": "Entfernen"
+};
+
+export default en;
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
new file mode 100644
index 000000000..a28c84b03
--- /dev/null
+++ b/app/assets/javascripts/components/locales/en.jsx
@@ -0,0 +1,49 @@
+const en = {
+  "column_back_button.label": "Back",
+  "lightbox.close": "Close",
+  "loading_indicator.label": "Loading...",
+  "status.mention": "Mention",
+  "status.delete": "Delete",
+  "status.reply": "Reply",
+  "status.reblog": "Reblog",
+  "status.favourite": "Favourite",
+  "status.reblogged_by": "{name} reblogged",
+  "video_player.toggle_sound": "Toggle sound",
+  "account.mention": "Mention",
+  "account.edit_profile": "Edit profile",
+  "account.unblock": "Unblock",
+  "account.unfollow": "Unfollow",
+  "account.block": "Block",
+  "account.follow": "Follow",
+  "account.block": "Block",
+  "account.posts": "Posts",
+  "account.follows": "Follows",
+  "account.followers": "Followers",
+  "account.follows_you": "Follows you",
+  "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 form at the top of the sidebar.",
+  "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.about_developer": "The developer of this project can be followed as Gargron@mastodon.social",
+  "column.home": "Home",
+  "column.mentions": "Mentions",
+  "column.public": "Public",
+  "tabs_bar.compose": "Compose",
+  "tabs_bar.home": "Home",
+  "tabs_bar.mentions": "Mentions",
+  "tabs_bar.public": "Public",
+  "compose_form.placeholder": "What is on your mind?",
+  "compose_form.publish": "Publish",
+  "navigation_bar.settings": "Settings",
+  "navigation_bar.public_timeline": "Public timeline",
+  "navigation_bar.logout": "Logout",
+  "reply_indicator.cancel": "Cancel",
+  "search.placeholder": "Search",
+  "search.account": "Account",
+  "search.hashtag": "Hashtag",
+  "suggestions_box.who_to_follow": "Who to follow",
+  "suggestions_box.refresh": "Refresh",
+  "upload_button.label": "Add media",
+  "upload_form.undo": "Undo"
+};
+
+export default en;
diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx
new file mode 100644
index 000000000..212cbcee5
--- /dev/null
+++ b/app/assets/javascripts/components/locales/index.jsx
@@ -0,0 +1,11 @@
+import en from './en';
+import de from './de';
+
+const locales = {
+  en,
+  de
+};
+
+export default function getMessagesForLocale (locale) {
+  return locales[locale];
+};
diff --git a/app/assets/javascripts/components/reducers/search.jsx b/app/assets/javascripts/components/reducers/search.jsx
index f3ee17f60..9c2041863 100644
--- a/app/assets/javascripts/components/reducers/search.jsx
+++ b/app/assets/javascripts/components/reducers/search.jsx
@@ -14,7 +14,7 @@ const initialState = Immutable.Map({
 const normalizeSuggestions = (state, value, accounts) => {
   let newSuggestions = [
     {
-      title: 'Account',
+      title: 'account',
       items: accounts.map(item => ({
         type: 'account',
         id: item.id,
@@ -25,7 +25,7 @@ const normalizeSuggestions = (state, value, accounts) => {
 
   if (value.indexOf('@') === -1) {
     newSuggestions.push({
-      title: 'Hashtag',
+      title: 'hashtag',
       items: [
         {
           type: 'hashtag',
diff --git a/config/application.rb b/config/application.rb
index e992c2481..114de57fb 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -20,7 +20,7 @@ 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]
+    config.i18n.available_locales = [:en, :de]
     config.i18n.default_locale    = :en
 
     # config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
diff --git a/config/locales/de.yml b/config/locales/de.yml
new file mode 100644
index 000000000..648be5db2
--- /dev/null
+++ b/config/locales/de.yml
@@ -0,0 +1,59 @@
+---
+de:
+  about:
+    about_instance: "<em>%{instance}</em> ist eine Instanz von Mastodon."
+    about_mastodon: Mastodon ist ein <em>freier, quelloffener</em> soziales Netzwerkserver. Eine <em>dezentralisierte</em> 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 <em>sozialen Netzwerk</em> teilnehmen.
+    get_started: Erste Schritte
+    source_code: Quellcode
+    terms: AGB
+  accounts:
+    follow: Folgen
+    followers: Folger
+    following: Folgt
+    nothing_here: Hier gibt es nichts!
+    people_followed_by: Nutzer, denen %{name} folgt
+    people_who_follow: Nutzer, die %{name} folgen
+    posts: Beiträge
+    unfollow: Entfolgen
+  application_mailer:
+    signature: Mastodon-Benachrichtigungen von %{instance}
+  auth:
+    change_password: Passwort ändern
+    didnt_get_confirmation: Keine Bestätigung bekommen?
+    forgot_password: Passwort vergessen?
+    login: Anmelden
+    register: Registrieren
+    resend_confirmation: Bestätigung nochmal versenden
+    reset_password: Passwort zurücksetzen
+    set_new_password: Neues Passwort setzen
+  generic:
+    changes_saved_msg: Änderungen gespeichert!
+    powered_by: angetrieben von %{link}
+    save_changes: Änderungen speichern
+    validation_errors:
+      one: Etwas ist noch nicht ganz richtig! Bitte korrigiere den Fehler
+      other: Etwas ist noch nicht ganz richtig! Bitte korrigiere %{count} Fehler
+  notification_mailer:
+    favourite:
+      body: "Dein Beitrag wurde von %{name} favorisiert:"
+      subject: "%{name} hat deinen Beitrag favorisiert"
+    follow:
+      body: "%{name} folgt dir jetzt!"
+      subject: "%{name} folgt dir nun"
+    mention:
+      body: "%{name} hat dich erwähnt:"
+      subject: "%{name} hat dich erwähnt"
+    reblog:
+      body: "Dein Beitrag wurde von %{name} geteilt:"
+      subject: "%{name} teilte deinen Beitrag"
+  pagination:
+    next: Vorwärts
+    prev: Zurück
+  settings:
+    edit_profile: Profil bearbeiten
+    preferences: Einstellungen
+  stream_entries:
+    favourited: favorisierte einen Beitrag von
+    is_now_following: folgt nun
+  will_paginate:
+    page_gap: "&hellip;"
diff --git a/config/locales/devise.de.yml b/config/locales/devise.de.yml
new file mode 100644
index 000000000..181502f9c
--- /dev/null
+++ b/config/locales/devise.de.yml
@@ -0,0 +1,61 @@
+---
+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."
+    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"
+      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."
+    mailer:
+      confirmation_instructions:
+        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"
+      unlock_instructions:
+        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."
+    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."
+      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."
+      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."
+      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"
+      not_locked: "ist nicht gesperrt"
+      not_saved:
+        one: "Konnte %{resource} nicht speichern: ein Fehler."
+        other: "Konnte %{resource} nicht speichern: %{count} Fehler."
diff --git a/config/locales/doorkeeper.de.yml b/config/locales/doorkeeper.de.yml
new file mode 100644
index 000000000..0c606f6a2
--- /dev/null
+++ b/config/locales/doorkeeper.de.yml
@@ -0,0 +1,112 @@
+---
+de:
+  activerecord:
+    attributes:
+      doorkeeper/application:
+        name: Name
+        redirect_uri: Redirect URI
+    errors:
+      models:
+        doorkeeper/application:
+          attributes:
+            redirect_uri:
+              fragment_present: darf kein Fragment enthalten.
+              invalid_uri: muss ein valider URI (Identifier) sein.
+              relative_uri: muss ein absoluter URI (Identifier) sein.
+              secured_uri: muss ein HTTPS/SSL URI (Identifier) sein.
+  doorkeeper:
+    applications:
+      buttons:
+        authorize: Autorisieren
+        cancel: Abbrechen
+        destroy: Löschen
+        edit: Bearbeiten
+        submit: Übertragen
+      confirmations:
+        destroy: Bist du sicher?
+      edit:
+        title: Applikation bearbeiten
+      form:
+        error: Whoops! Bitte überprüfe das Formular auf Fehler!
+      help:
+        native_redirect_uri: "%{native_redirect_uri} für lokale Tests benutzen"
+        redirect_uri: Bitte benutze eine Zeile pro URI
+        scopes: Bitte die "Scopes" mit Leerzeichen trennen. Bitte frei lassen für die Verwendung der Default-Werte.
+      index:
+        callback_url: Callback URL
+        name: Name
+        new: Neue Applikation
+        title: Deine Applikationen
+      new:
+        title: Neue Applikation
+      show:
+        actions: Aktionen
+        application_id: Applikations-ID
+        callback_urls: Callback URLs
+        scopes: Scopes
+        secret: Secret
+        title: 'Applikation: %{name}'
+    authorizations:
+      buttons:
+        authorize: Autorisieren
+        deny: Verweigern
+      error:
+        title: Ein Fehler ist aufgetreten
+      new:
+        able_to: 'Diese Anwendung wird folgende Rechte haben:'
+        prompt: Soll %{client_name} für die Benutzung dieses Accounts autorisiert werden?
+        title: Autorisierung erforderlich
+      show:
+        title: Autorisierungscode
+    authorized_applications:
+      buttons:
+        revoke: Ungültig machen
+      confirmations:
+        revoke: Bist du sicher?
+      index:
+        application: Applikation
+        created_at: erstellt am
+        date_format: "%Y-%m-%d %H:%M:%S"
+        title: Deine autorisierten Applikationen
+    errors:
+      messages:
+        access_denied: Der Resource Owner oder der Autorisierungs-Server hat die Anfrage verweigert.
+        credential_flow_not_configured: 'Die Prozedur "Resource Owner Password Credentials" ist fehlgeschlagen: Doorkeeper.configure.resource_owner_from_credentials ist nicht konfiguriert.'
+        invalid_client: 'Client-Autorisierung MKIM ist fehlgeschlagen: Unbekannter Client, keine Autorisierung mitgeliefert oder Autorisierungsmethode nicht unterstützt.'
+        invalid_grant: Die bereitgestellte Autorisierung ist inkorrekt, abgelaufen, widerrufen, ist mit einem anderen Client verknüpft oder der Redirection URI stimmt nicht mit der Autorisierungs-Anfrage überein.
+        invalid_redirect_uri: Der Redirect-URI in der Anfrage ist ungültig.
+        invalid_request: Die Anfrage enthält einen nicht-unterstützten Parameter, ein Parameter fehlt oder sie ist anderweitig fehlerhaft.
+        invalid_resource_owner: Die angegebenen Zugangsdaten für den "Resource Owner" sind inkorrekt oder dieser Benutzer existiert nicht.
+        invalid_scope: Der angeforderte Scope ist inkorrekt, unbekannt oder fehlerhaft.
+        invalid_token:
+          expired: Der Access Token ist abgelaufen
+          revoked: Der Access Token wurde annuliert
+          unknown: Der Access Token ist nicht gültig/korrekt
+        resource_owner_authenticator_not_configured: 'Die Prozedur "Resource Owner find" ist fehlgeschlagen: Doorkeeper.configure.resource_owner_authenticator ist nicht konfiguriert.'
+        server_error: Der Autorisierungs-Server hat ein unerwartetes Problem festgestellt und konnte die Anfrage nicht beenden.
+        temporarily_unavailable: Der Autorisierungs-Server ist derzeit auf Grund von temporärer Überlastung oder Wartungsarbeiten am Server nicht in der Lage, die Anfrage zu bearbeiten .
+        unauthorized_client: Der Client ist nicht autorisiert, diese Anfrage mit dieser Methode auszuführen.
+        unsupported_grant_type: Der Autorisierungs-Typ wird nicht vom Autorisierungs-Server unterstützt.
+        unsupported_response_type: Der Autorisierungs-Server unterstützt diesen Antwort-Typ nicht.
+    flash:
+      applications:
+        create:
+          notice: Applikation erstellt.
+        destroy:
+          notice: Applikation gelöscht.
+        update:
+          notice: Applikation geupdated.
+      authorized_applications:
+        destroy:
+          notice: Applikation widerrufen.
+    layouts:
+      admin:
+        nav:
+          applications: Applikationen
+          oauth2_provider: OAuth2 Provider
+      application:
+        title: OAuth Autorisierung nötig
+    scopes:
+      follow: Nutzer folgen, blocken, entblocken und entfolgen
+      read: deine Daten lesen
+      write: Beiträge von deinem Konto aus veröffentlichen
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ab16ed082..426f3928a 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -55,3 +55,5 @@ en:
   stream_entries:
     favourited: favourited a post by
     is_now_following: is now following
+  will_paginate:
+    page_gap: "&hellip;"
diff --git a/config/locales/simple_form.de.yml b/config/locales/simple_form.de.yml
new file mode 100644
index 000000000..6e6758df2
--- /dev/null
+++ b/config/locales/simple_form.de.yml
@@ -0,0 +1,27 @@
+---
+de:
+  simple_form:
+    labels:
+      defaults:
+        avatar: Avatar
+        confirm_new_password: Neues Passwort bestätigen
+        confirm_password: Passwort bestätigen
+        current_password: Derzeitiges Passwort
+        display_name: Anzeigename
+        email: E-mail-Addresse
+        header: Kopfbild
+        locale: Sprache
+        new_password: Neues Passwort
+        note: Über mich
+        password: Passwort
+        username: Nutzername
+      notification_emails:
+        favourite: E-mail senden, wenn jemand meinen Beitrag favorisiert
+        follow: E-mail senden, wenn mir jemand folgt
+        mention: E-mail senden, wenn mich jemand erwähnt
+        reblog: E-mail senden, wenn jemand meinen Beitrag teilt
+    'no': 'Nein'
+    required:
+      mark: "*"
+      text: Pflichtfeld
+    'yes': 'Ja'
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index bd22a1f3d..b8a69a075 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -1,8 +1,6 @@
 ---
 en:
   simple_form:
-    error_notification:
-      default_message: 'Please review the problems below:'
     labels:
       defaults:
         avatar: Avatar