about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock22
-rw-r--r--app/mailers/notification_mailer.rb8
-rw-r--r--app/views/about/index.html.haml21
-rw-r--r--app/views/about/terms.en.html.haml (renamed from app/views/about/terms.html.haml)0
-rw-r--r--app/views/accounts/_header.html.haml10
-rw-r--r--app/views/accounts/_nothing_here.html.haml2
-rw-r--r--app/views/accounts/followers.html.haml3
-rw-r--r--app/views/accounts/following.html.haml4
-rw-r--r--app/views/auth/mailer/confirmation_instructions.en.html.erb (renamed from app/views/auth/mailer/confirmation_instructions.html.erb)0
-rw-r--r--app/views/auth/mailer/confirmation_instructions.en.text.erb (renamed from app/views/auth/mailer/confirmation_instructions.text.erb)0
-rw-r--r--app/views/auth/mailer/password_change.en.html.erb (renamed from app/views/auth/mailer/password_change.html.erb)0
-rw-r--r--app/views/auth/mailer/password_change.en.text.erb (renamed from app/views/auth/mailer/password_change.text.erb)0
-rw-r--r--app/views/auth/mailer/reset_password_instructions.en.html.erb (renamed from app/views/auth/mailer/reset_password_instructions.html.erb)0
-rw-r--r--app/views/auth/mailer/reset_password_instructions.en.text.erb (renamed from app/views/auth/mailer/reset_password_instructions.text.erb)0
-rw-r--r--app/views/layouts/mailer.text.erb2
-rw-r--r--app/views/notification_mailer/favourite.text.erb2
-rw-r--r--app/views/notification_mailer/follow.text.erb2
-rw-r--r--app/views/notification_mailer/mention.text.erb2
-rw-r--r--app/views/notification_mailer/reblog.text.erb2
-rw-r--r--app/views/oauth/authorizations/new.html.haml10
-rw-r--r--app/views/stream_entries/_favourite.html.haml2
-rw-r--r--app/views/stream_entries/_follow.html.haml2
-rw-r--r--config/application.rb10
-rw-r--r--config/i18n-tasks.yml43
-rw-r--r--config/initializers/assets.rb3
-rw-r--r--config/locales/devise.en.yml89
-rw-r--r--config/locales/doorkeeper.en.yml159
-rw-r--r--config/locales/en.yml44
-rw-r--r--config/locales/simple_form.en.yml47
-rw-r--r--spec/i18n_spec.rb18
-rw-r--r--spec/models/domain_block_spec.rb2
-rw-r--r--spec/models/tag_spec.rb2
33 files changed, 294 insertions, 218 deletions
diff --git a/Gemfile b/Gemfile
index d6ef64cf7..9654c8828 100644
--- a/Gemfile
+++ b/Gemfile
@@ -55,6 +55,7 @@ group :development, :test do
   gem 'pry-rails'
   gem 'fuubar'
   gem 'fabrication'
+  gem 'i18n-tasks', '~> 0.9.6'
 end
 
 group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 9657ee212..a31573af6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -103,6 +103,10 @@ GEM
     dotenv-rails (2.1.1)
       dotenv (= 2.1.1)
       railties (>= 4.0, < 5.1)
+    easy_translate (0.5.0)
+      json
+      thread
+      thread_safe
     erubis (2.7.0)
     excon (0.53.0)
     execjs (2.7.0)
@@ -129,6 +133,7 @@ GEM
       hamlit (>= 1.2.0)
       railties (>= 4.0.1)
     hashdiff (0.3.0)
+    highline (1.7.8)
     hiredis (0.6.1)
     htmlentities (4.3.4)
     http (2.0.3)
@@ -143,6 +148,16 @@ GEM
     httplog (0.3.2)
       colorize
     i18n (0.7.0)
+    i18n-tasks (0.9.6)
+      activesupport (>= 4.0.2)
+      ast (>= 2.1.0)
+      easy_translate (>= 0.5.0)
+      erubis
+      highline (>= 1.7.3)
+      i18n
+      parser (>= 2.2.3.0)
+      term-ansicolor (>= 1.3.2)
+      terminal-table (>= 1.5.1)
     jbuilder (2.6.0)
       activesupport (>= 3.0.0, < 5.1)
       multi_json (~> 1.2)
@@ -352,9 +367,15 @@ GEM
       activesupport (>= 4.0)
       sprockets (>= 3.0.0)
     temple (0.7.7)
