diff --git a/Makefile b/Makefile
index 0ae86fef01e3f4135f85dce5afa99b1ece416af6..cfebe781c92790a43bf18933ba73d8d7a0e16476 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,9 @@ all: install compile-sass quality test
 install-test:
 	pip install -q -r requirements/test.txt
 
+install-dev:
+	pip install -q -r requirements/dev.txt
+
 install: install-test
 
 compile-sass:  ## Compile the Sass assets
@@ -31,6 +34,7 @@ upgrade: ## update the requirements/*.txt files with the latest packages satisfy
 	pip install -q -r requirements/pip_tools.txt
 	pip-compile --upgrade -o requirements/pip_tools.txt requirements/pip_tools.in
 	pip-compile --upgrade -o requirements/base.txt requirements/base.in
+	pip-compile --upgrade -o requirements/dev.txt requirements/dev.in
 	pip-compile --upgrade -o requirements/test.txt requirements/test.in
 	pip-compile --upgrade -o requirements/tox.txt requirements/tox.in
 	pip-compile --upgrade -o requirements/travis.txt requirements/travis.in
@@ -38,3 +42,33 @@ upgrade: ## update the requirements/*.txt files with the latest packages satisfy
 	grep -e "^django==" requirements/test.txt > requirements/django.txt
 	sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp
 	mv requirements/test.tmp requirements/test.txt
+
+
+## Localization targets
+
+WORKING_DIR := lti_consumer
+EXTRACT_DIR := $(WORKING_DIR)/translations/en/LC_MESSAGES
+EXTRACTED_DJANGO := $(EXTRACT_DIR)/django-partial.po
+EXTRACTED_DJANGOJS := $(EXTRACT_DIR)/djangojs-partial.po
+EXTRACTED_TEXT := $(EXTRACT_DIR)/text.po
+
+extract_translations: ## extract strings to be translated, outputting .po files
+	cd $(WORKING_DIR) && i18n_tool extract
+	mv $(EXTRACTED_DJANGO) $(EXTRACTED_TEXT)
+	tail -n +20 $(EXTRACTED_DJANGOJS) >> $(EXTRACTED_TEXT)
+	rm $(EXTRACTED_DJANGOJS)
+	sed -i'' -e 's/nplurals=INTEGER/nplurals=2/' $(EXTRACTED_TEXT)
+	sed -i'' -e 's/plural=EXPRESSION/plural=\(n != 1\)/' $(EXTRACTED_TEXT)
+
+compile_translations: ## compile translation files, outputting .mo files for each supported language
+	cd $(WORKING_DIR) && i18n_tool generate
+
+detect_changed_source_translations:
+	cd $(WORKING_DIR) && i18n_tool changed
+
+dummy_translations: ## generate dummy translation (.po) files
+	cd $(WORKING_DIR) && i18n_tool dummy
+
+build_dummy_translations: dummy_translations compile_translations ## generate and compile dummy translation files
+
+validate_translations: build_dummy_translations detect_changed_source_translations ## validate translations
\ No newline at end of file
diff --git a/lti_consumer/api.py b/lti_consumer/api.py
index df1e38b84e5c1b907f145c70c3e2015374f26b93..5490f35c1d9c4977edd4c3301104978c3ef48d3c 100644
--- a/lti_consumer/api.py
+++ b/lti_consumer/api.py
@@ -80,8 +80,11 @@ def get_lti_1p3_launch_info(config_id=None, block=None):
     lti_config = _get_lti_config(config_id, block)
     lti_consumer = lti_config.get_lti_consumer()
 
-    # Check if deep Linking is available, if so, retrieve it's launch url
+    # Check if deep Linking is available, if so, add some extra context:
+    # Deep linking launch URL, and if deep linking is already configured
     deep_linking_launch_url = None
+    deep_linking_content_items = []
+
     if lti_consumer.dl is not None:
         deep_linking_launch_url = lti_consumer.prepare_preflight_url(
             callback_url=get_lms_lti_launch_link(),
@@ -89,6 +92,14 @@ def get_lti_1p3_launch_info(config_id=None, block=None):
             lti_hint="deep_linking_launch"
         )
 
+        # Retrieve LTI Content Items (if any was set up)
+        dl_content_items = LtiDlContentItem.objects.filter(
+            lti_configuration=lti_config
+        )
+        # Add content item attributes to context
+        if dl_content_items.exists():
+            deep_linking_content_items = [item.attributes for item in dl_content_items]
+
     # Return LTI launch information for end user configuration
     return {
         'client_id': lti_config.lti_1p3_client_id,
@@ -97,6 +108,7 @@ def get_lti_1p3_launch_info(config_id=None, block=None):
         'oidc_callback': get_lms_lti_launch_link(),
         'token_url': get_lms_lti_access_token_link(lti_config.location),
         'deep_linking_launch_url': deep_linking_launch_url,
+        'deep_linking_content_items': deep_linking_content_items,
     }
 
 
diff --git a/lti_consumer/locale b/lti_consumer/locale
new file mode 120000
index 0000000000000000000000000000000000000000..cef3e3fa5dbda59d171c3fe69b4a908ca3576b56
--- /dev/null
+++ b/lti_consumer/locale
@@ -0,0 +1 @@
+translations
\ No newline at end of file
diff --git a/lti_consumer/lti_xblock.py b/lti_consumer/lti_xblock.py
index 22d31bb9e731a5d0698b86de04d11cc1d11d8595..58a9432db94c93c69868b418c718358359b3d362 100644
--- a/lti_consumer/lti_xblock.py
+++ b/lti_consumer/lti_xblock.py
@@ -674,8 +674,9 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
                     raise ValueError
                 key = ':'.join(key)
             except ValueError as err:
-                msg = 'Could not parse LTI passport: {lti_passport!r}. Should be "id:key:secret" string.'
-                msg = self.ugettext(msg).format(lti_passport=lti_passport)
+                msg = self.ugettext(
+                    'Could not parse LTI passport: {lti_passport!r}. Should be "id:key:secret" string.'
+                ).format(lti_passport=lti_passport)
                 raise LtiError(msg) from err
 
             if lti_id == self.lti_id.strip():
