mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Fix translation issue with javascript (#3246)
* 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
.
This commit is contained in:
parent
16ac1d97f7
commit
44b42050aa
4
.github/workflows/qc_checks.yaml
vendored
4
.github/workflows/qc_checks.yaml
vendored
@ -57,8 +57,8 @@ jobs:
|
|||||||
python3 check_js_templates.py
|
python3 check_js_templates.py
|
||||||
- name: Lint Javascript Files
|
- name: Lint Javascript Files
|
||||||
run: |
|
run: |
|
||||||
invoke render-js-files
|
python InvenTree/manage.py prerender
|
||||||
npx eslint js_tmp/*.js
|
npx eslint InvenTree/InvenTree/static_i18n/i18n/*.js
|
||||||
|
|
||||||
html:
|
html:
|
||||||
name: Style [HTML]
|
name: Style [HTML]
|
||||||
|
@ -218,18 +218,6 @@ logger.debug(f"STATIC_ROOT: '{STATIC_ROOT}'")
|
|||||||
|
|
||||||
INSTALLED_APPS = [
|
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
|
# InvenTree apps
|
||||||
'build.apps.BuildConfig',
|
'build.apps.BuildConfig',
|
||||||
'common.apps.CommonConfig',
|
'common.apps.CommonConfig',
|
||||||
@ -243,6 +231,18 @@ INSTALLED_APPS = [
|
|||||||
'plugin.apps.PluginAppConfig',
|
'plugin.apps.PluginAppConfig',
|
||||||
'InvenTree.apps.InvenTreeConfig', # InvenTree app runs last
|
'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
|
# Third part add-ons
|
||||||
'django_filters', # Extended filter functionality
|
'django_filters', # Extended filter functionality
|
||||||
'rest_framework', # DRF (Django Rest Framework)
|
'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)
|
Loading…
Reference in New Issue
Block a user