+    term-ansicolor (1.4.0)
+      tins (~> 1.0)
+    terminal-table (1.7.0)
+      unicode-display_width (~> 1.1)
     thor (0.19.1)
+    thread (0.2.2)
     thread_safe (0.3.5)
     tilt (2.0.5)
+    tins (1.12.0)
     tzinfo (1.2.2)
       thread_safe (~> 0.1)
     uglifier (3.0.1)
@@ -401,6 +422,7 @@ DEPENDENCIES
   htmlentities
   http
   httplog
+  i18n-tasks (~> 0.9.6)
   jbuilder (~> 2.0)
   jquery-rails
   ledermann-rails-settings
diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 2ed562979..33bea4c79 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -8,7 +8,7 @@ class NotificationMailer < ApplicationMailer
     @status = status
 
     return unless @me.user.settings(:notification_emails).mention
-    mail to: @me.user.email, subject: "You were mentioned by #{@status.account.acct}"
+    mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
   end
 
   def follow(followed_account, follower)
@@ -16,7 +16,7 @@ class NotificationMailer < ApplicationMailer
     @account = follower
 
     return unless @me.user.settings(:notification_emails).follow
-    mail to: @me.user.email, subject: "#{@account.acct} is now following you"
+    mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
   end
 
   def favourite(target_status, from_account)
@@ -25,7 +25,7 @@ class NotificationMailer < ApplicationMailer
     @status  = target_status
 
     return unless @me.user.settings(:notification_emails).favourite
-    mail to: @me.user.email, subject: "#{@account.acct} favourited your status"
+    mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
   end
 
   def reblog(target_status, from_account)
@@ -34,6 +34,6 @@ class NotificationMailer < ApplicationMailer
     @status  = target_status
 
     return unless @me.user.settings(:notification_emails).reblog
-    mail to: @me.user.email, subject: "#{@account.acct} reblogged your status"
+    mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
   end
 end
diff --git a/app/views/about/index.html.haml b/app/views/about/index.html.haml
index e9e2f9d93..307d75c81 100644
--- a/app/views/about/index.html.haml
+++ b/app/views/about/index.html.haml
@@ -6,24 +6,15 @@
     = image_tag 'logo.png'
     Mastodon
 
-  %p
-    Mastodon is a
-    %em free, open-source
-    social network server. A
-    %em decentralized
-    alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the
-    %em social network
-    seamlessly.
-  %p
-    %em= Rails.configuration.x.local_domain
-    is a Mastodon instance.
+  %p= t('about.about_mastodon').html_safe
+  %p= t('about.about_instance', instance: Rails.configuration.x.local_domain).html_safe
 
   .screenshot= image_tag 'screenshot.png'
 
   .actions
     .info
-      = link_to 'Terms', terms_path
-      = link_to 'Source code', 'https://github.com/Gargron/mastodon'
+      = link_to t('about.terms'), terms_path
+      = link_to t('about.source_code'), 'https://github.com/Gargron/mastodon'
 
-    = link_to 'Get started', new_user_registration_path, class: 'button'
-    = link_to 'Log in', new_user_session_path, class: 'button'
+    = link_to t('about.get_started'), new_user_registration_path, class: 'button'
+    = link_to t('auth.login'), new_user_session_path, class: 'button'
diff --git a/app/views/about/terms.html.haml b/app/views/about/terms.en.html.haml
index 9fb318053..9fb318053 100644
--- a/app/views/about/terms.html.haml
+++ b/app/views/about/terms.en.html.haml
diff --git a/app/views/accounts/_header.html.haml b/app/views/accounts/_header.html.haml
index ec2233b77..0063d9f16 100644
--- a/app/views/accounts/_header.html.haml
+++ b/app/views/accounts/_header.html.haml
@@ -2,9 +2,9 @@
   - if user_signed_in? && current_account.id != @account.id
     .controls
       - if current_account.following?(@account)
-        = link_to 'Unfollow', unfollow_account_path(@account), data: { method: :post }, class: 'button'
+        = link_to t('accounts.unfollow'), unfollow_account_path(@account), data: { method: :post }, class: 'button'
       - else
-        = link_to 'Follow', follow_account_path(@account), data: { method: :post }, class: 'button'
+        = link_to t('accounts.follow'), follow_account_path(@account), data: { method: :post }, class: 'button'
 
   .avatar= image_tag @account.avatar.url(:large)
   %h1.name
@@ -17,13 +17,13 @@
     .details-counters
       .counter{ class: active_nav_class(account_url(@account)) }
         = link_to account_url(@account) do