@@ -810,8 +811,9 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
                     param_name, param_value = [p.strip() for p in custom_parameter.split('=', 1)]
                 except ValueError as err:
                     _ = self.runtime.service(self, "i18n").ugettext
-                    msg = 'Could not parse custom parameter: {custom_parameter!r}. Should be "x=y" string.'
-                    msg = self.ugettext(msg).format(custom_parameter=custom_parameter)
+                    msg = self.ugettext(
+                        'Could not parse custom parameter: {custom_parameter!r}. Should be "x=y" string.'
+                    ).format(custom_parameter=custom_parameter)
                     raise LtiError(msg) from err
 
                 # LTI specs: 'custom_' should be prepended before each custom parameter, as pointed in link above.
@@ -924,7 +926,13 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
         # Render template
         fragment = Fragment()
         loader = ResourceLoader(__name__)
-        fragment.add_content(loader.render_mako_template('/templates/html/lti_1p3_studio.html', context))
+        fragment.add_content(
+            loader.render_django_template(
+                '/templates/html/lti_1p3_studio.html',
+                context,
+                i18n_service=self.runtime.service(self, 'i18n')
+            ),
+        )
         fragment.add_css(loader.load_unicode('static/css/student.css'))
         fragment.add_javascript(loader.load_unicode('static/js/xblock_lti_consumer.js'))
         fragment.initialize_js('LtiConsumerXBlock')
@@ -1101,10 +1109,21 @@ class LtiConsumerXBlock(StudioEditableXBlockMixin, XBlock):
 
             template = loader.render_mako_template('/templates/html/lti_1p3_launch.html', context)
             return Response(template, content_type='text/html')
-        except Lti1p3Exception:
+        except Lti1p3Exception as exc:
+            log.warning(
+                "Error preparing LTI 1.3 launch for block %r: %s",
+                str(self.location),  # pylint: disable=no-member
+                exc,
+            )
             template = loader.render_mako_template('/templates/html/lti_1p3_launch_error.html', context)
             return Response(template, status=400, content_type='text/html')
-        except AssertionError:
+        except AssertionError as exc:
+            log.warning(
+                "Permission on LTI block %r denied for user %r: %s",
+                str(self.location),  # pylint: disable=no-member
+                self.external_user_id,
+                exc,
+            )
             template = loader.render_mako_template('/templates/html/lti_1p3_permission_error.html', context)
             return Response(template, status=403, content_type='text/html')
 
diff --git a/lti_consumer/plugin/views.py b/lti_consumer/plugin/views.py
index e7b7973128176bb8e1c99213c1b5fe5bc9a7cad4..53101b0558cdd58cfc9cefdb8c1d0540d232275f 100644
--- a/lti_consumer/plugin/views.py
+++ b/lti_consumer/plugin/views.py
@@ -1,8 +1,10 @@
 """
 LTI consumer plugin passthrough views
 """
+import logging
+
 from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
-from django.http import HttpResponse, JsonResponse
+from django.http import JsonResponse, Http404
 from django.db import transaction
 from django.views.decorators.csrf import csrf_exempt
 from django.views.decorators.http import require_http_methods
@@ -22,7 +24,7 @@ from lti_consumer.models import (
     LtiDlContentItem,
 )
 
-from lti_consumer.lti_1p3.exceptions import Lti1p3Exception
+from lti_consumer.lti_1p3.exceptions import Lti1p3Exception, LtiDeepLinkingContentTypeNotSupported
 from lti_consumer.lti_1p3.extensions.rest_framework.constants import LTI_DL_CONTENT_TYPE_SERIALIZER_MAP
 from lti_consumer.lti_1p3.extensions.rest_framework.serializers import (
     LtiAgsLineItemSerializer,
@@ -48,6 +50,10 @@ from lti_consumer.plugin.compat import (
     user_course_access,
     user_has_access,
 )
+from lti_consumer.utils import _
+
+
+log = logging.getLogger(__name__)
 
 
 def user_has_staff_access(user, course_key):
@@ -111,8 +117,9 @@ def public_keyset_endpoint(request, usage_id=None):
         response = JsonResponse(lti_config.lti_1p3_public_jwk)
         response['Content-Disposition'] = 'attachment; filename=keyset.json'
         return response
-    except (LtiError, InvalidKeyError, ObjectDoesNotExist):
-        return HttpResponse(status=404)
+    except (LtiError, InvalidKeyError, ObjectDoesNotExist) as exc:
+        log.info("Error while retrieving keyset for usage_id %r: %s", usage_id, exc)
+        raise Http404 from exc
 
 
 @require_http_methods(["GET", "POST"])
@@ -135,8 +142,9 @@ def launch_gate_endpoint(request, suffix):
             handler='lti_1p3_launch_callback',
             suffix=suffix
         )
-    except Exception:  # pylint: disable=broad-except
-        return HttpResponse(status=404)
+    except Exception as exc:
+        log.warning("Error preparing LTI 1.3 launch for hint %r: %s", usage_key_str, exc)
+        raise Http404 from exc
 
 
 @csrf_exempt
@@ -154,8 +162,9 @@ def access_token_endpoint(request, usage_id=None):
             usage_id=str(usage_key),
             handler='lti_1p3_access_token'
         )
-    except Exception:  # pylint: disable=broad-except
-        return HttpResponse(status=404)
+    except Exception as exc:
+        log.warning("Error retrieving an access token for usage_id %r: %s", usage_id, exc)
+        raise Http404 from exc
 
 
 # Post from external tool that doesn't
@@ -194,10 +203,11 @@ def deep_linking_response_endpoint(request, lti_config_id=None):
             LtiDlContentItem.objects.filter(lti_configuration=lti_config).delete()
 
             for content_item in content_items:
-
                 content_type = content_item.get('type')
 
-                # Retrieve serializer (or throw error)
+                # Retrieve serializer (or raise)
+                if content_type not in LTI_DL_CONTENT_TYPE_SERIALIZER_MAP.keys():
+                    raise LtiDeepLinkingContentTypeNotSupported()
                 serializer_cls = LTI_DL_CONTENT_TYPE_SERIALIZER_MAP[content_type]
 
                 # Validate content item data
@@ -211,18 +221,41 @@ def deep_linking_response_endpoint(request, lti_config_id=None):
                     attributes=serializer.validated_data,
                 )
 
