diff --git a/Makefile b/Makefile index 9bc015f..f4339a1 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ WORKING_DIR := recommender EXTRACT_DIR := $(WORKING_DIR)/conf/locale/en/LC_MESSAGES -EXTRACTED_DJANGO := $(EXTRACT_DIR)/django-partial.po -EXTRACTED_DJANGOJS := $(EXTRACT_DIR)/djangojs-partial.po -EXTRACTED_TEXT := $(EXTRACT_DIR)/django.po +JS_TARGET := $(WORKING_DIR)/public/js/translations +EXTRACTED_DJANGO_PARTIAL := $(EXTRACT_DIR)/django-partial.po +EXTRACTED_DJANGOJS_PARTIAL := $(EXTRACT_DIR)/djangojs-partial.po +EXTRACTED_DJANGO := $(EXTRACT_DIR)/django.po COMMON_CONSTRAINTS_TXT=requirements/common_constraints.txt .PHONY: $(COMMON_CONSTRAINTS_TXT) @@ -23,8 +24,15 @@ upgrade: $(COMMON_CONSTRAINTS_TXT) ## update the requirements/*.txt files with 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) + 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) + +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/manage.py b/manage.py new file mode 100755 index 0000000..e665f81 --- /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", + "recommender.conf.locale.settings" + ) + + execute_from_command_line(sys.argv) 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/conf/locale/settings.py b/recommender/conf/locale/settings.py new file mode 100644 index 0000000..d218366 --- /dev/null +++ b/recommender/conf/locale/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', '",cB3Jr.?xu[x_Ci]!%HP>#^AVmWi@r/W3u,w?pY+~J!R>;WN+,3}Sb{K=Jp~;&k') + +# 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/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.