-          %span.counter-label Posts
+          %span.counter-label= t('accounts.posts')
           %span.counter-number= @account.statuses.count
       .counter{ class: active_nav_class(following_account_url(@account)) }
         = link_to following_account_url(@account) do
-          %span.counter-label Following
+          %span.counter-label= t('accounts.following')
           %span.counter-number= @account.following.count
       .counter{ class: active_nav_class(followers_account_url(@account)) }
         = link_to followers_account_url(@account) do
-          %span.counter-label Followers
+          %span.counter-label= t('accounts.followers')
           %span.counter-number= @account.followers.count
diff --git a/app/views/accounts/_nothing_here.html.haml b/app/views/accounts/_nothing_here.html.haml
index faa1feb20..0c6dc1168 100644
--- a/app/views/accounts/_nothing_here.html.haml
+++ b/app/views/accounts/_nothing_here.html.haml
@@ -1 +1 @@
-%p.nothing-here There is nothing here!
+%p.nothing-here= t('accounts.nothing_here')
diff --git a/app/views/accounts/followers.html.haml b/app/views/accounts/followers.html.haml
index 7a6e270ac..493491020 100644
--- a/app/views/accounts/followers.html.haml
+++ b/app/views/accounts/followers.html.haml
@@ -1,6 +1,5 @@
 - content_for :page_title do
-  People who follow
-  = display_name(@account)
+  = t('accounts.people_who_follow', name: display_name(@account))
 
 = render partial: 'header'
 
diff --git a/app/views/accounts/following.html.haml b/app/views/accounts/following.html.haml
index 95843f64d..370cd6c48 100644
--- a/app/views/accounts/following.html.haml
+++ b/app/views/accounts/following.html.haml
@@ -1,7 +1,5 @@
 - content_for :page_title do
-  People whom
-  = display_name(@account)
-  follows
+  = t('accounts.people_followed_by', name: display_name(@account))
 
 = render partial: 'header'
 
diff --git a/app/views/auth/mailer/confirmation_instructions.html.erb b/app/views/auth/mailer/confirmation_instructions.en.html.erb
index 69e9ff80f..69e9ff80f 100644
--- a/app/views/auth/mailer/confirmation_instructions.html.erb
+++ b/app/views/auth/mailer/confirmation_instructions.en.html.erb
diff --git a/app/views/auth/mailer/confirmation_instructions.text.erb b/app/views/auth/mailer/confirmation_instructions.en.text.erb
index bb21cf8e2..bb21cf8e2 100644
--- a/app/views/auth/mailer/confirmation_instructions.text.erb
+++ b/app/views/auth/mailer/confirmation_instructions.en.text.erb
diff --git a/app/views/auth/mailer/password_change.html.erb b/app/views/auth/mailer/password_change.en.html.erb
index a1bc77463..a1bc77463 100644
--- a/app/views/auth/mailer/password_change.html.erb
+++ b/app/views/auth/mailer/password_change.en.html.erb
diff --git a/app/views/auth/mailer/password_change.text.erb b/app/views/auth/mailer/password_change.en.text.erb
index 27581e604..27581e604 100644
--- a/app/views/auth/mailer/password_change.text.erb
+++ b/app/views/auth/mailer/password_change.en.text.erb
diff --git a/app/views/auth/mailer/reset_password_instructions.html.erb b/app/views/auth/mailer/reset_password_instructions.en.html.erb
index 643b43319..643b43319 100644
--- a/app/views/auth/mailer/reset_password_instructions.html.erb
+++ b/app/views/auth/mailer/reset_password_instructions.en.html.erb
diff --git a/app/views/auth/mailer/reset_password_instructions.text.erb b/app/views/auth/mailer/reset_password_instructions.en.text.erb
index fe73b0165..fe73b0165 100644
--- a/app/views/auth/mailer/reset_password_instructions.text.erb
+++ b/app/views/auth/mailer/reset_password_instructions.en.text.erb
diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb
index 632702439..ae52173b5 100644
--- a/app/views/layouts/mailer.text.erb
+++ b/app/views/layouts/mailer.text.erb
@@ -2,4 +2,4 @@
 
 ---
 
-Mastodon notifications from <%= Rails.configuration.x.local_domain %>
+<%= t('application_mailer.signature', instance: Rails.configuration.x.local_domain) %>
diff --git a/app/views/notification_mailer/favourite.text.erb b/app/views/notification_mailer/favourite.text.erb
index 1b6fa274c..b2e1e3e9e 100644
--- a/app/views/notification_mailer/favourite.text.erb
+++ b/app/views/notification_mailer/favourite.text.erb
@@ -1,5 +1,5 @@
 <%= display_name(@me) %>,
 
