diff --git a/Makefile b/Makefile index ee10bea..f3e6503 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,5 @@ WORKING_DIR := recommender -EXTRACT_DIR := $(WORKING_DIR)/conf/locale/en/LC_MESSAGES -EXTRACTED_DJANGO_PARTIAL := $(EXTRACT_DIR)/django-partial.po -EXTRACTED_DJANGOJS_PARTIAL := $(EXTRACT_DIR)/djangojs-partial.po -EXTRACTED_DJANGO := $(EXTRACT_DIR)/django.po +JS_TARGET := $(WORKING_DIR)/public/js/translations COMMON_CONSTRAINTS_TXT=requirements/common_constraints.txt .PHONY: $(COMMON_CONSTRAINTS_TXT) @@ -22,12 +19,8 @@ upgrade: $(COMMON_CONSTRAINTS_TXT) ## update the requirements/*.txt files with pip-compile --upgrade -o requirements/ci.txt requirements/ci.in extract_translations: ## extract strings to be translated, outputting .po files - cd $(WORKING_DIR) && i18n_tool extract - mv $(EXTRACTED_DJANGO_PARTIAL) $(EXTRACTED_DJANGO) - # Safely concatenate djangojs if it exists - if test -f $(EXTRACTED_DJANGOJS_PARTIAL); then \ - msgcat $(EXTRACTED_DJANGO) $(EXTRACTED_DJANGOJS_PARTIAL) -o $(EXTRACTED_DJANGO) && \ - rm $(EXTRACTED_DJANGOJS_PARTIAL); \ - fi - sed -i'' -e 's/nplurals=INTEGER/nplurals=2/' $(EXTRACTED_DJANGO) - sed -i'' -e 's/plural=EXPRESSION/plural=\(n != 1\)/' $(EXTRACTED_DJANGO) + cd $(WORKING_DIR) && i18n_tool extract --no-segment --merge-po-files + +compile_translations: ## compile translation files, outputting .mo files for each supported language + cd $(WORKING_DIR) && i18n_tool generate -v + python manage.py compilejsi18n --namespace RecommenderXBlockI18N --output $(JS_TARGET) diff --git a/dev_settings.py b/dev_settings.py new file mode 100644 index 0000000..acc9d54 --- /dev/null +++ b/dev_settings.py @@ -0,0 +1,92 @@ +""" +Django settings for xblock-drag-and-drop-v2 project. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +from __future__ import absolute_import +import os +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +# This is just a container for running tests, it's okay to allow it to be +# defaulted here if not present in environment settings +SECRET_KEY = os.environ.get('SECRET_KEY', '&=@m=qyqg#l!f99ouuuinpbsv0ah001unk@q^7)bkr^^n5@q1=') + +# SECURITY WARNING: don't run with debug turned on in production! +# This is just a container for running tests +DEBUG = True + +TEMPLATE_DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ( + 'statici18n', + 'recommender', +) + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' + +# statici18n +# http://django-statici18n.readthedocs.io/en/latest/settings.html + +LANGUAGES = [ + ('ar', 'Arabic'), + ('de', 'German'), + ('en', 'English - Source Language'), + ('eo', 'Esperanto'), + ('es_419', 'Spanish (Latin America)'), + ('fr', 'French'), + ('he', 'Hebrew'), + ('hi', 'Hindi'), + ('it', 'Italian'), + ('ja', 'Japanese'), + ('ko', 'Korean (Korea)'), + ('nl', 'Dutch'), + ('pl', 'Polski'), + ('pt_BR', 'Portuguese (Brazil)'), + ('pt_PT', 'Portuguese (Portugal)'), + ('ru', 'Russian'), + ('tr', 'Turkish'), + ('zh_CN', 'Chinese (China)'), +] + +LOCALE_PATHS = [os.path.join(BASE_DIR, "locale")] + +STATICI18N_DOMAIN = 'text' +STATICI18N_NAMESPACE = 'RecommenderXBlockI18N' +STATICI18N_PACKAGES = ( + 'recommender', +) +STATICI18N_ROOT = 'recommender/public/js' +STATICI18N_OUTPUT_DIR = 'translations' diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..44a316e --- /dev/null +++ b/manage.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +import os +import sys +from django.core.management import execute_from_command_line + +if __name__ == "__main__": + os.environ.setdefault( + "DJANGO_SETTINGS_MODULE", + "dev_settings" + ) + + execute_from_command_line(sys.argv) diff --git a/recommender/__init__.py b/recommender/__init__.py index 6e5ec82..40dad5f 100644 --- a/recommender/__init__.py +++ b/recommender/__init__.py @@ -2,6 +2,8 @@ This XBlock will show a set of recommended resources which may be helpful to students solving a given problem. """ -from .recommender import RecommenderXBlock +# We avoid importing RecommenderXBlock here, because it's importing Filesystem from xblock.reference.plugins +# which is not loaded when running `manage.py` commands (which is used by `make compile_translations`) +# from .recommender import RecommenderXBlock __version__ = '2.1.0' diff --git a/recommender/conf/locale/config.yaml b/recommender/conf/locale/config.yaml index a968d94..c0fb690 100644 --- a/recommender/conf/locale/config.yaml +++ b/recommender/conf/locale/config.yaml @@ -2,3 +2,6 @@ locales: - en # English - Source Language + +ignore_dirs: + - public diff --git a/recommender/recommender.py b/recommender/recommender.py index 9173beb..d1e2ad2 100644 --- a/recommender/recommender.py +++ b/recommender/recommender.py @@ -18,6 +18,7 @@ import bleach from webob.response import Response +from django.utils import translation from xblock.core import XBlock from xblock.exceptions import JsonHandlerError @@ -944,6 +945,21 @@ def _construct_view_resource(self, resource): return result + @staticmethod + def _get_statici18n_js_url(): # pragma: no cover + """ + Returns the Javascript translation file for the currently selected language, if any found by `pkg_resources` + """ + lang_code = translation.get_language() + if not lang_code: + return None + text_js = 'public/js/translations/{lang_code}/text.js' + country_code = lang_code.split('-')[0] + for code in (translation.to_locale(lang_code), lang_code, country_code): + if pkg_resources.resource_exists(resource_loader.module_name, text_js.format(lang_code=code)): + return text_js.format(lang_code=code) + return None + def student_view(self, _context=None): # pylint: disable=unused-argument """ The primary view of the RecommenderXBlock, shown to students @@ -990,6 +1006,9 @@ def student_view(self, _context=None): # pylint: disable=unused-argument frag.add_css(self.resource_string("static/css/recommender.css")) frag.add_css(self.resource_string("static/css/introjs.css")) frag.add_javascript(self.resource_string("static/js/src/jquery.tooltipster.min.js")) + statici18n_js_url = self._get_statici18n_js_url() + if statici18n_js_url: + frag.add_javascript(self.resource_string(statici18n_js_url)) frag.add_javascript(self.resource_string("static/js/src/cats.js")) frag.add_javascript(self.resource_string("static/js/src/recommender.js")) frag.initialize_js('RecommenderXBlock', self.get_client_configuration()) @@ -1007,6 +1026,9 @@ def studio_view(self, _context=None): # pylint: disable=unused-argument )) frag.add_css(load("static/css/recommenderstudio.css")) frag.add_javascript_url("//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js") + statici18n_js_url = self._get_statici18n_js_url() + if statici18n_js_url: + frag.add_javascript(self.resource_string(statici18n_js_url)) frag.add_javascript(load("static/js/src/recommenderstudio.js")) frag.initialize_js('RecommenderXBlock') return frag diff --git a/recommender/static/js/src/cats.js b/recommender/static/js/src/cats.js index 8e58a0e..7d77f87 100644 --- a/recommender/static/js/src/cats.js +++ b/recommender/static/js/src/cats.js @@ -3,10 +3,26 @@ // // Note: The global `window.*` variables should be converted into local ones to avoid over-using global variables. This is a 2014-era legacy XBlock coding standard that should be refactored -- @OmarIthawi at Apr 4, 2023 // - var gettext = window.gettext || (function (string) { - // Shim Django's `gettext` if unavailable. - return string; - }); + var gettext; + if ('RecommenderXBlockI18N' in window) { + // Use Recommender's local translations + gettext = function(string) { + var translated = window.RecommenderXBlockI18N.gettext(string); + // if Recommender's translation is the same as the input, check if global has a different value + // This is useful for overriding the XBlock's string by themes (only for English) + if (string === translated && 'gettext' in window) { + translated = window.gettext(string); + } + return translated; + }; + } else if ('gettext' in window) { + // Use edxapp's global translations + gettext = window.gettext; + } + if (typeof gettext == "undefined") { + // No translations -- used by test environment + gettext = function(string) { return string; }; + } var span = function(text) { // Surround text with a span. diff --git a/requirements/base.in b/requirements/base.in index f3ca6f6..7a8fbb9 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -8,3 +8,4 @@ mako simplejson webob web_fragments +edx-i18n-tools diff --git a/requirements/base.txt b/requirements/base.txt index 804ca0b..494e31d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,26 +1,48 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # make upgrade # appdirs==1.4.4 # via fs -bleach==6.0.0 +asgiref==3.7.2 + # via django +bleach==6.1.0 + # via -r requirements/base.in +django==3.2.22 + # via + # -c requirements/common_constraints.txt + # edx-i18n-tools +edx-i18n-tools==1.3.0 # via -r requirements/base.in fs==2.4.16 # via -r requirements/base.in +lxml==4.9.3 + # via edx-i18n-tools mako==1.2.4 # via -r requirements/base.in markupsafe==2.1.3 # via mako -simplejson==3.19.1 +path==16.7.1 + # via edx-i18n-tools +polib==1.2.0 + # via edx-i18n-tools +pytz==2023.3.post1 + # via django +pyyaml==6.0.1 + # via edx-i18n-tools +simplejson==3.19.2 # via -r requirements/base.in six==1.16.0 # via # bleach # fs -web-fragments==2.0.0 +sqlparse==0.4.4 + # via django +typing-extensions==4.8.0 + # via asgiref +web-fragments==2.1.0 # via -r requirements/base.in webencodings==0.5.1 # via bleach diff --git a/requirements/ci.txt b/requirements/ci.txt index 5285a01..d487b6b 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # make upgrade @@ -8,26 +8,65 @@ appdirs==1.4.4 # via # -r requirements/test.txt # fs -bleach==6.0.0 +asgiref==3.7.2 + # via + # -r requirements/test.txt + # django +bleach==6.1.0 + # via -r requirements/test.txt +django==3.2.22 + # via + # -c requirements/common_constraints.txt + # -r requirements/test.txt + # edx-i18n-tools +edx-i18n-tools==1.3.0 # via -r requirements/test.txt fs==2.4.16 # via -r requirements/test.txt +lxml==4.9.3 + # via + # -r requirements/test.txt + # edx-i18n-tools mako==1.2.4 # via -r requirements/test.txt markupsafe==2.1.3 # via # -r requirements/test.txt # mako -pycodestyle==2.10.0 +path==16.7.1 + # via + # -r requirements/test.txt + # edx-i18n-tools +polib==1.2.0 + # via + # -r requirements/test.txt + # edx-i18n-tools +pycodestyle==2.11.0 # via -r requirements/test.txt -simplejson==3.19.1 +pytz==2023.3.post1 + # via + # -r requirements/test.txt + # django +pyyaml==6.0.1 + # via + # -r requirements/test.txt + # edx-i18n-tools +simplejson==3.19.2 # via -r requirements/test.txt six==1.16.0 # via # -r requirements/test.txt # bleach # fs -web-fragments==2.0.0 +sqlparse==0.4.4 + # via + # -r requirements/test.txt + # django +typing-extensions==4.8.0 + # via + # -r requirements/test.txt + # asgiref +web-fragments==2.1.0 # via -r requirements/test.txt webencodings==0.5.1 # via diff --git a/requirements/common_constraints.txt b/requirements/common_constraints.txt index 7e39123..afe6aa8 100644 --- a/requirements/common_constraints.txt +++ b/requirements/common_constraints.txt @@ -25,8 +25,3 @@ django-simple-history==3.0.0 # tox>4.0.0 isn't yet compatible with many tox plugins, causing CI failures in almost all repos. # Details can be found in this discussion: https://github.com/tox-dev/tox/discussions/1810 tox<4.0.0 - -# edx-sphinx-theme is not compatible with latest Sphinx==6.0.0 version -# Pinning Sphinx version unless the compatibility issue gets resolved -# For details, see issue https://github.com/openedx/edx-sphinx-theme/issues/197 -sphinx<6.0.0 diff --git a/requirements/pip.txt b/requirements/pip.txt index c9cbf00..33acbb0 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,14 +1,14 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: # # make upgrade # -wheel==0.40.0 +wheel==0.41.2 # via -r requirements/pip.in # The following packages are considered to be unsafe in a requirements file: -pip==23.1.2 +pip==23.2.1 # via -r requirements/pip.in -setuptools==67.8.0 +setuptools==68.2.2 # via -r requirements/pip.in diff --git a/requirements/pip_tools.txt b/requirements/pip_tools.txt index 86c333e..cc24a33 100644 --- a/requirements/pip_tools.txt +++ b/requirements/pip_tools.txt @@ -1,22 +1,25 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: # # make upgrade # -build==0.10.0 +build==1.0.3 # via pip-tools -click==8.1.3 +click==8.1.7 # via pip-tools -packaging==23.1 +packaging==23.2 # via build -pip-tools==6.13.0 +pip-tools==7.3.0 # via -r requirements/pip_tools.in pyproject-hooks==1.0.0 # via build tomli==2.0.1 - # via build -wheel==0.40.0 + # via + # build + # pip-tools + # pyproject-hooks +wheel==0.41.2 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff --git a/requirements/test.txt b/requirements/test.txt index 5089e85..65aaf71 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.10 # by the following command: # # make upgrade @@ -8,26 +8,65 @@ appdirs==1.4.4 # via # -r requirements/base.txt # fs -bleach==6.0.0 +asgiref==3.7.2 + # via + # -r requirements/base.txt + # django +bleach==6.1.0 + # via -r requirements/base.txt +django==3.2.22 + # via + # -c requirements/common_constraints.txt + # -r requirements/base.txt + # edx-i18n-tools +edx-i18n-tools==1.3.0 # via -r requirements/base.txt fs==2.4.16 # via -r requirements/base.txt +lxml==4.9.3 + # via + # -r requirements/base.txt + # edx-i18n-tools mako==1.2.4 # via -r requirements/base.txt markupsafe==2.1.3 # via # -r requirements/base.txt # mako -pycodestyle==2.10.0 +path==16.7.1 + # via + # -r requirements/base.txt + # edx-i18n-tools +polib==1.2.0 + # via + # -r requirements/base.txt + # edx-i18n-tools +pycodestyle==2.11.0 # via -r requirements/test.in -simplejson==3.19.1 +pytz==2023.3.post1 + # via + # -r requirements/base.txt + # django +pyyaml==6.0.1 + # via + # -r requirements/base.txt + # edx-i18n-tools +simplejson==3.19.2 # via -r requirements/base.txt six==1.16.0 # via # -r requirements/base.txt # bleach # fs -web-fragments==2.0.0 +sqlparse==0.4.4 + # via + # -r requirements/base.txt + # django +typing-extensions==4.8.0 + # via + # -r requirements/base.txt + # asgiref +web-fragments==2.1.0 # via -r requirements/base.txt webencodings==0.5.1 # via diff --git a/setup.py b/setup.py index 0da9bb8..a8dc26c 100644 --- a/setup.py +++ b/setup.py @@ -78,10 +78,10 @@ def get_version(file_path): license='AGPL 3.0', entry_points={ 'xblock.v1': [ - 'recommender = recommender:RecommenderXBlock', + 'recommender = recommender.recommender:RecommenderXBlock', ] }, - package_data=package_data("recommender", ["static", "templates", "translations"]), + package_data=package_data("recommender", ["static", "templates", "translations", "public"]), cmdclass={ 'install': XBlockInstall, },