-        # TODO: Redirect the user to the launch endpoint, and present content
-        # selected in Deep Linking flow. Can only be completed once content
-        # presentation is implemented. For now, return ok status page
-        return HttpResponse(status=200)
+        # Display sucess page to indicate that LTI DL Content was
+        # saved successfully and auto-close after a few seconds.
+        return render(request, 'html/lti-dl/dl_response_saved.html')
 
     # If LtiConfiguration doesn't exist, error with 404 status.
-    except LtiConfiguration.DoesNotExist:
-        return HttpResponse(status=404)
-    # Bad JWT message, invalid token, or any message validation issues
-    except (Lti1p3Exception, KeyError, PermissionDenied):
-        # TODO: Add template with error message
-        return HttpResponse(status=403)
+    except LtiConfiguration.DoesNotExist as exc:
+        log.info("LtiConfiguration %r does not exist: %s", lti_config_id, exc)
+        raise Http404 from exc
+    # If the deep linking content type is not supported
+    except LtiDeepLinkingContentTypeNotSupported as exc:
+        log.info("One of the selected LTI Content Types is not supported: %s", exc)
+        return render(
+            request,
+            'html/lti-dl/dl_response_error.html',
+            {"error": _("The selected content type is not supported by Open edX.")},
+            status=400
+        )
+    # Bad JWT message, invalid token, or any other message validation issues
+    except (Lti1p3Exception, PermissionDenied) as exc:
+        log.warning(
+            "Permission on LTI Config %r denied for user %r: %s",
+            lti_config,
+            request.user,
+            exc,
+        )
+        return render(
+            request,
+            'html/lti-dl/dl_response_error.html',
+            {
+                "error": _("You don't have access to save LTI Content Items."),
+                "explanation": _("Please check that you have course staff permissions "
+                                 "and double check this block's LTI settings."),
+            },
+            status=403
+        )
 
 
 @require_http_methods(['GET'])
@@ -233,19 +266,26 @@ def deep_linking_content_endpoint(request, lti_config_id=None):
     try:
         # Get LTI Configuration
         lti_config = LtiConfiguration.objects.get(id=lti_config_id)
-    except LtiConfiguration.DoesNotExist:
-        return HttpResponse(status=404)
+    except LtiConfiguration.DoesNotExist as exc:
+        log.info("LtiConfiguration %r does not exist: %s", lti_config_id, exc)
+        raise Http404 from exc
 
     # check if user has proper access
     if not has_block_access(request.user, lti_config.block, lti_config.location.course_key):
-        return HttpResponse(status=403)
+        log.warning(
+            "Permission on LTI Config %r denied for user %r.",
+            lti_config_id,
+            request.user,
+        )
+        raise PermissionDenied
 
     # Get all LTI-DL contents
     content_items = LtiDlContentItem.objects.filter(lti_configuration=lti_config)
 
     # If no LTI-DL contents found for current configuration, throw 404 error
     if not content_items.exists():
