diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index 979419e..5bdb4da 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -5,215 +5,11 @@
  * Administration functions for language.module.
  */
 
-use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
- * User interface for the language addition screen.
- */
-function language_admin_add_form($form, &$form_state) {
-  $predefined_languages = language_admin_predefined_list();
-  $predefined_languages['custom'] = t('Custom language...');
-  $predefined_default = !empty($form_state['values']['predefined_langcode']) ? $form_state['values']['predefined_langcode'] : key($predefined_languages);
-  $form['predefined_langcode'] = array(
-    '#type' => 'select',
-    '#title' => t('Language name'),
-    '#default_value' => $predefined_default,
-    '#options' => $predefined_languages,
-  );
-  $form['predefined_submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Add language'),
-    '#limit_validation_errors' => array(array('predefined_langcode'), array('predefined_submit')),
-    '#states' => array(
-      'invisible' => array(
-        'select#edit-predefined-langcode' => array('value' => 'custom'),
-      ),
-    ),
-    '#validate' => array('language_admin_add_predefined_form_validate'),
-    '#submit' => array('language_admin_add_form_submit'),
-  );
-
-  $form['custom_language'] = array(
-    '#type' => 'container',
-    '#states' => array(
-      'visible' => array(
-        'select#edit-predefined-langcode' => array('value' => 'custom'),
-      ),
-    ),
-  );
-  _language_admin_common_controls($form['custom_language']);
-  $form['custom_language']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Add custom language'),
-    '#validate' => array('language_admin_add_custom_form_validate'),
-    '#submit' => array('language_admin_add_form_submit'),
-  );
-
-  return $form;
-}
-
-/**
- * Editing screen for a particular language.
- *
- * @param $langcode
- *   Language code of the language to edit.
- */
-function language_admin_edit_form($form, &$form_state, $language) {
-  _language_admin_common_controls($form, $language);
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save language')
-  );
-  $form['#submit'][] = 'language_admin_edit_form_submit';
-  $form['#validate'][] = 'language_admin_edit_form_validate';
-  return $form;
-}
-
-/**
- * Common elements of the language addition and editing form.
- *
- * @param $form
- *   A parent form item (or empty array) to add items below.
- * @param $language
- *   Language object to edit.
- */
-function _language_admin_common_controls(&$form, $language = NULL) {
-  if (!is_object($language)) {
-    $language = new Language(array('id' => NULL, 'name' => NULL,));
-  }
-  if (isset($language->id)) {
-    $form['langcode_view'] = array(
-      '#type' => 'item',
-      '#title' => t('Language code'),
-      '#markup' => $language->id
-    );
-    $form['langcode'] = array(
-      '#type' => 'value',
-      '#value' => $language->id
-    );
-  }
-  else {
-    $form['langcode'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Language code'),
-      '#maxlength' => 12,
-      '#required' => TRUE,
-      '#default_value' => '',
-      '#disabled' => FALSE,
-      '#description' => t('Use language codes as <a href="@w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', array('@w3ctags' => 'http://www.w3.org/International/articles/language-tags/')),
-    );
-  }
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Language name in English'),
-    '#maxlength' => 64,
-    '#default_value' => @$language->name,
-    '#required' => TRUE,
-  );
-  $form['direction'] = array(
-    '#type' => 'radios',
-    '#title' => t('Direction'),
-    '#required' => TRUE,
-    '#description' => t('Direction that text in this language is presented.'),
-    '#default_value' => @$language->direction,
-    '#options' => array(Language::DIRECTION_LTR => t('Left to right'), Language::DIRECTION_RTL => t('Right to left')),
-  );
-  return $form;
-}
-
-/**
- * Element specific validator for the Add language button.
- */
-function language_admin_add_predefined_form_validate($form, &$form_state) {
-  $langcode = $form_state['values']['predefined_langcode'];
-  if ($langcode == 'custom') {
-    form_set_error('predefined_langcode', t('Fill in the language details and save the language with <em>Add custom language</em>.'));
-  }
-  else {
-    if ($language = language_load($langcode)) {
-      form_set_error('predefined_langcode', t('The language %language (%langcode) already exists.', array('%language' => $language->name, '%langcode' => $langcode)));
-    }
-  }
-}
-
-/**
- * Validate the language addition form on custom language button.
- */
-function language_admin_add_custom_form_validate($form, &$form_state) {
-  if ($form_state['values']['predefined_langcode'] == 'custom') {
-    $langcode = $form_state['values']['langcode'];
-    // Reuse the editing form validation routine if we add a custom language.
-    language_admin_edit_form_validate($form['custom_language'], $form_state);
-
-    if ($language = language_load($langcode)) {
-      form_set_error('langcode', t('The language %language (%langcode) already exists.', array('%language' => $language->name, '%langcode' => $langcode)));
-    }
-  }
-  else {
-    form_set_error('predefined_langcode', t('Use the <em>Add language</em> button to save a predefined language.'));
-  }
-}
-
-/**
- * Process the custom and predefined language addition form submission.
- */
-function language_admin_add_form_submit($form, &$form_state) {
-  $langcode = $form_state['values']['predefined_langcode'];
-  if ($langcode == 'custom') {
-    $langcode = $form_state['values']['langcode'];
-    // Custom language form.
-    $language = new Language(array(
-      'id' => $langcode,
-      'name' => $form_state['values']['name'],
-      'direction' => $form_state['values']['direction'],
-    ));
-  }
-  else {
-    $language = new Language(array('id' => $langcode));
-  }
-  // Save the language and inform the user that it happened.
-  $language = language_save($language);
-  drupal_set_message(t('The language %language has been created and can now be used.', array('%language' => $language->name)));
-
-  // Tell the user they have the option to add a language switcher block
-  // to their theme so they can switch between the languages.
-  drupal_set_message(t('Use one of the language switcher blocks to allow site visitors to switch between languages. You can enable these blocks on the <a href="@block-admin">block administration page</a>.', array('@block-admin' => url('admin/structure/block'))));
-
-  $form_state['redirect'] = 'admin/config/regional/language';
-}
-
-/**
- * Validate the language editing form. Reused for custom language addition too.
- */
-function language_admin_edit_form_validate($form, &$form_state) {
-  // Ensure sane field values for langcode and name.
-  if (!isset($form['langcode_view']) && preg_match('@[^a-zA-Z_-]@', $form_state['values']['langcode'])) {
-    form_set_error('langcode', t('%field may only contain characters a-z, underscores, or hyphens.', array('%field' => $form['langcode']['#title'])));
-  }
-  if ($form_state['values']['name'] != check_plain($form_state['values']['name'])) {
-    form_set_error('name', t('%field cannot contain any markup.', array('%field' => $form['name']['#title'])));
-  }
-}
-
-/**
- * Process the language editing form submission.
- */
-function language_admin_edit_form_submit($form, &$form_state) {
-  // Prepare a language object for saving.
-  $languages = language_list();
-  $langcode = $form_state['values']['langcode'];
-  $language = $languages[$langcode];
-  $language->name = $form_state['values']['name'];
-  $language->direction = $form_state['values']['direction'];
-  language_save($language);
-  $form_state['redirect'] = 'admin/config/regional/language';
-}
-
-/**
  * User interface for the language deletion confirmation screen.
  */
 function language_admin_delete_form($form, &$form_state, $language) {
diff --git a/core/modules/language/language.local_actions.yml b/core/modules/language/language.local_actions.yml
new file mode 100644
index 0000000..32c143d
--- /dev/null
+++ b/core/modules/language/language.local_actions.yml
@@ -0,0 +1,5 @@
+language_add_local_action:
+  route_name: language_add
+  title: 'Add language'
+  appears_on:
+    - language_admin_overview
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 4d2688c..4e0e4ec 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -78,20 +78,12 @@ function language_menu() {
     'type' => MENU_DEFAULT_LOCAL_TASK,
   );
   $items['admin/config/regional/language/add'] = array(
-    'title' => 'Add language',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('language_admin_add_form'),
-    'access arguments' => array('administer languages'),
-    'type' => MENU_LOCAL_ACTION,
-    'file' => 'language.admin.inc',
+    'route_name' => 'language_add',
+    'type' => MENU_SIBLING_LOCAL_TASK,
   );
   $items['admin/config/regional/language/edit/%language'] = array(
     'title' => 'Edit language',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('language_admin_edit_form', 5),
-    'access callback' => 'language_access_language_edit_or_delete',
-    'access arguments' => array(5),
-    'file' => 'language.admin.inc',
+    'route_name' => 'language_edit',
   );
   $items['admin/config/regional/language/delete/%language'] = array(
     'title' => 'Confirm delete',
diff --git a/core/modules/language/language.routing.yml b/core/modules/language/language.routing.yml
index e75e1ce..715ee95 100644
--- a/core/modules/language/language.routing.yml
+++ b/core/modules/language/language.routing.yml
@@ -19,6 +19,20 @@ language_negotiation_selected:
   requirements:
     _permission: 'administer languages'
 
+language_add:
+  pattern: '/admin/config/regional/language/add'
+  defaults:
+    _entity_form: 'language_entity.add'
+  requirements:
+    _entity_create_access: 'language_entity'
+
+language_edit:
+  pattern: '/admin/config/regional/language/edit/{language_entity}'
+  defaults:
+    _entity_form: 'language_entity.edit'
+  requirements:
+    _entity_access: 'language_entity.update'
+
 language_admin_overview:
   pattern: '/admin/config/regional/language'
   defaults:
diff --git a/core/modules/language/lib/Drupal/language/Entity/Language.php b/core/modules/language/lib/Drupal/language/Entity/Language.php
index 15c6a67..e652906 100644
--- a/core/modules/language/lib/Drupal/language/Entity/Language.php
+++ b/core/modules/language/lib/Drupal/language/Entity/Language.php
@@ -24,7 +24,11 @@
  *   controllers = {
  *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
  *     "list" = "Drupal\language\LanguageListController",
- *     "access" = "Drupal\language\LanguageAccessController"
+ *     "access" = "Drupal\language\LanguageAccessController",
+ *     "form" = {
+ *       "add" = "Drupal\language\Form\LanguageAddForm",
+ *       "edit" = "Drupal\language\Form\LanguageEditForm"
+ *     }
  *   },
  *   config_prefix = "language.entity",
  *   entity_keys = {
diff --git a/core/modules/language/lib/Drupal/language/Form/LanguageAddForm.php b/core/modules/language/lib/Drupal/language/Form/LanguageAddForm.php
new file mode 100644
index 0000000..244fc3e
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/Form/LanguageAddForm.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\language\Form\LanguageAddForm.
+ */
+
+namespace Drupal\language\Form;
+
+use Drupal\language\Form\LanguageFormBase;
+use Drupal\Core\Language\Language;
+
+/**
+ * Controller for language addition forms.
+ */
+class LanguageAddForm extends LanguageFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    // @todo Remove in favour of base method.
+    return 'language_admin_add_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state) {
+    $this->moduleHandler->loadInclude('language', 'inc', 'language.admin');
+    $predefined_languages = language_admin_predefined_list();
+
+    $predefined_languages['custom'] = $this->translator->translate('Custom language...');
+    $predefined_default = !empty($form_state['values']['predefined_langcode']) ? $form_state['values']['predefined_langcode'] : key($predefined_languages);
+    $form['predefined_langcode'] = array(
+      '#type' => 'select',
+      '#title' => $this->translator->translate('Language name'),
+      '#default_value' => $predefined_default,
+      '#options' => $predefined_languages,
+    );
+    $form['predefined_submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->translator->translate('Add language'),
+      '#limit_validation_errors' => array(array('predefined_langcode'), array('predefined_submit')),
+      '#states' => array(
+        'invisible' => array(
+          'select#edit-predefined-langcode' => array('value' => 'custom'),
+        ),
+      ),
+      '#validate' => array(array($this, 'validatePredefined')),
+      '#submit' => array(array($this, 'submitForm')),
+    );
+
+    $form['custom_language'] = array(
+      '#type' => 'container',
+      '#states' => array(
+        'visible' => array(
+          'select#edit-predefined-langcode' => array('value' => 'custom'),
+        ),
+      ),
+    );
+    parent::commonForm($form['custom_language']);
+    $form['custom_language']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => $this->translator->translate('Add custom language'),
+      '#validate' => array(array($this, 'validateCustom')),
+      '#submit' => array(array($this, 'submitForm')),
+    );
+
+    return $form;
+  }
+
+    /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $langcode = $form_state['values']['predefined_langcode'];
+    if ($langcode == 'custom') {
+      $langcode = $form_state['values']['langcode'];
+      // Custom language form.
+      $language = new Language(array(
+        'id' => $langcode,
+        'name' => $form_state['values']['name'],
+        'direction' => $form_state['values']['direction'],
+      ));
+    }
+    else {
+      $language = new Language(array('id' => $langcode));
+    }
+    // Save the language and inform the user that it happened.
+    $language = language_save($language);
+    drupal_set_message($this->translator->translate('The language %language has been created and can now be used.', array('%language' => $language->name)));
+
+    // Tell the user they have the option to add a language switcher block
+    // to their theme so they can switch between the languages.
+    drupal_set_message($this->translator->translate('Use one of the language switcher blocks to allow site visitors to switch between languages. You can enable these blocks on the <a href="@block-admin">block administration page</a>.', array('@block-admin' => url('admin/structure/block'))));
+    $form_state['redirect'] = 'admin/config/regional/language';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function actions(array $form, array &$form_state) {
+    $actions = parent::actions($form, $form_state);
+    unset($actions['submit']);
+
+    return $actions;
+  }
+
+  /**
+   * Validates the language addition form on custom language button.
+   */
+  public function validateCustom(array $form, array &$form_state) {
+    if ($form_state['values']['predefined_langcode'] == 'custom') {
+      $langcode = $form_state['values']['langcode'];
+      // Reuse the editing form validation routine if we add a custom language.
+      parent::validate($form['custom_language'], $form_state);
+
+      if ($language = language_load($langcode)) {
+        form_error($form['custom_language']['langcode'], $this->translator->translate('The language %language (%langcode) already exists.', array('%language' => $language->name, '%langcode' => $langcode)));
+      }
+    }
+    else {
+      form_error($form['predefined_langcode'], $this->translator->translate('Use the <em>Add language</em> button to save a predefined language.'));
+    }
+  }
+
+  /**
+   * Element specific validator for the Add language button.
+   */
+  public function validatePredefined($form, &$form_state) {
+    $langcode = $form_state['values']['predefined_langcode'];
+    if ($langcode == 'custom') {
+      form_error($form['predefined_langcode'], $this->translator->translate('Fill in the language details and save the language with <em>Add custom language</em>.'));
+    }
+    else {
+      if ($language = language_load($langcode)) {
+        form_error($form['predefined_langcode'], $this->translator->translate('The language %language (%langcode) already exists.', array('%language' => $language->name, '%langcode' => $langcode)));
+      }
+    }
+  }
+
+
+}
diff --git a/core/modules/language/lib/Drupal/language/Form/LanguageEditForm.php b/core/modules/language/lib/Drupal/language/Form/LanguageEditForm.php
new file mode 100644
index 0000000..d16e8c0
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/Form/LanguageEditForm.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\language\Form\LanguageEditForm.
+ */
+
+namespace Drupal\language\Form;
+
+use Drupal\language\Form\LanguageFormBase;
+
+/**
+ * Controller for language edit forms.
+ */
+class LanguageEditForm extends LanguageFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    // @todo Remove in favour of base method.
+    return 'language_admin_edit_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state) {
+    parent::commonForm($form);
+    return parent::form($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function actions(array $form, array &$form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = $this->translator->translate('Save language');
+    return $actions;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/Form/LanguageFormBase.php b/core/modules/language/lib/Drupal/language/Form/LanguageFormBase.php
new file mode 100644
index 0000000..34df4bd
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/Form/LanguageFormBase.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\language\Form\LanguageFormBase.
+ */
+
+namespace Drupal\language\Form;
+
+use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityFormController;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Language\Language;
+
+/**
+ * Base form controller for language add and edit forms.
+ */
+abstract class LanguageFormBase extends EntityFormController implements EntityControllerInterface {
+
+  /**
+   * The language entity storage controller.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageControllerInterface
+   */
+  protected $languageStorage;
+
+  /**
+   * The translator service.
+   *
+   * @var \Drupal\Core\StringTranslation\Translator\TranslatorInterface
+   */
+  protected $translator;
+
+  /**
+   * Constructs a base class for language add and edit forms.
+   *
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler service.
+   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $language_storage
+   *   The language style entity storage controller.
+   * @param \Drupal\Core\StringTranslation\Translator\TranslatorInterface $translator
+   *   The translator service.
+   */
+  public function __construct(ModuleHandlerInterface $module_handler, EntityStorageControllerInterface $language_storage, TranslatorInterface $translator) {
+    parent::__construct($module_handler);
+    $this->languageStorage = $language_storage;
+    $this->translator = $translator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, $entity_type, array $entity_info) {
+    return new static(
+      $container->get('module_handler'),
+      $container->get('plugin.manager.entity')->getStorageController($entity_type),
+      $container->get('string_translation')
+    );
+  }
+
+  /**
+   * Common elements of the language addition and editing form.
+   */
+  public function commonForm(array &$form) {
+    $language = $this->entity;
+    if (isset($language->id)) {
+      $form['langcode_view'] = array(
+        '#type' => 'item',
+        '#title' => $this->translator->translate('Language code'),
+        '#markup' => $language->id
+      );
+      $form['langcode'] = array(
+        '#type' => 'value',
+        '#value' => $language->id
+      );
+    }
+    else {
+      $form['langcode'] = array(
+        '#type' => 'textfield',
+        '#title' => $this->translator->translate('Language code'),
+        '#maxlength' => 12,
+        '#required' => TRUE,
+        '#default_value' => '',
+        '#disabled' => FALSE,
+        '#description' => $this->translator->translate('Use language codes as <a href="@w3ctags">defined by the W3C</a> for interoperability. <em>Examples: "en", "en-gb" and "zh-hant".</em>', array('@w3ctags' => 'http://www.w3.org/International/articles/language-tags/')),
+      );
+    }
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->translator->translate('Language name in English'),
+      '#maxlength' => 64,
+      '#default_value' => $language->label,
+      '#required' => TRUE,
+    );
+    $form['direction'] = array(
+      '#type' => 'radios',
+      '#title' => $this->translator->translate('Direction'),
+      '#required' => TRUE,
+      '#description' => $this->translator->translate('Direction that text in this language is presented.'),
+      '#default_value' => $language->direction,
+      '#options' => array(
+        Language::DIRECTION_LTR => $this->translator->translate('Left to right'),
+        Language::DIRECTION_RTL => $this->translator->translate('Right to left'),
+      ),
+    );
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate(array $form, array &$form_state) {
+    // Ensure sane field values for langcode and name.
+    if (!isset($form['langcode_view']) && preg_match('@[^a-zA-Z_-]@', $form_state['values']['langcode'])) {
+      form_error($form['langcode'], $this->translator->translate('%field may only contain characters a-z, underscores, or hyphens.', array('%field' => $form['langcode']['#title'])));
+    }
+    if ($form_state['values']['name'] != check_plain($form_state['values']['name'])) {
+      form_error($form['name'], $this->translator->translate('%field cannot contain any markup.', array('%field' => $form['name']['#title'])));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, array &$form_state) {
+    // @todo Use $this->entity->save()
+    $result = $this->entity->isNew() ? SAVED_NEW : SAVED_UPDATED;
+    language_save($this->entity);
+    $form_state['redirect'] = 'admin/config/regional/language';
+    return $result;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function actions(array $form, array &$form_state) {
+    $actions = parent::actions($form, $form_state);
+    unset($actions['delete']);
+    return $actions;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageAccessController.php b/core/modules/language/lib/Drupal/language/LanguageAccessController.php
index e201dec..1609dd0 100644
--- a/core/modules/language/lib/Drupal/language/LanguageAccessController.php
+++ b/core/modules/language/lib/Drupal/language/LanguageAccessController.php
@@ -19,7 +19,6 @@ class LanguageAccessController extends EntityAccessController {
    */
   public function access(EntityInterface $entity, $operation, $langcode = Language::LANGCODE_DEFAULT, AccountInterface $account = NULL) {
     switch ($operation) {
-      case 'create':
       case 'update':
       case 'delete':
         return !$entity->locked && user_access('administer languages');
@@ -28,4 +27,11 @@ public function access(EntityInterface $entity, $operation, $langcode = Language
     return FALSE;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
+    return $account->hasPermission('administer languages');
+  }
+
 }
