mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	* Adds a custom translation node class to strip dirty characters from translated strings
* Update javascript files to use new template tag
* Override behaviour of {% load i18n %}
- No longer requires custom tag loading
- All templates now use escaped translation values
- Requires re-ordering of app loading
- Revert js_i18n to simply i18n
* CI step now lints JS files compiled in each locale
* Checking that the CI step fails
* Revert "Checking that the CI step fails"
This reverts commit ba2be0470d.
(cherry picked from commit 44b42050aa)
			
			
This commit is contained in:
		
							
								
								
									
										4
									
								
								.github/workflows/qc_checks.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/qc_checks.yaml
									
									
									
									
										vendored
									
									
								
							@@ -77,8 +77,8 @@ jobs:
 | 
			
		||||
          python check_js_templates.py
 | 
			
		||||
      - name: Lint Javascript Files
 | 
			
		||||
        run: |
 | 
			
		||||
          invoke render-js-files
 | 
			
		||||
          npx eslint js_tmp/*.js
 | 
			
		||||
          python InvenTree/manage.py prerender
 | 
			
		||||
          npx eslint InvenTree/InvenTree/static_i18n/i18n/*.js
 | 
			
		||||
 | 
			
		||||
  html:
 | 
			
		||||
    name: html template files
 | 
			
		||||
 
 | 
			
		||||
@@ -217,18 +217,6 @@ logger.debug(f"STATIC_ROOT: '{STATIC_ROOT}'")
 | 
			
		||||
 | 
			
		||||
INSTALLED_APPS = [
 | 
			
		||||
 | 
			
		||||
    # Core django modules
 | 
			
		||||
    'django.contrib.admin',
 | 
			
		||||
    'django.contrib.auth',
 | 
			
		||||
    'django.contrib.contenttypes',
 | 
			
		||||
    'user_sessions',                # db user sessions
 | 
			
		||||
    'django.contrib.messages',
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    'django.contrib.sites',
 | 
			
		||||
 | 
			
		||||
    # Maintenance
 | 
			
		||||
    'maintenance_mode',
 | 
			
		||||
 | 
			
		||||
    # InvenTree apps
 | 
			
		||||
    'build.apps.BuildConfig',
 | 
			
		||||
    'common.apps.CommonConfig',
 | 
			
		||||
@@ -242,6 +230,18 @@ INSTALLED_APPS = [
 | 
			
		||||
    'plugin.apps.PluginAppConfig',
 | 
			
		||||
    'InvenTree.apps.InvenTreeConfig',       # InvenTree app runs last
 | 
			
		||||
 | 
			
		||||
    # Core django modules
 | 
			
		||||
    'django.contrib.admin',
 | 
			
		||||
    'django.contrib.auth',
 | 
			
		||||
    'django.contrib.contenttypes',
 | 
			
		||||
    'user_sessions',                # db user sessions
 | 
			
		||||
    'django.contrib.messages',
 | 
			
		||||
    'django.contrib.staticfiles',
 | 
			
		||||
    'django.contrib.sites',
 | 
			
		||||
 | 
			
		||||
    # Maintenance
 | 
			
		||||
    'maintenance_mode',
 | 
			
		||||
 | 
			
		||||
    # Third part add-ons
 | 
			
		||||
    'django_filters',                       # Extended filter functionality
 | 
			
		||||
    'rest_framework',                       # DRF (Django Rest Framework)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										121
									
								
								InvenTree/part/templatetags/i18n.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								InvenTree/part/templatetags/i18n.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
			
		||||
"""This module provides custom translation tags specifically for use with javascript code.
 | 
			
		||||
 | 
			
		||||
Translated strings are escaped, such that they can be used as string literals in a javascript file.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import django.templatetags.i18n
 | 
			
		||||
from django import template
 | 
			
		||||
from django.template import TemplateSyntaxError
 | 
			
		||||
from django.templatetags.i18n import TranslateNode
 | 
			
		||||
 | 
			
		||||
import bleach
 | 
			
		||||
 | 
			
		||||
register = template.Library()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomTranslateNode(TranslateNode):
 | 
			
		||||
    """Custom translation node class, which sanitizes the translated strings for javascript use"""
 | 
			
		||||
 | 
			
		||||
    def render(self, context):
 | 
			
		||||
        """Custom render function overrides / extends default behaviour"""
 | 
			
		||||
 | 
			
		||||
        result = super().render(context)
 | 
			
		||||
 | 
			
		||||
        result = bleach.clean(result)
 | 
			
		||||
 | 
			
		||||
        # Remove any escape sequences
 | 
			
		||||
        for seq in ['\a', '\b', '\f', '\n', '\r', '\t', '\v']:
 | 
			
		||||
            result = result.replace(seq, '')
 | 
			
		||||
 | 
			
		||||
        # Remove other disallowed characters
 | 
			
		||||
        for c in ['\\', '`', ';', '|', '&']:
 | 
			
		||||
            result = result.replace(c, '')
 | 
			
		||||
 | 
			
		||||
        # Escape any quotes contained in the string
 | 
			
		||||
        result = result.replace("'", r"\'")
 | 
			
		||||
        result = result.replace('"', r'\"')
 | 
			
		||||
 | 
			
		||||
        # Return the 'clean' resulting string
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@register.tag("translate")
 | 
			
		||||
@register.tag("trans")
 | 
			
		||||
def do_translate(parser, token):
 | 
			
		||||
    """Custom translation function, lifted from https://github.com/django/django/blob/main/django/templatetags/i18n.py
 | 
			
		||||
 | 
			
		||||
    The only difference is that we pass this to our custom rendering node class
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    bits = token.split_contents()
 | 
			
		||||
    if len(bits) < 2:
 | 
			
		||||
        raise TemplateSyntaxError("'%s' takes at least one argument" % bits[0])
 | 
			
		||||
    message_string = parser.compile_filter(bits[1])
 | 
			
		||||
    remaining = bits[2:]
 | 
			
		||||
 | 
			
		||||
    noop = False
 | 
			
		||||
    asvar = None
 | 
			
		||||
    message_context = None
 | 
			
		||||
    seen = set()
 | 
			
		||||
    invalid_context = {"as", "noop"}
 | 
			
		||||
 | 
			
		||||
    while remaining:
 | 
			
		||||
        option = remaining.pop(0)
 | 
			
		||||
        if option in seen:
 | 
			
		||||
            raise TemplateSyntaxError(
 | 
			
		||||
                "The '%s' option was specified more than once." % option,
 | 
			
		||||
            )
 | 
			
		||||
        elif option == "noop":
 | 
			
		||||
            noop = True
 | 
			
		||||
        elif option == "context":
 | 
			
		||||
            try:
 | 
			
		||||
                value = remaining.pop(0)
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                raise TemplateSyntaxError(
 | 
			
		||||
                    "No argument provided to the '%s' tag for the context option."
 | 
			
		||||
                    % bits[0]
 | 
			
		||||
                )
 | 
			
		||||
            if value in invalid_context:
 | 
			
		||||
                raise TemplateSyntaxError(
 | 
			
		||||
                    "Invalid argument '%s' provided to the '%s' tag for the context "
 | 
			
		||||
                    "option" % (value, bits[0]),
 | 
			
		||||
                )
 | 
			
		||||
            message_context = parser.compile_filter(value)
 | 
			
		||||
        elif option == "as":
 | 
			
		||||
            try:
 | 
			
		||||
                value = remaining.pop(0)
 | 
			
		||||
            except IndexError:
 | 
			
		||||
                raise TemplateSyntaxError(
 | 
			
		||||
                    "No argument provided to the '%s' tag for the as option." % bits[0]
 | 
			
		||||
                )
 | 
			
		||||
            asvar = value
 | 
			
		||||
        else:
 | 
			
		||||
            raise TemplateSyntaxError(
 | 
			
		||||
                "Unknown argument for '%s' tag: '%s'. The only options "
 | 
			
		||||
                "available are 'noop', 'context' \"xxx\", and 'as VAR'."
 | 
			
		||||
                % (
 | 
			
		||||
                    bits[0],
 | 
			
		||||
                    option,
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
        seen.add(option)
 | 
			
		||||
 | 
			
		||||
    return CustomTranslateNode(message_string, noop, asvar, message_context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Re-register tags which we have not explicitly overridden
 | 
			
		||||
register.tag("blocktrans", django.templatetags.i18n.do_block_translate)
 | 
			
		||||
register.tag("blocktranslate", django.templatetags.i18n.do_block_translate)
 | 
			
		||||
 | 
			
		||||
register.tag("language", django.templatetags.i18n.language)
 | 
			
		||||
 | 
			
		||||
register.tag("get_available_languages", django.templatetags.i18n.do_get_available_languages)
 | 
			
		||||
register.tag("get_language_info", django.templatetags.i18n.do_get_language_info)
 | 
			
		||||
register.tag("get_language_info_list", django.templatetags.i18n.do_get_language_info_list)
 | 
			
		||||
register.tag("get_current_language", django.templatetags.i18n.do_get_current_language)
 | 
			
		||||
register.tag("get_current_language_bidi", django.templatetags.i18n.do_get_current_language_bidi)
 | 
			
		||||
 | 
			
		||||
register.filter("language_name", django.templatetags.i18n.language_name)
 | 
			
		||||
register.filter("language_name_translated", django.templatetags.i18n.language_name_translated)
 | 
			
		||||
register.filter("language_name_local", django.templatetags.i18n.language_name_local)
 | 
			
		||||
register.filter("language_bidi", django.templatetags.i18n.language_bidi)
 | 
			
		||||
		Reference in New Issue
	
	Block a user