-        return HttpResponse(status=404)
+        log.info("There's no Deep linking content for LTI configuration %s.", lti_config)
+        raise Http404
 
     # Render LTI-DL contents
     return render(request, 'html/lti-dl/render_dl_content.html', {
diff --git a/lti_consumer/templates/html/lti-dl/dl_response_error.html b/lti_consumer/templates/html/lti-dl/dl_response_error.html
new file mode 100644
index 0000000000000000000000000000000000000000..70acaa3a2a7c31cb1c1d0cc98c1d27f2efe008fb
--- /dev/null
+++ b/lti_consumer/templates/html/lti-dl/dl_response_error.html
@@ -0,0 +1,18 @@
+{% load i18n %}
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>{% trans "LTI Deep Linking failed." %}</title>
+    </head>
+    <body>
+        <p>
+            <b>{{ error }}</b>
+        </p>
+        {% if explanation %}
+            <p>
+                {{ explanation }}
+            </p>
+        {% endif %}
+    </body>
+</html>
diff --git a/lti_consumer/templates/html/lti-dl/dl_response_saved.html b/lti_consumer/templates/html/lti-dl/dl_response_saved.html
new file mode 100644
index 0000000000000000000000000000000000000000..5be0f5dc6eb0d90e025c99c8f12362baba2381b5
--- /dev/null
+++ b/lti_consumer/templates/html/lti-dl/dl_response_saved.html
@@ -0,0 +1,16 @@
+{% load i18n %}
+<!DOCTYPE HTML>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+        <title>{% trans "LTI Deep Linking" %}</title>
+    </head>
+    <body>
+        <p>
+            <b>{% trans "The LTI Deep Linking content was successfully saved in the LMS." %}</b>
+        </p>
+        <p>
+            {% trans "You can safely close this page now." %}
+        </p>
+    </body>
+</html>
diff --git a/lti_consumer/templates/html/lti_1p3_launch_error.html b/lti_consumer/templates/html/lti_1p3_launch_error.html
index ba5dba765f3949bbe1bd180de21167a1677534a8..813591ab5175169408d3d3e0a1ffcfcc3197d40e 100644
--- a/lti_consumer/templates/html/lti_1p3_launch_error.html
+++ b/lti_consumer/templates/html/lti_1p3_launch_error.html
@@ -1,3 +1,4 @@
+{% load i18n %}
 <!DOCTYPE HTML>
 <html>
     <head>
@@ -6,10 +7,10 @@
     </head>
     <body>
         <p>
-            <b>There was an error while launching the LTI 1.3 tool.</b>
+            <b>{% trans "There was an error while launching the LTI 1.3 tool." %}</b>
         </p>
         <p>
-            If you're seeing this on a live course, please contact the course staff.
+            {% trans "If you're seeing this on a live course, please contact the course staff." %}
         </p>
     </body>
 </html>
diff --git a/lti_consumer/templates/html/lti_1p3_permission_error.html b/lti_consumer/templates/html/lti_1p3_permission_error.html
index eb74976e68c5f8c3219c45e13d3aae74d7c005fa..184ebeda143853519d37c93171d99ab5c6b477aa 100644
--- a/lti_consumer/templates/html/lti_1p3_permission_error.html
+++ b/lti_consumer/templates/html/lti_1p3_permission_error.html
@@ -1,3 +1,4 @@
+{% load i18n %}
 <!DOCTYPE HTML>
 <html>
     <head>
@@ -6,10 +7,10 @@
     </head>
     <body>
         <p>
-            <b>Unauthorized.</b>
+            <b>{% trans "Unauthorized." %}</b>
         </p>
         <p>
-            Students don't have permissions to perform LTI Deep Linking configuration launches.
+            {% trans "Students don't have permissions to perform LTI Deep Linking configuration launches." %}
         </p>
     </body>
 </html>
diff --git a/lti_consumer/templates/html/lti_1p3_studio.html b/lti_consumer/templates/html/lti_1p3_studio.html
index b5303957c1dcf0b45d1d6582836c0bfbed39c42a..820c378d573a9e31147d0c661da01dd931f65ffd 100644
--- a/lti_consumer/templates/html/lti_1p3_studio.html
+++ b/lti_consumer/templates/html/lti_1p3_studio.html
@@ -1,61 +1,58 @@
-<!DOCTYPE HTML>
-<html>
-    <head>
-        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
-        <title>LTI</title>
-    </head>
-    <body>
-        <p>
-            <b>LTI 1.3 Launches can only be performed from the LMS.</b>
-        </p>
-
-        <p>
-            The current implementation of the LTI 1.3 XBlock does not support
-            <a href="http://www.imsglobal.org/lti-advantage-faq#what-is-LTI-Advantage">
-                LTI Advantage services
-            </a>.
-            Therefore, there is no mechanism for LTI Tools to pass back grades.
-        </p>
-
-        <p>
-            To set up the LTI integration, you need to register the LMS in the tool with the information provided below.
-        </p>
-
-        <p>
-            <b>Client: </b>
-            ${client_id}
-        </p>
-
-        <p>
-            <b>Deployment ID: </b>
-            ${deployment_id}
-        </p>
-
-        <p>
-            <b>Keyset URL: </b>
-            ${keyset_url}
-        </p>
-
-        <p>
-            <b>OAuth Token URL: </b>
-            ${token_url}
-        </p>
-
-        <p>
-            <b>OIDC Callback URL: </b>
-            ${oidc_callback}
-        </p>
-
-        % if deep_linking_launch_url:
-            <hr />
-            <p>
-                You can configure this tool's content using LTI Deep Linking.
-                To do that, make sure the block is published and click the link below:
-            </p>
-            <a href="${deep_linking_launch_url}" target="_blank">
-                Deep Linking Launch - Configure tool
-                <i class="icon fa fa-external-link"></i>
-            </a>
-        % endif
-    </body>
-</html>
+{% load i18n %}
+<p>
+    <b>{% trans "LTI 1.3 Launches can only be performed from the LMS." %}</b>
+</p>
+
+<p>
+    {% trans "To set up the LTI integration, you need to register the LMS in the tool with the information provided below." %}
+</p>
+
+<p>
+    <b>{% trans "Client: " %}</b>
+    {{ client_id }}
+</p>
+
+<p>
+    <b>{% trans "Deployment ID: " %}</b>
+    {{ deployment_id }}
+</p>
+
+<p>
+    <b>{% trans "Keyset URL: " %}</b>
+    {{ keyset_url }}
+</p>
+
+<p>
+    <b>{% trans "OAuth Token URL: " %}</b>
+    {{ token_url }}
+</p>
+
+<p>
+    <b>{% trans "OIDC Callback URL: " %}</b>
+    {{ oidc_callback }}
+</p>
+
+{% if deep_linking_launch_url %}
+    <hr />
+    {% if deep_linking_content_items %}
+        <p>
+            <b>{% trans "Deep Linking is configured on this tool." %}</b>
+            {% trans "The Deep Linking configuration stored is presented below:" %}
+        </p>
+        <code>
+            {{ deep_linking_content_items }}
+        </code>
+
+        <p>
+            {% trans "If you run deep linking again, the content above will be replaced." %}
+        </p>
+    {% endif %}
+    <p>
+        {% trans "You can configure this tool's content using LTI Deep Linking." %}
+        {% trans "To do that, make sure the block is published and click the link below:" %}
+    </p>
+    <a href="{{ deep_linking_launch_url }}" target="_blank">
+        {% trans "Deep Linking Launch - Configure tool" %}
+        <i class="icon fa fa-external-link"></i>
+    </a>
+{% endif %}
diff --git a/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py b/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py
index 746626f0a4749bdee5a64fdddc8cde454eb060b6..364b258c6251becf3a99f9a9b22dcbc79e9323c3 100644
--- a/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py
+++ b/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py
@@ -136,7 +136,7 @@ class LtiDeepLinkingResponseEndpointTestCase(LtiDeepLinkingTestCase):
 
     def test_lti_deep_linking_with_invalid_content_type(self):
         """
-        Test that the endpoint returns 403 when content type is not supported.
+        Test that the endpoint returns 400 when content type is not supported.
         """
         response = self.client.post(
             self.url,
@@ -146,7 +146,7 @@ class LtiDeepLinkingResponseEndpointTestCase(LtiDeepLinkingTestCase):
                 }])
             },
         )
