diff --git a/.github/workflows/javascript.yaml b/.github/workflows/javascript.yaml new file mode 100644 index 0000000000..d5e5fca18f --- /dev/null +++ b/.github/workflows/javascript.yaml @@ -0,0 +1,28 @@ +# Check javascript template files + +name: Javascript Templates + +on: + push: + branches: + - master + + pull_request: + branches-ignore: + - l10* + +jobs: + + python: + runs-on: ubuntu-latest + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout Code + uses: actions/checkout@v2 + - name: Check Files + run: | + cd ci + python check_js_templates.py + \ No newline at end of file diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index fe22111232..f9ecaa394d 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -202,7 +202,7 @@ STATICFILES_DIRS = [ # Translated Template settings STATICFILES_I18_PREFIX = 'i18n' -STATICFILES_I18_SRC = os.path.join(BASE_DIR, 'templates', 'js') +STATICFILES_I18_SRC = os.path.join(BASE_DIR, 'templates', 'js', 'translated') STATICFILES_I18_TRG = STATICFILES_DIRS[0] + '_' + STATICFILES_I18_PREFIX STATICFILES_DIRS.append(STATICFILES_I18_TRG) STATICFILES_I18_TRG = os.path.join(STATICFILES_I18_TRG, STATICFILES_I18_PREFIX) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 41ef306a50..7d398c6e4f 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -93,28 +93,32 @@ settings_urls = [ url(r'^.*$', SettingsView.as_view(template_name='InvenTree/settings/settings.html'), name='settings'), ] -# Some javascript files are served 'dynamically', allowing them to pass through the Django translation layer +# These javascript files are served "dynamically" - i.e. rendered on demand dynamic_javascript_urls = [ - url(r'^api.js', DynamicJsView.as_view(template_name='js/api.js'), name='api.js'), - url(r'^attachment.js', DynamicJsView.as_view(template_name='js/attachment.js'), name='attachment.js'), - url(r'^barcode.js', DynamicJsView.as_view(template_name='js/barcode.js'), name='barcode.js'), - url(r'^bom.js', DynamicJsView.as_view(template_name='js/bom.js'), name='bom.js'), - url(r'^build.js', DynamicJsView.as_view(template_name='js/build.js'), name='build.js'), - url(r'^calendar.js', DynamicJsView.as_view(template_name='js/calendar.js'), name='calendar.js'), - url(r'^company.js', DynamicJsView.as_view(template_name='js/company.js'), name='company.js'), - url(r'^filters.js', DynamicJsView.as_view(template_name='js/filters.js'), name='filters.js'), - url(r'^forms.js', DynamicJsView.as_view(template_name='js/forms.js'), name='forms.js'), - url(r'^inventree.js', DynamicJsView.as_view(template_name='js/inventree.js'), name='inventree.js'), - url(r'^label.js', DynamicJsView.as_view(template_name='js/label.js'), name='label.js'), - url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/model_renderers.js'), name='model_renderers.js'), - url(r'^modals.js', DynamicJsView.as_view(template_name='js/modals.js'), name='modals.js'), - url(r'^nav.js', DynamicJsView.as_view(template_name='js/nav.js'), name='nav.js'), - url(r'^order.js', DynamicJsView.as_view(template_name='js/order.js'), name='order.js'), - url(r'^part.js', DynamicJsView.as_view(template_name='js/part.js'), name='part.js'), - url(r'^report.js', DynamicJsView.as_view(template_name='js/report.js'), name='report.js'), - url(r'^stock.js', DynamicJsView.as_view(template_name='js/stock.js'), name='stock.js'), - url(r'^tables.js', DynamicJsView.as_view(template_name='js/tables.js'), name='tables.js'), - url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/table_filters.js'), name='table_filters.js'), + url(r'^inventree.js', DynamicJsView.as_view(template_name='js/dynamic/inventree.js'), name='inventree.js'), +] + +# These javascript files are pased through the Django translation layer +translated_javascript_urls = [ + url(r'^api.js', DynamicJsView.as_view(template_name='js/translated/api.js'), name='api.js'), + url(r'^attachment.js', DynamicJsView.as_view(template_name='js/translated/attachment.js'), name='attachment.js'), + url(r'^barcode.js', DynamicJsView.as_view(template_name='js/translated/barcode.js'), name='barcode.js'), + url(r'^bom.js', DynamicJsView.as_view(template_name='js/translated/bom.js'), name='bom.js'), + url(r'^build.js', DynamicJsView.as_view(template_name='js/translated/build.js'), name='build.js'), + url(r'^calendar.js', DynamicJsView.as_view(template_name='js/translated/calendar.js'), name='calendar.js'), + url(r'^company.js', DynamicJsView.as_view(template_name='js/translated/company.js'), name='company.js'), + url(r'^filters.js', DynamicJsView.as_view(template_name='js/translated/filters.js'), name='filters.js'), + url(r'^forms.js', DynamicJsView.as_view(template_name='js/translated/forms.js'), name='forms.js'), + url(r'^label.js', DynamicJsView.as_view(template_name='js/translated/label.js'), name='label.js'), + url(r'^model_renderers.js', DynamicJsView.as_view(template_name='js/translated/model_renderers.js'), name='model_renderers.js'), + url(r'^modals.js', DynamicJsView.as_view(template_name='js/translated/modals.js'), name='modals.js'), + url(r'^nav.js', DynamicJsView.as_view(template_name='js/translated/nav.js'), name='nav.js'), + url(r'^order.js', DynamicJsView.as_view(template_name='js/translated/order.js'), name='order.js'), + url(r'^part.js', DynamicJsView.as_view(template_name='js/translated/part.js'), name='part.js'), + url(r'^report.js', DynamicJsView.as_view(template_name='js/translated/report.js'), name='report.js'), + url(r'^stock.js', DynamicJsView.as_view(template_name='js/translated/stock.js'), name='stock.js'), + url(r'^tables.js', DynamicJsView.as_view(template_name='js/translated/tables.js'), name='tables.js'), + url(r'^table_filters.js', DynamicJsView.as_view(template_name='js/translated/table_filters.js'), name='table_filters.js'), ] urlpatterns = [ @@ -123,7 +127,8 @@ urlpatterns = [ url(r'^supplier-part/', include(supplier_part_urls)), # "Dynamic" javascript files which are rendered using InvenTree templating. - url(r'^dynamic/', include(dynamic_javascript_urls)), + url(r'^js/dynamic/', include(dynamic_javascript_urls)), + url(r'^js/i18n/', include(translated_javascript_urls)), url(r'^common/', include(common_urls)), diff --git a/InvenTree/templates/js/inventree.js b/InvenTree/templates/js/dynamic/inventree.js similarity index 100% rename from InvenTree/templates/js/inventree.js rename to InvenTree/templates/js/dynamic/inventree.js diff --git a/InvenTree/templates/js/api.js b/InvenTree/templates/js/translated/api.js similarity index 100% rename from InvenTree/templates/js/api.js rename to InvenTree/templates/js/translated/api.js diff --git a/InvenTree/templates/js/attachment.js b/InvenTree/templates/js/translated/attachment.js similarity index 100% rename from InvenTree/templates/js/attachment.js rename to InvenTree/templates/js/translated/attachment.js diff --git a/InvenTree/templates/js/barcode.js b/InvenTree/templates/js/translated/barcode.js similarity index 100% rename from InvenTree/templates/js/barcode.js rename to InvenTree/templates/js/translated/barcode.js diff --git a/InvenTree/templates/js/bom.js b/InvenTree/templates/js/translated/bom.js similarity index 100% rename from InvenTree/templates/js/bom.js rename to InvenTree/templates/js/translated/bom.js diff --git a/InvenTree/templates/js/build.js b/InvenTree/templates/js/translated/build.js similarity index 100% rename from InvenTree/templates/js/build.js rename to InvenTree/templates/js/translated/build.js diff --git a/InvenTree/templates/js/calendar.js b/InvenTree/templates/js/translated/calendar.js similarity index 100% rename from InvenTree/templates/js/calendar.js rename to InvenTree/templates/js/translated/calendar.js diff --git a/InvenTree/templates/js/company.js b/InvenTree/templates/js/translated/company.js similarity index 100% rename from InvenTree/templates/js/company.js rename to InvenTree/templates/js/translated/company.js diff --git a/InvenTree/templates/js/filters.js b/InvenTree/templates/js/translated/filters.js similarity index 100% rename from InvenTree/templates/js/filters.js rename to InvenTree/templates/js/translated/filters.js diff --git a/InvenTree/templates/js/forms.js b/InvenTree/templates/js/translated/forms.js similarity index 100% rename from InvenTree/templates/js/forms.js rename to InvenTree/templates/js/translated/forms.js diff --git a/InvenTree/templates/js/label.js b/InvenTree/templates/js/translated/label.js similarity index 100% rename from InvenTree/templates/js/label.js rename to InvenTree/templates/js/translated/label.js diff --git a/InvenTree/templates/js/modals.js b/InvenTree/templates/js/translated/modals.js similarity index 100% rename from InvenTree/templates/js/modals.js rename to InvenTree/templates/js/translated/modals.js diff --git a/InvenTree/templates/js/model_renderers.js b/InvenTree/templates/js/translated/model_renderers.js similarity index 100% rename from InvenTree/templates/js/model_renderers.js rename to InvenTree/templates/js/translated/model_renderers.js diff --git a/InvenTree/templates/js/nav.js b/InvenTree/templates/js/translated/nav.js similarity index 100% rename from InvenTree/templates/js/nav.js rename to InvenTree/templates/js/translated/nav.js diff --git a/InvenTree/templates/js/order.js b/InvenTree/templates/js/translated/order.js similarity index 100% rename from InvenTree/templates/js/order.js rename to InvenTree/templates/js/translated/order.js diff --git a/InvenTree/templates/js/part.js b/InvenTree/templates/js/translated/part.js similarity index 100% rename from InvenTree/templates/js/part.js rename to InvenTree/templates/js/translated/part.js diff --git a/InvenTree/templates/js/report.js b/InvenTree/templates/js/translated/report.js similarity index 100% rename from InvenTree/templates/js/report.js rename to InvenTree/templates/js/translated/report.js diff --git a/InvenTree/templates/js/stock.js b/InvenTree/templates/js/translated/stock.js similarity index 100% rename from InvenTree/templates/js/stock.js rename to InvenTree/templates/js/translated/stock.js diff --git a/InvenTree/templates/js/table_filters.js b/InvenTree/templates/js/translated/table_filters.js similarity index 100% rename from InvenTree/templates/js/table_filters.js rename to InvenTree/templates/js/translated/table_filters.js diff --git a/InvenTree/templates/js/tables.js b/InvenTree/templates/js/translated/tables.js similarity index 100% rename from InvenTree/templates/js/tables.js rename to InvenTree/templates/js/translated/tables.js diff --git a/ci/check_js_templates.py b/ci/check_js_templates.py new file mode 100644 index 0000000000..aa40b88742 --- /dev/null +++ b/ci/check_js_templates.py @@ -0,0 +1,121 @@ +""" +Test that the "translated" javascript files to not contain template tags +which need to be determined at "run time". + +This is because the "translated" javascript files are compiled into the "static" directory. + +They should only contain template tags that render static information. +""" + +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import sys +import re +import os +import pathlib + +here = os.path.abspath(os.path.dirname(__file__)) +template_dir = os.path.abspath(os.path.join(here, '..', 'InvenTree', 'templates')) + +# We only care about the 'translated' files +js_i18n_dir = os.path.join(template_dir, 'js', 'translated') +js_dynamic_dir = os.path.join(template_dir, 'js', 'dynamic') + +errors = 0 + +print("=================================") +print("Checking static javascript files:") +print("=================================") + +def check_invalid_tag(data): + + pattern = r"{%(\w+)" + + err_count = 0 + + for idx, line in enumerate(data): + + results = re.findall(pattern, line) + + for result in results: + err_count += 1 + + print(f" - Error on line {idx+1}: %{{{result[0]}") + + return err_count + +def check_prohibited_tags(data): + + allowed_tags = [ + 'if', + 'elif', + 'else', + 'endif', + 'for', + 'endfor', + 'trans', + 'load', + 'include', + 'url', + ] + + pattern = r"{% (\w+)\s" + + err_count = 0 + + has_trans = False + + for idx, line in enumerate(data): + + for tag in re.findall(pattern, line): + + if tag not in allowed_tags: + print(f" > Line {idx+1} - '{tag}'") + err_count += 1 + + if tag == 'trans': + has_trans = True + + if not has_trans: + print(f" > missing 'trans' tag") + err_count += 1 + + return err_count + + +for filename in pathlib.Path(js_i18n_dir).rglob('*.js'): + + print(f"Checking file 'translated/{os.path.basename(filename)}':") + + with open(filename, 'r') as js_file: + data = js_file.readlines() + + errors += check_invalid_tag(data) + errors += check_prohibited_tags(data) + +for filename in pathlib.Path(js_dynamic_dir).rglob('*.js'): + + print(f"Checking file 'dynamic/{os.path.basename(filename)}':") + + # Check that the 'dynamic' files do not contains any translated strings + with open(filename, 'r') as js_file: + data = js_file.readlines() + + pattern = r'{% trans ' + + err_count = 0 + + for idx, line in enumerate(data): + + results = re.findall(pattern, line) + + if len(results) > 0: + errors += 1 + + print(f" > {{% trans %}} tag found at line {idx + 1}") + +if errors > 0: + print(f"Found {errors} incorrect template tags") + +sys.exit(errors)