diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index ce93436f..aab74f2a 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -275,6 +275,7 @@ const getNodeInfo = async ({ store }) => {
       store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
       store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
       store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
+      store.dispatch('setInstanceOption', { name: 'translationEnabled', value: features.includes('akkoma:machine_translation') })
 
       const uploadLimits = metadata.uploadLimits
       store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index edc57d89..6d9713ff 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -8,7 +8,6 @@
       <button
         class="emoji-reaction btn button-default"
         :class="{ 'picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
-        :disabled="!isLocalReaction(reaction.url)"
         @click="emojiOnClick(reaction.name, $event)"
         @mouseenter="fetchEmojiReactionsByIfMissing()"
       >
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index f7045110..97fb7d6e 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -55,6 +55,12 @@ const ExtraButtons = {
     hideDeleteStatusConfirmDialog () {
       this.showingDeleteDialog = false
     },
+
+    translateStatus () {
+      this.$store.dispatch('translateStatus', { id: this.status.id, language: this.$store.state.instance.interfaceLanguage })
+        .then(() => this.$emit('onSuccess'))
+        .catch(err => this.$emit('onError', err.error.error))
+    },
     pinStatus () {
       this.$store.dispatch('pinStatus', this.status.id)
         .then(() => this.$emit('onSuccess'))
@@ -110,6 +116,9 @@ const ExtraButtons = {
     canMute () {
       return !!this.currentUser
     },
+    canTranslate () {
+      return this.$store.state.instance.translationEnabled === true
+    },
     statusLink () {
       return `${this.$store.state.instance.server}${this.$router.resolve({ name: 'conversation', params: { id: this.status.id } }).href}`
     },
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index 2b574bbd..bbb140e4 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -116,6 +116,17 @@
             :icon="['far', 'flag']"
           /><span>{{ $t("user_card.report") }}</span>
         </button>
+        <button
+          v-if="canTranslate"
+          class="button-default dropdown-item dropdown-item-icon"
+          @click.prevent="translateStatus"
+          @click="close"
+        >
+          <FAIcon
+            fixed-width
+            icon="globe"
+          /><span>{{ $t("status.translate") }}</span>
+        </button>
       </div>
     </template>
     <template v-slot:trigger>
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
index 0f946f79..230a27ac 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -4,6 +4,12 @@
   display: flex;
   flex-direction: column;
 
+  .translation {
+    border: 1px solid var(--accent, $fallback--link);
+    border-radius: var(--panelRadius, $fallback--panelRadius);
+    margin-top: 1em;
+    padding: 0.5em;
+  }
   .emoji {
     --_still_image-label-scale: 0.5;
     --emoji-size: 38px;
diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue
index 321f3c4b..c1a3d9e7 100644
--- a/src/components/status_body/status_body.vue
+++ b/src/components/status_body/status_body.vue
@@ -56,6 +56,23 @@
             :attentions="status.attentions"
             @parseReady="onParseReady"
           />
+          <div
+            v-if="status.translation"
+            class="translation"
+          >
+            <h4>{{ $t('status.translated_from', { language: status.translation.detected_language }) }}</h4>
+            <RichContent
+              :class="{ '-single-line': singleLine }"
+              class="text media-body"
+              :html="status.translation.text"
+              :emoji="status.emojis"
+              :handle-links="true"
+              :mfm="renderMisskeyMarkdown && (status.media_type === 'text/x.misskeymarkdown')"
+              :greentext="mergedConfig.greentext"
+              :attentions="status.attentions"
+              @parseReady="onParseReady"
+            />
+          </div>
         </div>
         <button
           v-show="hideSubjectStatus"
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 7d845ca4..3671a2a2 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -875,6 +875,8 @@
         "thread_show": "Show this thread",
         "thread_show_full": "Show everything under this thread ({numStatus} post in total, max depth {depth}) | Show everything under this thread ({numStatus} posts in total, max depth {depth})",
         "thread_show_full_with_icon": "{icon} {text}",
+        "translate": "Translate",
+        "translated_from": "Translated from {language}",
         "unbookmark": "Unbookmark",
         "unmute_conversation": "Unmute conversation",
         "unpin": "Unpin from profile",
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
index 1858ed92..888afe29 100644
--- a/src/i18n/ja_pedantic.json
+++ b/src/i18n/ja_pedantic.json
@@ -791,6 +791,8 @@
         "status_unavailable": "利用できません",
         "thread_muted": "ミュートされたスレッド",
         "thread_muted_and_words": "以下の単語を含むため:",
+        "translate": "翻訳",
+        "translated_from": "{language}から翻訳されました",
         "unbookmark": "ブックマーク解除",
         "unmute_conversation": "スレッドのミュートを解除",
         "unpin": "プロフィールのピン留めを外す",
diff --git a/src/modules/config.js b/src/modules/config.js
index f97e5a8f..789c0640 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -187,6 +187,7 @@ const config = {
         case 'interfaceLanguage':
           messages.setLanguage(this.getters.i18n, value)
           Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
+          dispatch('setInstanceOption', { name: 'interfaceLanguage', value })
           break
         case 'thirdColumnMode':
           dispatch('setLayoutWidth', undefined)
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 56e48759..8bb8abb9 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -425,6 +425,10 @@ export const mutations = {
       state.conversationsObject[newStatus.statusnet_conversation_id].forEach(status => { status.thread_muted = newStatus.thread_muted })
     }
   },
+  setTranslatedStatus (state, { id, translation }) {
+    const newStatus = state.allStatusesObject[id]
+    newStatus.translation = translation
+  },
   setRetweeted (state, { status, value }) {
     const newStatus = state.allStatusesObject[status.id]
 
@@ -637,6 +641,10 @@ const statuses = {
       rootState.api.backendInteractor.unpinOwnStatus({ id: statusId })
         .then((status) => dispatch('addNewStatuses', { statuses: [status] }))
     },
+    translateStatus ({ rootState, commit }, { id, translation, language }) {
+      return rootState.api.backendInteractor.translateStatus({ id: id, translation, language })
+        .then((translation) => commit('setTranslatedStatus', { id, translation }))
+    },
     muteConversation ({ rootState, commit }, statusId) {
       return rootState.api.backendInteractor.muteConversation({ id: statusId })
         .then((status) => commit('setMutedStatus', status))
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 686689cd..cc2da9c5 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -31,6 +31,7 @@ const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
 const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
 const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
 const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
+const AKKOMA_TRANSLATE_URL = (id, lang) => `/api/v1/statuses/${id}/translations/${lang}`
 const MASTODON_DISMISS_NOTIFICATION_URL = id => `/api/v1/notifications/${id}/dismiss`
 const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
 const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
@@ -738,6 +739,13 @@ const unretweet = ({ id, credentials }) => {
     .then((data) => parseStatus(data))
 }
 
+const translateStatus = ({ id, credentials, language }) => {
+  return promisedRequest({ url: AKKOMA_TRANSLATE_URL(id, language), method: 'GET', credentials })
+    .then((data) => {
+      return data
+    })
+}
+
 const bookmarkStatus = ({ id, credentials }) => {
   return promisedRequest({
     url: MASTODON_BOOKMARK_STATUS_URL(id),
@@ -1576,7 +1584,8 @@ const apiService = {
   postAnnouncement,
   editAnnouncement,
   deleteAnnouncement,
-  adminFetchAnnouncements
+  adminFetchAnnouncements,
+  translateStatus
 }
 
 export default apiService