-Your status was favourited by <%= @account.acct %>:
+<%= t('notification_mailer.favourite.body', name: @account.acct) %>
 
 <%= render partial: 'status' %>
diff --git a/app/views/notification_mailer/follow.text.erb b/app/views/notification_mailer/follow.text.erb
index 4098e4846..4b2ec142c 100644
--- a/app/views/notification_mailer/follow.text.erb
+++ b/app/views/notification_mailer/follow.text.erb
@@ -1,5 +1,5 @@
 <%= display_name(@me) %>,
 
-<%= @account.acct %> is now following you!
+<%= t('notification_mailer.follow.body', name: @account.acct) %>
 
 <%= web_url("accounts/#{@account.id}") %>
diff --git a/app/views/notification_mailer/mention.text.erb b/app/views/notification_mailer/mention.text.erb
index 72dc1b009..31a294bb9 100644
--- a/app/views/notification_mailer/mention.text.erb
+++ b/app/views/notification_mailer/mention.text.erb
@@ -1,5 +1,5 @@
 <%= display_name(@me) %>,
 
-You were mentioned by <%= @status.account.acct %> in:
+<%= t('notification_mailer.mention.body', name: @status.account.acct) %>
 
 <%= render partial: 'status' %>
diff --git a/app/views/notification_mailer/reblog.text.erb b/app/views/notification_mailer/reblog.text.erb
index 2077e949e..7af8052ca 100644
--- a/app/views/notification_mailer/reblog.text.erb
+++ b/app/views/notification_mailer/reblog.text.erb
@@ -1,5 +1,5 @@
 <%= display_name(@me) %>,
 
-Your status was reblogged by <%= @account.acct %>:
+<%= t('notification_mailer.reblog.body', name: @account.acct) %>
 
 <%= render partial: 'status' %>
diff --git a/app/views/oauth/authorizations/new.html.haml b/app/views/oauth/authorizations/new.html.haml
index ba5d426f5..cd6e93e08 100644
--- a/app/views/oauth/authorizations/new.html.haml
+++ b/app/views/oauth/authorizations/new.html.haml
@@ -1,11 +1,11 @@
+- content_for :page_title do
+  = t('doorkeeper.authorizations.new.title')
+
 .oauth-prompt
-  %h2
-    Application
-    %strong=@pre_auth.client.name
-    requests access to your account
+  %h2= t('doorkeeper.authorizations.new.prompt', name: @pre_auth.client.name)
 
   %p
-    It will be able to
+    = t('doorkeeper.authorizations.new.able_to')
     = @pre_auth.scopes.map { |scope| t(scope, scope: [:doorkeeper, :scopes]) }.map { |s| "<strong>#{s}</strong>"}.to_sentence.html_safe
 
 = form_tag oauth_authorization_path, method: :post, class: 'simple_form' do
diff --git a/app/views/stream_entries/_favourite.html.haml b/app/views/stream_entries/_favourite.html.haml
index 85e3a0824..aac90dcdf 100644
--- a/app/views/stream_entries/_favourite.html.haml
+++ b/app/views/stream_entries/_favourite.html.haml
@@ -1,5 +1,5 @@
 .entry.entry-favourite
   .content
     %strong= favourite.account.acct
-    favourited a post by
+    = t('stream_entries.favourited')
     %strong= favourite.status.account.acct
diff --git a/app/views/stream_entries/_follow.html.haml b/app/views/stream_entries/_follow.html.haml
index f6ec8c4f5..1a2e2c554 100644
--- a/app/views/stream_entries/_follow.html.haml
+++ b/app/views/stream_entries/_follow.html.haml
@@ -1,5 +1,5 @@
 .entry.entry-follow
   .content
     %strong= link_to follow.account.acct, account_path(follow.account)
-    is now following
+    = t('stream_entries.is_now_following')
     %strong= link_to follow.target_account.acct, TagManager.instance.url_for(follow.target_account)
diff --git a/config/application.rb b/config/application.rb
index 5b5e66ca7..6525571cc 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -31,16 +31,16 @@ module Mastodon
       allow do
         origins  '*'
 
-        resource '/api/*',       :headers => :any, :methods => [:post, :put, :delete, :get, :options], credentials: false
-        resource '/oauth/token', :headers => :any, :methods => [:post], credentials: false
+        resource '/api/*',       headers: :any, methods: [:post, :put, :delete, :get, :options], credentials: false
+        resource '/oauth/token', headers: :any, methods: [:post], credentials: false
       end
     end
 
     config.middleware.use Rack::Attack
     config.middleware.use Rack::Deflater
 