-        self.assertEqual(response.status_code, 403)
+        self.assertEqual(response.status_code, 400)
 
     def test_lti_deep_linking_valid_request(self):
         """
diff --git a/lti_consumer/tests/unit/test_api.py b/lti_consumer/tests/unit/test_api.py
index 30de24f4f1ff387a5a693a779a96007467523090..4a7b0e4e557c9e0c5caad7b7ee9caebf492fa046 100644
--- a/lti_consumer/tests/unit/test_api.py
+++ b/lti_consumer/tests/unit/test_api.py
@@ -183,7 +183,8 @@ class TestGetLti1p3LaunchInfo(TestCase):
                 'token_url': 'https://example.com/api/lti_consumer/v1/token/{}'.format(
                     lti_config.lti_1p3_client_id
                 ),
-                'deep_linking_launch_url': 'https://example.com'
+                'deep_linking_launch_url': 'https://example.com',
+                'deep_linking_content_items': []
             }
         )
 
diff --git a/lti_consumer/tests/unit/test_lti_xblock.py b/lti_consumer/tests/unit/test_lti_xblock.py
index d40d228fc88b0c2e38f8397e2e7cc60a1b251a17..94841d293f09b1124eb4728a14f4648b4a25436a 100644
--- a/lti_consumer/tests/unit/test_lti_xblock.py
+++ b/lti_consumer/tests/unit/test_lti_xblock.py
@@ -1287,8 +1287,10 @@ class TestLtiConsumer1p3XBlock(TestCase):
             'oidc_callback': "mock-oidc_callback",
             'token_url': "mock-token_url",
         }
-
+        # Mock i18n service before fetching author view
+        self.xblock.runtime.service.return_value = None
         response = self.xblock.author_view({})
+
         self.assertIn("mock-client_id", response.content)
         self.assertIn("mock-keyset_url", response.content)
         self.assertIn("mock-token_url", response.content)
diff --git a/lti_consumer/translations/ar/LC_MESSAGES/text.mo b/lti_consumer/translations/ar/LC_MESSAGES/text.mo
index a0ebeb64cb88f844bf66df2caf14218b6524dcf8..9f9a74b4686279e24141c6c03e45915e7739c611 100644
Binary files a/lti_consumer/translations/ar/LC_MESSAGES/text.mo and b/lti_consumer/translations/ar/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/config.yaml b/lti_consumer/translations/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f6e22273614c422a18c3765c209f1ff10005dfc6
--- /dev/null
+++ b/lti_consumer/translations/config.yaml
@@ -0,0 +1,14 @@
+# Configuration for i18n workflow.
+
+locales:
+  - en  # English - Source Language
+  - ar  # Arabic
+  - es_419  # Spanish (Latin America)
+  - fr  # French
+  - he  # Hebrew
+  - hi  # Hindi
+  - ja_JP  # Japanese (Japan)
+  - ko_KR  # Korean (Korea)
+  - pt_BR  # Portuguese (Brazil)
+  - ru  # Russian
+  - zh_CN  # Chinese (China)
diff --git a/lti_consumer/translations/en/LC_MESSAGES/text.mo b/lti_consumer/translations/en/LC_MESSAGES/text.mo
index 29fe0c3be6558862833ea7fc65a7665f10b3243a..71cbdf3e9d8d54be31066ec4ad8628bc2c1f2845 100644
Binary files a/lti_consumer/translations/en/LC_MESSAGES/text.mo and b/lti_consumer/translations/en/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/en/LC_MESSAGES/text.po b/lti_consumer/translations/en/LC_MESSAGES/text.po
index 924650b00d930adf1ccefe78996a039187f395e1..2a944197560dbd16b3594e6b928ead2f724383f1 100644
--- a/lti_consumer/translations/en/LC_MESSAGES/text.po
+++ b/lti_consumer/translations/en/LC_MESSAGES/text.po
@@ -1,53 +1,150 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: \n"
-"POT-Creation-Date: 2016-03-18 15:09+0500\n"
-"PO-Revision-Date: 2016-03-18 15:09+0500\n"
-"Last-Translator: \n"
-"Language-Team: \n"
-"Language: en\n"
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2021-03-02 11:48-0300\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 1.8.7\n"
-"X-Poedit-Basepath: ../../..\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Poedit-SearchPath-0: lti_consumer.py\n"
 
-#: lti_consumer.py
+#: lti_1p3/extensions/rest_framework/authentication.py:40
+msgid "Missing LTI 1.3 authentication token."
+msgstr ""
+
+#: lti_1p3/extensions/rest_framework/authentication.py:44
+msgid "Invalid token header. No credentials provided."
+msgstr ""
+
+#: lti_1p3/extensions/rest_framework/authentication.py:48
+msgid "Invalid token header. Token string should not contain spaces."
+msgstr ""
+
+#: lti_1p3/extensions/rest_framework/authentication.py:56
+msgid "LTI configuration not found."
+msgstr ""
+
+#: lti_1p3/extensions/rest_framework/authentication.py:65
+msgid "Invalid token signature."
+msgstr ""
+
+#: lti_xblock.py:130
 msgid "No valid user id found in endpoint URL"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:236
 msgid "Display Name"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:238
 msgid ""
 "Enter the name that students see for this component. Analytics reports may "
 "also use the display name to identify this component."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:242
 msgid "LTI Consumer"
 msgstr ""
 
-#: lti_consumer.py:256
+#: lti_xblock.py:245
 msgid "LTI Application Information"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:247
 msgid ""
 "Enter a description of the third party application. If requesting username "
 "and/or email, use this text box to inform users why their username and/or "
 "email will be forwarded to a third party application."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:257
+msgid "LTI Version"
+msgstr ""
+
+#: lti_xblock.py:265
+msgid ""
+"Select the LTI version that your tool supports.<br />The XBlock LTI Consumer "
+"fully supports LTI 1.1.1 and partially supports LTI 1.3 (only launches, no "
+"grade support)."
+msgstr ""
+
+#: lti_xblock.py:271
+msgid "LTI 1.3 Tool Launch URL"
+msgstr ""
+
+#: lti_xblock.py:275
+msgid ""
+"Enter the LTI 1.3 Tool Launch URL. <br />This is the URL the LMS will use to "
+"launch the LTI Tool."
+msgstr ""
+
+#: lti_xblock.py:280
+msgid "LTI 1.3 OIDC URL"
+msgstr ""
+
+#: lti_xblock.py:284
+msgid ""
+"Enter the LTI 1.3 Tool OIDC Authorization url (can also be called login or "
+"login initiation URL).<br />This is the URL the LMS will use to start a LTI "
+"authorization prior to doing the launch request."
+msgstr ""
+
+#: lti_xblock.py:290
+msgid "LTI 1.3 Tool Public Key"
+msgstr ""
+
+#: lti_xblock.py:295
+msgid ""
+"Enter the LTI 1.3 Tool's public key.<br />This is a string that starts with "
+"'-----BEGIN PUBLIC KEY-----' and is required so that the LMS can check if "
+"the messages and launch requests received have the signature from the tool."
+"<br /><b>This is not required when doing LTI 1.3 Launches without LTI "
+"Advantage nor Basic Outcomes requests.</b>"
+msgstr ""
+
+#: lti_xblock.py:305
+msgid "LTI 1.3 Block Client ID - DEPRECATED"
+msgstr ""
+
+#: lti_xblock.py:308
+msgid "DEPRECATED - This is now stored in the LtiConfiguration model."
+msgstr ""
+
+#: lti_xblock.py:311
+msgid "LTI 1.3 Block Key - DEPRECATED"
+msgstr ""
+
+#: lti_xblock.py:318
+msgid "Deep linking"
+msgstr ""
+
+#: lti_xblock.py:319
+msgid "Select True if you want to enable LTI Advantage Deep Linking."
+msgstr ""
+
+#: lti_xblock.py:324
+msgid "LTI Advantage Deep Linking Launch URL"
+msgstr ""
+
+#: lti_xblock.py:327
+msgid "Enter the LTI Advantage Deep Linking Launch URL. "
+msgstr ""
+
+#: lti_xblock.py:332
 msgid "LTI ID"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:334
 #, python-brace-format
 msgid ""
 "Enter the LTI ID for the external LTI provider. This value must be the same "
@@ -56,11 +153,11 @@ msgid ""
 "documentation{anchor_close} for more details on this setting."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:346
 msgid "LTI URL"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:348
 #, python-brace-format
 msgid ""
 "Enter the URL of the external tool that this component launches. This "
@@ -69,11 +166,11 @@ msgid ""
 "this setting."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:361
 msgid "Custom Parameters"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:363
 #, python-brace-format
 msgid ""
 "Add the key/value pair for any custom parameters, such as the page your e-"
@@ -82,11 +179,11 @@ msgid ""
 "documentation{anchor_close} for more details on this setting."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:373
 msgid "LTI Launch Target"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:375
 msgid ""
 "Select Inline if you want the LTI content to open in an IFrame in the "
 "current page. Select Modal if you want the LTI content to open in a modal "
@@ -95,130 +192,322 @@ msgid ""
 "Tool is set to False."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:389
 msgid "Button Text"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:391
 msgid ""
 "Enter the text on the button used to launch the third party application. "
 "This setting is only used when Hide External Tool is set to False and LTI "
 "Launch Target is set to Modal or New Window."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:399
 msgid "Inline Height"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:401
 msgid ""
 "Enter the desired pixel height of the iframe which will contain the LTI "
 "tool. This setting is only used when Hide External Tool is set to False and "
 "LTI Launch Target is set to Inline."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:409
 msgid "Modal Height"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:411
 msgid ""
 "Enter the desired viewport percentage height of the modal overlay which will "
 "contain the LTI tool. This setting is only used when Hide External Tool is "
 "set to False and LTI Launch Target is set to Modal."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:419
 msgid "Modal Width"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:421
 msgid ""
 "Enter the desired viewport percentage width of the modal overlay which will "
 "contain the LTI tool. This setting is only used when Hide External Tool is "
 "set to False and LTI Launch Target is set to Modal."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:429
 msgid "Scored"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:430
 msgid ""
 "Select True if this component will receive a numerical score from the "
 "external LTI system."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:437
 msgid ""
 "Enter the number of points possible for this component.  The default value "
 "is 1.0.  This setting is only used when Scored is set to True."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:446
 msgid ""
 "The score kept in the xblock KVS -- duplicate of the published score in "
 "django DB"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:451
 msgid "Comment as returned from grader, LTI2.0 spec"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:456
 msgid "Hide External Tool"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:458
 msgid ""
 "Select True if you want to use this component as a placeholder for syncing "
 "with an external grading  system rather than launch an external tool.  This "
 "setting hides the Launch button and any IFrames for this component."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:466
 msgid "Accept grades past deadline"
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:467
 msgid ""
 "Select True to allow third party systems to post grades past the deadline."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:475
 msgid "Request user's username"
 msgstr ""
 
-#: lti_consumer.py
+#. Translators: This is used to request the user's username for a third party service.
+#: lti_xblock.py:477
 msgid "Select True to request the user's username."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:482
 msgid "Request user's email"
 msgstr ""
 
-#: lti_consumer.py
+#. Translators: This is used to request the user's email for a third party service.
+#: lti_xblock.py:484
 msgid "Select True to request the user's email address."
 msgstr ""
 
-#: lti_consumer.py
-#, python-brace-format
+#: lti_xblock.py:489
+msgid "Send extra parameters"
+msgstr ""
+
+#: lti_xblock.py:490
 msgid ""
-"Could not parse LTI passport: {lti_passport}. Should be \"id:key:secret\" "
+"Select True to send the extra parameters, which might contain Personally "
+"Identifiable Information. The processors are site-wide, please consult the "
+"site administrator if you have any questions."
+msgstr ""
+
+#: lti_xblock.py:550
+msgid "Custom Parameters must be a list"
+msgstr ""
+
+#: lti_xblock.py:678
+msgid ""
+"Could not parse LTI passport: {lti_passport!r}. Should be \"id:key:secret\" "
 "string."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:694 lti_xblock.py:710
 msgid "Could not get user id for current request"
 msgstr ""
 
-#: lti_consumer.py
-#, python-brace-format
+#: lti_xblock.py:815
 msgid ""
-"Could not parse custom parameter: {custom_parameter}. Should be \"x=y\" "
+"Could not parse custom parameter: {custom_parameter!r}. Should be \"x=y\" "
 "string."
 msgstr ""
 
-#: lti_consumer.py
+#: lti_xblock.py:1249
 msgid "[LTI]: Real user not found against anon_id: {}"
 msgstr ""
+
+#: models.py:68
+msgid "Configuration Stored on XBlock fields"
+msgstr ""
+
+#: models.py:69
+msgid "Configuration Stored on this model"
+msgstr ""
+
+#: models.py:93
+msgid "LTI configuration data."
+msgstr ""
+
+#: models.py:100
+msgid "The URL of the external tool that initiates the launch."
+msgstr ""
+
+#: models.py:105
+msgid "Client key provided by the LTI tool provider."
+msgstr ""
+
+#: models.py:111
+msgid "Client secret provided by the LTI tool provider."
+msgstr ""
+
+#: models.py:117
+msgid "Platform's generated Private key. Keep this value secret."
+msgstr ""
+
+#: models.py:123
+msgid "Platform's generated Private key ID"
+msgstr ""
+
+#: models.py:128
+msgid "Platform's generated JWK keyset."
+msgstr ""
+
+#: models.py:134
+msgid "Client ID used by LTI tool"
+msgstr ""
+
+#: models.py:144
+msgid "LTI Configuration stores on XBlock needs a block location set."
+msgstr ""
+
+#: models.py:151
+msgid "Invalid LTI configuration."
+msgstr ""
+
+#: models.py:161
+msgid "Block location not set, it's not possible to retrieve the block."
+msgstr ""
+
+#: plugin/views.py:238
+msgid "You don't have access to save LTI Content Items."
+msgstr ""
+
+#: plugin/views.py:239
+msgid ""
+"Please check that you have course staff permissions and double check this "
+"block's LTI settings."
+msgstr ""
+
+#: plugin/views.py:249
+msgid "The selected content type is not supported by Open edX."
+msgstr ""
+
+#: templates/html/lti-dl/dl_response_error.html:6
+msgid "LTI Deep Linking failed."
+msgstr ""
+
+#: templates/html/lti-dl/dl_response_saved.html:6
+msgid "LTI Deep Linking"
+msgstr ""
+
+#: templates/html/lti-dl/dl_response_saved.html:10
+msgid "The LTI Deep Linking content was successfully saved in the LMS."
+msgstr ""
+
+#: templates/html/lti-dl/dl_response_saved.html:13
+msgid "You can safely close this page now."
+msgstr ""
+
+#: templates/html/lti_1p3_launch_error.html:10
+msgid "There was an error while launching the LTI 1.3 tool."
+msgstr ""
+
+#: templates/html/lti_1p3_launch_error.html:13
+msgid ""
+"If you're seeing this on a live course, please contact the course staff."
+msgstr ""
+
+#: templates/html/lti_1p3_permission_error.html:10
+msgid "Unauthorized."
+msgstr ""
+
+#: templates/html/lti_1p3_permission_error.html:13
+msgid ""
+"Students don't have permissions to perform LTI Deep Linking configuration "
+"launches."
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:3
+msgid "LTI 1.3 Launches can only be performed from the LMS."
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:7
+msgid ""
+"To set up the LTI integration, you need to register the LMS in the tool with "
+"the information provided below."
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:11
+msgid "Client: "
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:16
+msgid "Deployment ID: "
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:21
+msgid "Keyset URL: "
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:26
+msgid "OAuth Token URL: "
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:31
+msgid "OIDC Callback URL: "
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:39
+msgid "Deep Linking is configured on this tool."
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:40
+msgid "The Deep Linking configuration stored is presented below:"
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:47
+msgid "If you run deep linking again, the content above will be replaced."
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:51
+msgid "You can configure this tool's content using LTI Deep Linking."
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:52
+msgid "To do that, make sure the block is published and click the link below:"
+msgstr ""
+
+#: templates/html/lti_1p3_studio.html:55
+msgid "Deep Linking Launch - Configure tool"
+msgstr ""
+
+#: static/js/xblock_lti_consumer.js:110
+msgid ""
+"Click OK to have your username and e-mail address sent to a 3rd party "
+"application.\n"
+"\n"
+"Click Cancel to return to this page without sending your information."
+msgstr ""
+
+#: static/js/xblock_lti_consumer.js:112
+msgid ""
+"Click OK to have your username sent to a 3rd party application.\n"
+"\n"
+"Click Cancel to return to this page without sending your information."
+msgstr ""
+
+#: static/js/xblock_lti_consumer.js:114
+msgid ""
+"Click OK to have your e-mail address sent to a 3rd party application.\n"
+"\n"
+"Click Cancel to return to this page without sending your information."
+msgstr ""
diff --git a/lti_consumer/translations/es_419/LC_MESSAGES/text.mo b/lti_consumer/translations/es_419/LC_MESSAGES/text.mo
index dee7f292335a9852a85e76daf8278d6349408967..707ef9cb3e526d0d3324ab9f303081517de12a25 100644
Binary files a/lti_consumer/translations/es_419/LC_MESSAGES/text.mo and b/lti_consumer/translations/es_419/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/fr/LC_MESSAGES/text.mo b/lti_consumer/translations/fr/LC_MESSAGES/text.mo
index f5dcc675e2789cde7d7f20616c7e4eb33b161216..d8b0e7f66593db85b6c5a89811d0cd542680bd49 100644
Binary files a/lti_consumer/translations/fr/LC_MESSAGES/text.mo and b/lti_consumer/translations/fr/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/fr_CA/LC_MESSAGES/text.mo b/lti_consumer/translations/fr_CA/LC_MESSAGES/text.mo
index ec09793ff502ec1d41e67b5c60be2e1840c2655d..d55f999e96f9a848804285b785516157ef38a855 100644
Binary files a/lti_consumer/translations/fr_CA/LC_MESSAGES/text.mo and b/lti_consumer/translations/fr_CA/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/he/LC_MESSAGES/text.mo b/lti_consumer/translations/he/LC_MESSAGES/text.mo
index a1b5b9bd3f7f6e554956af8f9f5c5fae50ed83ed..cdd6c76909f32dd55a07c24ed45fce1a23a409e6 100644
Binary files a/lti_consumer/translations/he/LC_MESSAGES/text.mo and b/lti_consumer/translations/he/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/hi/LC_MESSAGES/text.mo b/lti_consumer/translations/hi/LC_MESSAGES/text.mo
index 71f6f7536b56be732aac0f2f541632268b13bc15..fc050f01e0b6a5ac1df4999f38c9056543a867d7 100644
Binary files a/lti_consumer/translations/hi/LC_MESSAGES/text.mo and b/lti_consumer/translations/hi/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/ja_JP/LC_MESSAGES/text.mo b/lti_consumer/translations/ja_JP/LC_MESSAGES/text.mo
index 10a45b26449637d4d96fe7e1f1273af5264b1da1..9aa9d3661ca98bb7e51e83e6bba8007da06c259a 100644
Binary files a/lti_consumer/translations/ja_JP/LC_MESSAGES/text.mo and b/lti_consumer/translations/ja_JP/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/ko_KR/LC_MESSAGES/text.mo b/lti_consumer/translations/ko_KR/LC_MESSAGES/text.mo
index b3018ddf66b0eb144e60f3b6a4299745c48fd865..7577cdb9adf34b65ba397fb85f87026114d841db 100644
Binary files a/lti_consumer/translations/ko_KR/LC_MESSAGES/text.mo and b/lti_consumer/translations/ko_KR/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/pt_BR/LC_MESSAGES/text.mo b/lti_consumer/translations/pt_BR/LC_MESSAGES/text.mo
index 9daf6963dd04ce0b8a03d9b47e5bec5680667b09..6473d702156ace9e6274756d1cb8d2b5304c116d 100644
Binary files a/lti_consumer/translations/pt_BR/LC_MESSAGES/text.mo and b/lti_consumer/translations/pt_BR/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/ru/LC_MESSAGES/text.mo b/lti_consumer/translations/ru/LC_MESSAGES/text.mo
index 6a05ffc0c97e991261982be454d381549603f7ff..f3293fc4b4e2ec6a9cc38a42d4bad020656b8ef5 100644
Binary files a/lti_consumer/translations/ru/LC_MESSAGES/text.mo and b/lti_consumer/translations/ru/LC_MESSAGES/text.mo differ
diff --git a/lti_consumer/translations/zh_CN/LC_MESSAGES/text.mo b/lti_consumer/translations/zh_CN/LC_MESSAGES/text.mo
index bd720a922a781fb3b6e1170eab986f9a6bfb25cf..1009173b8a38841f3dd7241dce522d371dd4415a 100644
Binary files a/lti_consumer/translations/zh_CN/LC_MESSAGES/text.mo and b/lti_consumer/translations/zh_CN/LC_MESSAGES/text.mo differ
diff --git a/requirements/dev.in b/requirements/dev.in
new file mode 100644
index 0000000000000000000000000000000000000000..da687bd6b5ecdc996b945b1b8f19b284cf51150b
--- /dev/null
+++ b/requirements/dev.in
@@ -0,0 +1,3 @@
+# Core requirements + translation tools
+-r base.txt
+edx-i18n-tools==0.5.0
\ No newline at end of file
diff --git a/requirements/dev.txt b/requirements/dev.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e136e8b1d029f1af017a8ff30f2321034480597c
--- /dev/null
+++ b/requirements/dev.txt
@@ -0,0 +1,159 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+#    make upgrade
+#
+appdirs==1.4.4
+    # via
+    #   -r requirements/base.txt
+    #   fs
+bleach==3.3.0
+    # via -r requirements/base.txt
+certifi==2020.12.5
+    # via
+    #   -r requirements/base.txt
+    #   requests
+chardet==4.0.0
+    # via
+    #   -r requirements/base.txt
+    #   requests
+django-filter==2.4.0
+    # via -r requirements/base.txt
+django==2.2.19
+    # via
+    #   -r requirements/base.txt
+    #   django-filter
+    #   edx-i18n-tools
+    #   edx-opaque-keys
+    #   jsonfield2
+edx-i18n-tools==0.5.0
+    # via -r requirements/dev.in
+edx-opaque-keys[django]==2.2.0
+    # via -r requirements/base.txt
+fs==2.4.12
+    # via
+    #   -r requirements/base.txt
+    #   xblock
+future==0.18.2
+    # via
+    #   -r requirements/base.txt
+    #   pyjwkest
+idna==2.10
+    # via
+    #   -r requirements/base.txt
+    #   requests
+jsonfield2==3.0.3
+    # via -r requirements/base.txt
+lazy==1.4
+    # via -r requirements/base.txt
+lxml==4.6.2
+    # via
+    #   -r requirements/base.txt
+    #   xblock
+mako==1.1.4
+    # via
+    #   -r requirements/base.txt
+    #   xblock-utils
+markupsafe==1.1.1
+    # via
+    #   -r requirements/base.txt
+    #   mako
+    #   xblock
+oauthlib==3.1.0
+    # via -r requirements/base.txt
+packaging==20.9
+    # via
+    #   -r requirements/base.txt
+    #   bleach
+path.py==12.5.0
+    # via edx-i18n-tools
+path==15.1.2
+    # via path.py
+pbr==5.5.1
+    # via
+    #   -r requirements/base.txt
+    #   stevedore
+polib==1.1.0
+    # via edx-i18n-tools
+pycryptodomex==3.10.1
+    # via
+    #   -r requirements/base.txt
+    #   pyjwkest
+pyjwkest==1.4.2
+    # via -r requirements/base.txt
+pymongo==3.11.3
+    # via
+    #   -r requirements/base.txt
+    #   edx-opaque-keys
+pyparsing==2.4.7
+    # via
+    #   -r requirements/base.txt
+    #   packaging
+python-dateutil==2.8.1
+    # via
+    #   -r requirements/base.txt
+    #   xblock
+pytz==2021.1
+    # via
+    #   -r requirements/base.txt
+    #   django
+    #   fs
+    #   xblock
+pyyaml==5.4.1
+    # via
+    #   -r requirements/base.txt
+    #   edx-i18n-tools
+    #   xblock
+requests==2.25.1
+    # via
+    #   -r requirements/base.txt
+    #   pyjwkest
+simplejson==3.17.2
+    # via
+    #   -r requirements/base.txt
+    #   xblock-utils
+six==1.15.0
+    # via
+    #   -r requirements/base.txt
+    #   bleach
+    #   edx-i18n-tools
+    #   fs
+    #   pyjwkest
+    #   python-dateutil
+    #   stevedore
+    #   xblock
+sqlparse==0.4.1
+    # via
+    #   -r requirements/base.txt
+    #   django
+stevedore==1.32.0
+    # via
+    #   -r requirements/base.txt
+    #   edx-opaque-keys
+urllib3==1.26.3
+    # via
+    #   -r requirements/base.txt
+    #   requests
+web-fragments==1.0.0
+    # via
+    #   -r requirements/base.txt
+    #   xblock
+    #   xblock-utils
+webencodings==0.5.1
+    # via
+    #   -r requirements/base.txt
+    #   bleach
+webob==1.8.7
+    # via
+    #   -r requirements/base.txt
+    #   xblock
+xblock-utils==2.1.2
+    # via -r requirements/base.txt
+xblock==1.4.0
+    # via
+    #   -r requirements/base.txt
+    #   xblock-utils
+
+# The following packages are considered to be unsafe in a requirements file:
+# setuptools