diff --git a/core/core.services.yml b/core/core.services.yml
index 6f8cce6..06d10ef 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -239,6 +239,11 @@ services:
       - [setRequest, ['@?request']]
     tags:
       - { name: persist }
+  link_generator:
+    class: Drupal\Core\Utility\LinkGenerator
+    arguments: ['@url_generator', '@module_handler', '@language_manager']
+    calls:
+      - [setRequest, ['@?request']]
   router.dynamic:
     class: Symfony\Cmf\Component\Routing\DynamicRouter
     arguments: ['@router.request_context', '@router.matcher', '@url_generator']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index f0c421a..3a61be9 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1297,6 +1297,9 @@ function drupal_http_header_attributes(array $attributes = array()) {
  *   An HTML string containing a link to the given path.
  *
  * @see url()
+ *
+ * @deprecated as of Drupal 8.0 for internal links. Use route names with rl()
+ * instead, and only use l() for formatting and sanitizing external URLs.
  */
 function l($text, $path, array $options = array()) {
   // Start building a structured representation of our link to be altered later.
diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
new file mode 100644
index 0000000..78d49f9
--- /dev/null
+++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Utility\LinkGenerator.
+ */
+
+namespace Drupal\Core\Utility;
+
+use Drupal\Component\Utility\String;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Template\Attribute;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
+
+class LinkGenerator {
+
+  /**
+   * Stores some information about the current request, like the language.
+   *
+   * @var array
+   */
+  protected $active;
+
+  /**
+   * The url generator.
+   *
+   * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface
+   */
+  protected $urlGenerator;
+
+  /**
+   * The module handler firing the route_link alter hook.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The current request.
+   *
+   * @var \Symfony\Component\HttpFoundation\Request
+   */
+  protected $request;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a LinkGenerator instance.
+   *
+   * @param \Symfony\Component\Routing\Generator\UrlGeneratorInterface $url_generator
+   *   The url generator.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
+   */
+  public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, LanguageManager $language_manager) {
+    $this->urlGenerator = $url_generator;
+    $this->moduleHandler = $module_handler;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * Sets the $request property.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The HttpRequest object representing the current request.
+   */
+  public function setRequest(Request $request) {
+    $this->request = $request;
+
+    // Because this service is called very often we statically cache values that
+    // require an extra function call.
+    $this->active = array(
+      'path' => $this->request->attributes->get('system_path'),
+      'front_page' => drupal_is_front_page(),
+      'language' => $this->languageManager->getLanguage(Language::TYPE_URL)->id,
+      'query' => $this->request->query->all(),
+    );
+  }
+
+  /**
+   * Renders a link to a route given a route name and its parameters.
+   *
+   * This function correctly handles aliased paths and sanitizing text, so all
+   * internal links output by modules should be generated by this function if
+   * possible.
+   *
+   * However, for links enclosed in translatable text you should use t() and
+   * embed the HTML anchor tag directly in the translated string. For example:
+   * @code
+   * t('Visit the <a href="@url">content types</a> page', array('@url' => Drupal::urlGenerator()->generate('node_overview_types')));
+   * @endcode
+   * This keeps the context of the link title ('settings' in the example) for
+   * translators.
+   *
+   * @param string|array $text
+   *   The link text for the anchor tag as a translated string or render array.
+   * @param string $route_name
+   *   The name of the route to use to generate the link.
+   * @param array $parameters
+   *   Any parameters needed to render the route path pattern.
+   * @param array $options
+   *   An associative array of additional options. Defaults to an empty array. It
+   *   may contain the following elements.
+   *   - 'attributes': An associative array of HTML attributes to apply to the
+   *     anchor tag. If element 'class' is included, it must be an array; 'title'
+   *     must be a string; other elements are more flexible, as they just need
+   *     to work as an argument for the constructor of the class
+   *     Drupal\Core\Template\Attribute($options['attributes']).
+   *   - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
+   *     example, to make an image tag into a link, this must be set to TRUE, or
+   *     you will see the escaped HTML image tag. $text is not sanitized if
+   *     'html' is TRUE. The calling function must ensure that $text is already
+   *     safe.
+   *
+   * @return string
+   *   An HTML string containing a link to the given path.
+   *
+   * @see \Drupal\Core\Routing\UrlGenerator::generate()
+   */
+  public function render($text, $route_name, array $parameters = array(), array $options = array()) {
+    // Start building a structured representation of our link to be altered later.
+    $variables = array(
+      'text' => is_array($text) ? drupal_render($text) : $text,
+      'route_name' => $route_name,
+      'parameters' => $parameters,
+      'options' => $options,
+    );
+
+    // Merge in default options.
+    $variables['options'] += array(
+      'attributes' => array(),
+      'query' => array(),
+      'absolute' => FALSE,
+      'html' => FALSE,
+    );
+
+    // The result of the url generator is a plain-text URL. Because we are using
+    // it here in an HTML argument context, we need to encode it properly.
+
+    $url = String::checkPlain($this->urlGenerator->generate($variables['route_name'], $variables['parameters'], $variables['options']['absolute']));
+
+    // Determine whether this link is "active', meaning that it links to the
+    // current page. Being removed from l() in https://drupal.org/node/1979468
+    // @todo What about introducing a special '_front_' route which links to the
+    // frontpage.
+    // @todo It seems to make sense to compare just the current active route
+    // instead of the current path.
+    $variables['url_is_active'] = (trim($url, '/') == $this->active['path'] || (trim($url, '/') == '<front>' && $this->active['front_page']))
+      // The language of an active link is equal to the current language.
+      && (empty($variables['options']['language']) || $variables['options']['language']->id == $this->active['language'])
+      // The query parameters of an active link are equal to the current parameters.
+      && ($variables['options']['query'] == $this->active['query']);
+
+    // Add the "active" class if appropriate.
+    if ($variables['url_is_active']) {
+      $variables['options']['attributes']['class'][] = 'active';
+    }
+
+    // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
+    // only when a quick strpos() gives suspicion tags are present.
+    if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) {
+      $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']);
+    }
+
+    // Allow other modules to modify the structure of the link.
+    $this->moduleHandler->alter('route_link', $variables);
+
+    // Copy attributes out of options.
+    $attributes = new Attribute($variables['options']['attributes']);
+
+    // Sanitize the link text if necessary.
+    $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
+
+    return '<a href="' . $url . '"' . $attributes . '>' . $text . '</a>';
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
new file mode 100644
index 0000000..c426bf7
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Utility\LinkGeneratorTest.
+ */
+
+namespace Drupal\Tests\Core\Utility {
+
+  use Drupal\Core\Language\Language;
+  use Drupal\Core\Utility\LinkGenerator;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Tests the link generator.
+ *
+ * @see \Drupal\Core\Utility\LinkGenerator
+ */
+class LinkGeneratorTest extends UnitTestCase {
+
+  /**
+   * The tested link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGenerator
+   */
+  public $linkGenerator;
+
+  /**
+   * The mocked url generator.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $urlGenerator;
+
+  /**
+   * The mocked module handler.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The mocked language manager.
+   *
+   * @var \PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $languageManager;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Link generator',
+      'description' => 'Tests the link generator.',
+      'group' => 'Common',
+    );
+  }
+
+  protected function setUp() {
+    parent::setUp();
+
+    $this->urlGenerator = $this->getMock('Symfony\Component\Routing\Generator\UrlGeneratorInterface');
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+    $this->languageManager = $this->getMockBuilder('Drupal\Core\Language\LanguageManager')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler, $this->languageManager);
+  }
+
+  /**
+   * Setup a proper language manager.
+   */
+  public function setUpLanguageManager() {
+    $this->languageManager->expects($this->any())
+      ->method('getLanguage')
+      ->will($this->returnValue(new Language(array('id' => 'en'))));
+  }
+
+  /**
+   * Tests the render method.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::render()
+   */
+  public function testRender() {
+    $this->urlGenerator->expects($this->exactly(2))
+      ->method('generate')
+      ->will($this->returnValueMap(array(
+        array('test_route_1', array(), FALSE, '/test-route-1'),
+        array(
+          'test_route_2',
+          array('value' => 'example'),
+          FALSE,
+          '/test-route-2/example'
+        ),
+      )));
+
+    $this->moduleHandler->expects($this->exactly(2))
+      ->method('alter');
+
+    $this->setUpLanguageManager();
+    $request = new Request();
+    $this->linkGenerator->setRequest($request);
+
+    $result = $this->linkGenerator->render('Test', 'test_route_1');
+    $this->assertTag(array('tag' => 'a', 'attributes' => array('href' => '/test-route-1')), $result);
+
+    $result = $this->linkGenerator->render('Test', 'test_route_2', array('value' => 'example'));
+    $this->assertTag(array('tag' => 'a', 'attributes' => array('href' => '/test-route-2/example')), $result);
+  }
+
+  /**
+   * Tests the active class on the render method.
+   *
+   * @see \Drupal\Core\Utility\LinkGenerator::render()
+   */
+  public function testRenderActive() {
+    $this->urlGenerator->expects($this->exactly(2))
+      ->method('generate')
+      ->will($this->returnValueMap(array(
+        array('test_route_1', array(), FALSE, '/test-route-1'),
+      )));
+
+    $this->moduleHandler->expects($this->exactly(2))
+      ->method('alter');
+
+    $this->setUpLanguageManager();
+
+    // Render a link with a path different from the current path.
+    $request = new Request(array(), array(), array('system_path' => 'test-route-2'));
+    $this->linkGenerator->setRequest($request);
+
+    $result = $this->linkGenerator->render('Test', 'test_route_1');
+    $this->assertNotTag(array('tag' => 'a', 'attributes' => array('class' => 'active')), $result);
+
+    // Render a link with a path with the same path as the current path.
+    $request = new Request(array(), array(), array('system_path' => 'test-route-1'));
+    $this->linkGenerator->setRequest($request);
+
+    $result = $this->linkGenerator->render('Test', 'test_route_1');
+    $this->assertTag(array('tag' => 'a', 'attributes' => array('class' => 'active')), $result);
+  }
+
+}
+
+}
+namespace {
+  if (!function_exists('drupal_is_front_page')) {
+    function drupal_is_front_page() {
+      return FALSE;
+    }
+  }
+}