-    config.browserify_rails.source_map_environments += %w[development production]
-    config.browserify_rails.commandline_options = "--transform [ babelify --presets [ es2015 react ] ] --extension=\".jsx\""
+    config.browserify_rails.source_map_environments += %w(development production)
+    config.browserify_rails.commandline_options = '--transform [ babelify --presets [ es2015 react ] ] --extension=".jsx"'
 
     config.to_prepare do
       Doorkeeper::AuthorizationsController.layout 'auth'
@@ -50,7 +50,7 @@ module Mastodon
       'Server'                 => 'Mastodon',
       'X-Frame-Options'        => 'DENY',
       'X-Content-Type-Options' => 'nosniff',
-      'X-XSS-Protection'       => '1; mode=block'
+      'X-XSS-Protection'       => '1; mode=block',
     }
   end
 end
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
new file mode 100644
index 000000000..d345ce6c0
--- /dev/null
+++ b/config/i18n-tasks.yml
@@ -0,0 +1,43 @@
+# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
+
+# The "main" locale.
+base_locale: en
+data:
+  read:
+    - config/locales/%{locale}.yml
+    - config/locales/**/*.%{locale}.yml
+
+  write:
+    - ['{devise, simple_form, doorkeeper}.*', 'config/locales/\1.%{locale}.yml']
+    - config/locales/%{locale}.yml
+
+  yaml:
+    write:
+      line_width: -1
+
+search:
+  paths:
+   - app/
+
+  relative_roots:
+    - app/controllers
+    - app/helpers
+    - app/mailers
+    - app/views
+
+  exclude:
+    - app/assets/images
+    - app/assets/fonts
+    - app/assets/videos
+
+ignore_missing:
+  - '{devise,simple_form}.*'
+
+ignore_unused:
+  - 'activerecord.attributes.*'
+  - '{devise,will_paginate,doorkeeper}.*'
+  - 'simple_form.{yes,no}'
+  - 'simple_form.{placeholders,hints,labels}.*'
+  - 'simple_form.{error_notification,required}.:'
+  - 'errors.messages.*'
+  - 'activerecord.errors.models.doorkeeper/*'
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index acf79149d..43f7860cb 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -8,4 +8,5 @@ Rails.application.config.assets.version = '1.0'
 
 # Precompile additional assets.
 # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
-Rails.application.config.assets.precompile += %w( application_public.js )
+Rails.application.config.assets.precompile += %w(application_public.js)
+Rails.application.config.assets.initialize_on_precompile = true
diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml
index d06eee4fd..32ac92cf9 100644
--- a/config/locales/devise.en.yml
+++ b/config/locales/devise.en.yml
@@ -1,62 +1,61 @@
-# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
-
+---
 en:
   devise:
     confirmations:
-      confirmed: "Your email address has been successfully confirmed."
-      send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
-      send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
+      confirmed: Your email address has been successfully confirmed.
+      send_instructions: You will receive an email with instructions for how to confirm your email address in a few minutes.
+      send_paranoid_instructions: If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes.
     failure:
-      already_authenticated: "You are already signed in."
-      inactive: "Your account is not activated yet."
-      invalid: "Invalid %{authentication_keys} or password."
-      locked: "Your account is locked."
-      last_attempt: "You have one more attempt before your account is locked."
-      not_found_in_database: "Invalid %{authentication_keys} or password."
-      timeout: "Your session expired. Please sign in again to continue."
-      unauthenticated: "You need to sign in or sign up before continuing."
-      unconfirmed: "You have to confirm your email address before continuing."
+      already_authenticated: You are already signed in.
+      inactive: Your account is not activated yet.
+      invalid: Invalid %{authentication_keys} or password.
+      last_attempt: You have one more attempt before your account is locked.
+      locked: Your account is locked.
+      not_found_in_database: Invalid %{authentication_keys} or password.
+      timeout: Your session expired. Please sign in again to continue.
+      unauthenticated: You need to sign in or sign up before continuing.
+      unconfirmed: You have to confirm your email address before continuing.
     mailer:
       confirmation_instructions:
-        subject: "Mastodon: Confirmation instructions"
+        subject: 'Mastodon: Confirmation instructions'
+      password_change:
+        subject: 'Mastodon: Password changed'
       reset_password_instructions:
-        subject: "Mastodon: Reset password instructions"
+        subject: 'Mastodon: Reset password instructions'
       unlock_instructions:
-        subject: "Mastodon: Unlock instructions"
-      password_change:
-        subject: "Mastodon: Password changed"
+        subject: 'Mastodon: Unlock instructions'
     omniauth_callbacks:
-      failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
-      success: "Successfully authenticated from %{kind} account."
+      failure: Could not authenticate you from %{kind} because "%{reason}".
+      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_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."
+      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_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.
     registrations:
-      destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
-      signed_up: "Welcome! You have signed up successfully."
-      signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
-      signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
-      signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
-      update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
-      updated: "Your account has been updated successfully."
+      destroyed: Bye! Your account has been successfully cancelled. We hope to see you again soon.
+      signed_up: Welcome! You have signed up successfully.
+      signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated.
+      signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked.
+      signed_up_but_unconfirmed: A message with a confirmation link has been sent to your email address. Please follow the link to activate your account.
+      update_needs_confirmation: You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address.
+      updated: Your account has been updated successfully.
     sessions:
-      signed_in: "Signed in successfully."
-      signed_out: "Signed out successfully."
-      already_signed_out: "Signed out successfully."
+      already_signed_out: Signed out successfully.
+      signed_in: Signed in successfully.
+      signed_out: Signed out successfully.
     unlocks:
-      send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
-      send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
-      unlocked: "Your account has been unlocked successfully. Please sign in to continue."
+      send_instructions: You will receive an email with instructions for how to unlock your account in a few minutes.
+      send_paranoid_instructions: If your account exists, you will receive an email with instructions for how to unlock it in a few minutes.
+      unlocked: Your account has been unlocked successfully. Please sign in to continue.
   errors:
     messages:
-      already_confirmed: "was already confirmed, please try signing in"
-      confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
-      expired: "has expired, please request a new one"
-      not_found: "not found"
-      not_locked: "was not locked"
+      already_confirmed: was already confirmed, please try signing in
+      confirmation_period_expired: needs to be confirmed within %{period}, please request a new one
+      expired: has expired, please request a new one
+      not_found: not found
+      not_locked: was not locked
       not_saved:
-        one: "1 error prohibited this %{resource} from being saved:"
+        one: '1 error prohibited this %{resource} from being saved:'
         other: "%{count} errors prohibited this %{resource} from being saved:"
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index e4444c972..aaf5df6fc 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -1,127 +1,112 @@
+---
 en:
   activerecord:
     attributes:
       doorkeeper/application:
-        name: 'Name'
-        redirect_uri: 'Redirect URI'
+        name: Name
+        redirect_uri: Redirect URI
     errors:
       models:
         doorkeeper/application:
           attributes:
             redirect_uri:
-              fragment_present: 'cannot contain a fragment.'
-              invalid_uri: 'must be a valid URI.'
-              relative_uri: 'must be an absolute URI.'
-              secured_uri: 'must be an HTTPS/SSL URI.'
-
+              fragment_present: cannot contain a fragment.
+              invalid_uri: must be a valid URI.
+              relative_uri: must be an absolute URI.
+              secured_uri: must be an HTTPS/SSL URI.
   doorkeeper:
-    scopes:
-      read: read your account's data
-      write: post on your behalf
-      follow: follow, block, unblock and unfollow accounts
     applications:
-      confirmations:
-        destroy: 'Are you sure?'
       buttons:
-        edit: 'Edit'
-        destroy: 'Destroy'
-        submit: 'Submit'
-        cancel: 'Cancel'
-        authorize: 'Authorize'
+        authorize: Authorize
+        cancel: Cancel
+        destroy: Destroy
+        edit: Edit
+        submit: Submit
+      confirmations:
+        destroy: Are you sure?
+      edit:
+        title: Edit application
       form:
-        error: 'Whoops! Check your form for possible errors'
+        error: Whoops! Check your form for possible errors
       help:
-        redirect_uri: 'Use one line per URI'
-        native_redirect_uri: 'Use %{native_redirect_uri} for local tests'
-        scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.'
-      edit:
-        title: 'Edit application'
+        native_redirect_uri: Use %{native_redirect_uri} for local tests
+        redirect_uri: Use one line per URI
+        scopes: Separate scopes with spaces. Leave blank to use the default scopes.
       index:
-        title: 'Your applications'
-        new: 'New Application'
-        name: 'Name'
-        callback_url: 'Callback URL'
+        callback_url: Callback URL
+        name: Name
+        new: New Application
+        title: Your applications
       new:
-        title: 'New Application'
+        title: New Application
       show:
+        actions: Actions
+        application_id: Application Id
+        callback_urls: Callback urls
+        scopes: Scopes
+        secret: Secret
         title: 'Application: %{name}'
-        application_id: 'Application Id'
-        secret: 'Secret'
-        scopes: 'Scopes'
-        callback_urls: 'Callback urls'
-        actions: 'Actions'
-
     authorizations:
       buttons:
-        authorize: 'Authorize'
-        deny: 'Deny'
+        authorize: Authorize
+        deny: Deny
       error:
-        title: 'An error has occurred'
+        title: An error has occurred
       new:
-        title: 'Authorization required'
-        prompt: 'Authorize %{client_name} to use your account?'
-        able_to: 'This application will be able to'
+        able_to: It will be able to
+        prompt: Application %{client_name} requests access to your account
+        title: Authorization required
       show:
-        title: 'Authorization code'
-
+        title: Authorization code
     authorized_applications:
-      confirmations:
-        revoke: 'Are you sure?'
       buttons:
-        revoke: 'Revoke'
+        revoke: Revoke
+      confirmations:
+        revoke: Are you sure?
       index:
-        title: 'Your authorized applications'
-        application: 'Application'
-        created_at: 'Created At'
-        date_format: '%Y-%m-%d %H:%M:%S'
-
+        application: Application
+        created_at: Created At
+        date_format: "%Y-%m-%d %H:%M:%S"
+        title: Your authorized applications
     errors:
       messages:
-        # Common error messages
-        invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
-        invalid_redirect_uri: 'The redirect uri included is not valid.'
-        unauthorized_client: 'The client is not authorized to perform this request using this method.'
-        access_denied: 'The resource owner or authorization server denied the request.'
-        invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
-        server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
-        temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
-
-        #configuration error messages
-        credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
-        resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'
-
-        # Access grant errors
-        unsupported_response_type: 'The authorization server does not support this response type.'
-
-        # Access token errors
-        invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
-        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.'
-        unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
-
-        # Password Access token errors
-        invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found'
-
+        access_denied: The resource owner or authorization server denied the request.
+        credential_flow_not_configured: Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.
+        invalid_client: Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.
+        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 is not valid.
+        invalid_request: The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.
+        invalid_resource_owner: The provided resource owner credentials are not valid, or resource owner cannot be found
+        invalid_scope: The requested scope is invalid, unknown, or malformed.
         invalid_token:
-          revoked: "The access token was revoked"
-          expired: "The access token expired"
-          unknown: "The access token is invalid"
-
+          expired: The access token expired
+          revoked: The access token was revoked
+          unknown: The access token is invalid
+        resource_owner_authenticator_not_configured: Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.
+        server_error: The authorization server encountered an unexpected condition which prevented it from fulfilling the request.
+        temporarily_unavailable: The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.
+        unauthorized_client: The client is not authorized to perform this request using this method.
+        unsupported_grant_type: The authorization grant type is not supported by the authorization server.
+        unsupported_response_type: The authorization server does not support this response type.
     flash:
       applications:
         create:
-          notice: 'Application created.'
+          notice: Application created.
         destroy:
-          notice: 'Application deleted.'
+          notice: Application deleted.
         update:
-          notice: 'Application updated.'
+          notice: Application updated.
       authorized_applications:
         destroy:
-          notice: 'Application revoked.'
-
+          notice: Application revoked.
     layouts:
       admin:
         nav:
-          oauth2_provider: 'OAuth2 Provider'
-          applications: 'Applications'
+          applications: Applications
+          oauth2_provider: OAuth2 Provider
       application:
-        title: 'OAuth authorization required'
+        title: OAuth authorization required
+    scopes:
+      follow: follow, block, unblock and unfollow accounts
+      read: read your account's data
+      write: post on your behalf
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 55b062c0d..ab16ed082 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1,23 +1,57 @@
+---
 en:
+  about:
+    about_instance: "<em>%{instance}</em> is a Mastodon instance."
+    about_mastodon: Mastodon is a <em>free, open-source</em> social network server. A <em>decentralized</em> alternative to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the <em>social network</em> seamlessly.
+    get_started: Get started
+    source_code: Source code
+    terms: Terms
+  accounts:
+    follow: Follow
+    followers: Followers
+    following: Following
+    nothing_here: There is nothing here!
+    people_followed_by: People whom %{name} follows
+    people_who_follow: People who follow %{name}
+    posts: Posts
+    unfollow: Unfollow
+  application_mailer:
+    signature: Mastodon notifications from %{instance}
   auth:
+    change_password: Change password
+    didnt_get_confirmation: Didn't receive confirmation instructions?
+    forgot_password: Forgot your password?
     login: Log in
     register: Sign up
-    forgot_password: Forgot your password?
-    didnt_get_confirmation: Didn't receive confirmation instructions?
     resend_confirmation: Resend confirmation instructions
     reset_password: Reset password
     set_new_password: Set new password
-    change_password: Change password
   generic:
+    changes_saved_msg: Changes successfully saved!
+    powered_by: powered by %{link}
     save_changes: Save changes
     validation_errors:
       one: Something isn't quite right yet! Please review the error below
       other: Something isn't quite right yet! Please review %{count} errors below
-    powered_by: powered by %{link}
-    changes_saved_msg: Changes successfully saved!
+  notification_mailer:
+    favourite:
+      body: 'Your status was favourited by %{name}:'
+      subject: "%{name} favourited your status"
+    follow:
+      body: "%{name} is now following you!"
+      subject: "%{name} is now following you"
+    mention:
+      body: 'You were mentioned by %{name} in:'
+      subject: You were mentioned by %{name}
+    reblog:
+      body: 'Your status was reblogged by %{name}:'
+      subject: "%{name} reblogged your status"
   pagination:
     next: Next
     prev: Prev
   settings:
     edit_profile: Edit profile
     preferences: Preferences
+  stream_entries:
+    favourited: favourited a post by
+    is_now_following: is now following
diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml
index 34bc94963..ef69f7569 100644
--- a/config/locales/simple_form.en.yml
+++ b/config/locales/simple_form.en.yml
@@ -1,43 +1,28 @@
+---
 en:
   simple_form:
-    "yes": 'Yes'
-    "no": 'No'
-    required:
-      text: 'required'
-      mark: '*'
     error_notification:
-      default_message: "Please review the problems below:"
-
+      default_message: 'Please review the problems below:'
     labels:
       defaults:
-        email: E-mail address
-        password: Password
-        username: Username
-        confirm_password: Confirm password
-        new_password: New password
+        avatar: Avatar
         confirm_new_password: Confirm new password
+        confirm_password: Confirm password
         current_password: Current password
         display_name: Display name
-        note: Bio
-        avatar: Avatar
+        email: E-mail address
         header: Header
+        new_password: New password
+        note: Bio
+        password: Password
+        username: Username
       notification_emails:
-        follow: Send e-mail when someone follows you
-        reblog: Send e-mail when someone reblogs your status
         favourite: Send e-mail when someone favourites your status
+        follow: Send e-mail when someone follows you
         mention: Send e-mail when someone mentions you
-    #   user:
-    #     new:
-    #       email: 'E-mail to sign in.'
-    #     edit:
-    #       email: 'E-mail.'
-    # hints:
-    #   defaults:
-    #     username: 'User name to sign in.'
-    #     password: 'No special characters, please.'
-    # include_blanks:
-    #   defaults:
-    #     age: 'Rather not say'
-    # prompts:
-    #   defaults:
-    #     age: 'Select your age'
+        reblog: Send e-mail when someone reblogs your status
+    'no': 'No'
+    required:
+      mark: "*"
+      text: required
+    'yes': 'Yes'
diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb
new file mode 100644
index 000000000..e7126127e
--- /dev/null
+++ b/spec/i18n_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+require 'i18n/tasks'
+
+RSpec.describe 'I18n' do
+  let(:i18n) { I18n::Tasks::BaseTask.new }
+  let(:missing_keys) { i18n.missing_keys }
+  let(:unused_keys) { i18n.unused_keys }
+
+  it 'does not have missing keys' do
+    expect(missing_keys).to be_empty,
+      "Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
+  end
+
+  it 'does not have unused keys' do
+    expect(unused_keys).to be_empty,
+      "#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
+  end
+end
diff --git a/spec/models/domain_block_spec.rb b/spec/models/domain_block_spec.rb
index 57c519014..ad5403110 100644
--- a/spec/models/domain_block_spec.rb
+++ b/spec/models/domain_block_spec.rb
@@ -1,5 +1,5 @@
 require 'rails_helper'
 
 RSpec.describe DomainBlock, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+
 end
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index 0d0fcb057..9a7f481e4 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -1,5 +1,5 @@
 require 'rails_helper'
 
 RSpec.describe Tag, type: :model do
-  pending "add some examples to (or delete) #{__FILE__}"
+
 end